与狼共舞——为Go2安装M416全自动水弹枪(二)
导语
作为本系列的开篇,有必要先做些约定:
- 以后的内容仅提供核心功能的代码,尽可能简洁,毕竟每个狗主都会有自己的想法,会添加自己的功能,写太多不容易整合,更何况我也没做什么复杂功能。
- 提供的代码片段不进行过多解释,程序语言也是语言,“领会精神“”就好。
- 不管是硬件设计还是软件编程部分,咱遵循三哥军工的设计思路,能依赖现有软件功能的就绝不自己编码,能购买的电子模块绝不自己动手制作。
- 机器人操作系统采用
Ros2 Foxy
,软件编程使用python
语言,硬件编程c
语言,操作系统Ubuntu 20.04
。
功能
这里的功能仅指与架枪相关的功能。(水弹枪是儿子友情赞助的,请勿对人射击。)
- 通过3D打印机打印支架和电器盒子,固定枪和电路板。
- 由go2的扩展坞发送UDP广播消息给控制电路,击发开火。
- 编程实现原厂遥控器的
向上按钮
作为开火键。 - 用
cv2
实现一个简单的人脸识别系统。 - 实现一个
deepseek agent
功能,让go2能理解你发出的指令,并按照你的指令执行任务(如,找到张三,立即开火。 或者 打个招呼 ...)
硬件
先上图,实物接线图:
图中的板子,都是淘宝上淘来的,其中控制板150元左右、DCDC模块2元左右,电子开关不到1元。
1.水弹枪
《绝地求生》装备M416全自动步枪,许多年前买的,不记得多少钱了,原先还有很多附件,都丢光了。破是破了点,但还能打,玩起来很解压。靠2节18650电池驱动(3.7+3.7V=7.4V),时间久了,电池也已经失效,因此,从go2的12v输出取电,通过一个可调DC-DC降压模块给枪供电。
2.控制板
以前买的一块开发板,STM32F103C8T6芯片+W5500以太网模块+2路CAN+2路485,价值150元左右,其实控制枪不用这么多功能,只要有W5500以太网模块和GPIO口输出的板子就行,GPIO口控制电子开关给枪供电。
3.DCDC模块
把go2的12V直流电降为7.4V。
4.电子开关
接收GPIO控制信号,打开或关闭枪的电源,枪有电就发射。(枪的扳机被我绑在击发位置)
控制程序
- STM32CubeMX
-
移植W5500驱动库
-- 1. 下载W5500驱动库,gitee下载地址、github下载地址。将ioLibrary_Driver
目录拷贝到你的工程下面。
-- 2. 将ioLibrary_Driver\Ethernet
目录下的3个文件及其头文件——w5500.c
,wizchip_conf.c
,socket.c
,添加到你的工程中。 -
控制程序
该部分代码使用了FreeRTOS
操作系统,监听UDP 5001
端口,当接收到消息含有fire
字符串是,PB7口持续1秒的高电平。也就是说当收到UDP消息fire
的时候,M416枪开火1秒。
/* USER CODE END Header_StartTaskSendMsg */
void StartTaskGetMsg(void *argument)
{
/* USER CODE BEGIN StartTaskSendMsg */
/* Infinite loop */
for(;;)
{
do_udp();
if (fired == 1)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET);
osDelay(1000);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_7);
fired = 0;
}
osDelay(1000);
}
/* USER CODE END StartTaskSendMsg */
}
myudp.c
程序中定义了W5500的网络信息,监听端口、ip地址等,并处理接收到的消息。
myudp.h
#ifndef MYUDP_H
#define MYUDP_H
#include "myudp.h"
#include "main.h"
#include "w5500.h"
#include "socket.h"
#include "wizchip_conf.h"
#include "spi.h"
#include <string.h>
#define SOCK_UDPS 2
extern uint8_t buff[128];
extern uint8_t UDP_send_buff[128];
extern uint8_t remote_ip[4];
extern uint16_t remote_port;
void UDPinit(void);
void do_udp(void);
void udp_send(uint8_t* data, uint8_t len);
void Analysis(uint8_t *buf);
#endif // MYUDP_H
在这里可以修改你的IP地址,监听端口,处理接收到的消息。
myudp.c
#include "myudp.h"
#include "main.h"
#include "w5500.h"
#include "socket.h"
#include "wizchip_conf.h"
#include "spi.h"
#include <string.h>
uint8_t buff[128];
uint8_t UDP_send_buff[128];
uint8_t remote_ip[4] = {255,255,255,255};
uint16_t remote_port = 8080;
//片选
void W5500_Select(void) {
HAL_GPIO_WritePin(SCSn_GPIO_Port, SCSn_Pin, GPIO_PIN_RESET);
}
void W5500_Unselect(void) {
HAL_GPIO_WritePin(SCSn_GPIO_Port, SCSn_Pin, GPIO_PIN_SET);
}
void W5500_Restart(void) {
HAL_GPIO_WritePin(RSTn_GPIO_Port, RSTn_Pin, GPIO_PIN_RESET);
HAL_Delay(1); // delay 1ms
HAL_GPIO_WritePin(RSTn_GPIO_Port, RSTn_Pin, GPIO_PIN_SET);
HAL_Delay(1600); // delay 1600ms
}
void W5500_ReadBuff(uint8_t* buff, uint16_t len) {
HAL_SPI_Receive(&hspi2, buff, len, HAL_MAX_DELAY);
}
void W5500_WriteBuff(uint8_t* buff, uint16_t len) {
HAL_SPI_Transmit(&hspi2, buff, len, HAL_MAX_DELAY);
}
uint8_t W5500_ReadByte(void) {
uint8_t byte;
W5500_ReadBuff(&byte, sizeof(byte));
return byte;
}
void W5500_WriteByte(uint8_t byte) {
W5500_WriteBuff(&byte, sizeof(byte));
}
//配置W5500网络信息
wiz_NetInfo gSetNetInfo = {
.mac = {0x00, 0x08, 0xdc, 0x11, 0x11, 0x11},
.ip = {192, 168, 123, 55},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 123, 1},
.dns = {144, 144, 144, 144},
.dhcp = NETINFO_STATIC};
wiz_NetInfo gGetNetInfo;
enum Status
{
Failed = 0,
Success = 1
};
/**
* @brief valid the result of set net info
* @return 1: Success
* 0: Failed
*/
uint8_t validSetNetInfoResult(wiz_NetInfo* _set, wiz_NetInfo* _get)
{
return (!memcmp(_set, _get, sizeof(wiz_NetInfo))); // if same, memcmp return 0
}
void UDPinit(void)
{
reg_wizchip_cs_cbfunc(W5500_Select, W5500_Unselect);
reg_wizchip_spi_cbfunc(W5500_ReadByte, W5500_WriteByte);
W5500_Restart(); // hardware restart through RESET pin
ctlnetwork(CN_SET_NETINFO, (void*)&gSetNetInfo); // set net info
// maybe need delay
ctlnetwork(CN_GET_NETINFO, (void*)&gGetNetInfo); // get net info
// W5500 has 8 channel, 32k buffer, 2 means 2KBytes
uint8_t buffer_size_8channel_tx_rx[16] = {2, 2, 2, 2, 2, 2, 2, 2, // 8 channel tx
2, 2, 2, 2, 2, 2, 2, 2}; // 8 channel rx
if(ctlwizchip(CW_INIT_WIZCHIP,(void*)buffer_size_8channel_tx_rx))
{
// failed
}
uint8_t sta = getSn_SR(SOCK_UDPS);
if(sta == SOCK_CLOSED)
{
socket(SOCK_UDPS, Sn_MR_UDP, 5001, 0);
}
HAL_Delay(100);
}
void do_udp(void)
{
uint16_t len=0;
switch(getSn_SR(SOCK_UDPS)) /*获取socket的状态*/
{
case SOCK_CLOSED: /*socket处于关闭状态*/
socket(SOCK_UDPS,Sn_MR_UDP,5001,0); /*初始化socket*/
break;
case SOCK_UDP: /*socket初始化完成*/
HAL_Delay(10);
if(getSn_IR(SOCK_UDPS) & Sn_IR_RECV) //检查是否有接收中断
{
setSn_IR(SOCK_UDPS, Sn_IR_RECV); /*清接收中断*/
}
if((len=getSn_RX_RSR(SOCK_UDPS))>0) /*接收到数据*/
{
recvfrom(SOCK_UDPS,buff, len, remote_ip,&remote_port); /*W5500接收计算机发送来的数据W5500接收计算机发送来的数据,并获取发送方的IP地址和端口号*/
sendto(SOCK_UDPS,buff,len-8, remote_ip, remote_port); /*W5500把接收到的数据发送*/
Analysis(buff);//分析数据
memset(buff, 0, sizeof(buff));
}
break;
}
}
void udp_send(uint8_t* data,uint8_t len)
{
sendto(SOCK_UDPS, data, len, remote_ip, remote_port);
memset(data, 0, len);
}
void SystemReset() {
__HAL_RCC_BACKUPRESET_FORCE(); // 强制备份域复位
__HAL_RCC_BACKUPRESET_RELEASE(); // 释放备份域复位
NVIC_SystemReset(); // 发起系统复位
}
void Analysis(uint8_t *buf)
{
char * gun = "fire";
char * result = strstr((char*)buf, gun);
if (result != NULL)
{
fired =1;
return;
}
char * devreset ="reset";
result = strstr((char*)buf, devreset);
if (result != NULL)
{
SystemReset();
return;
}
}
待续
预祝您,成功将以上代码烧写进你的板子中。接下来,我们开始给go2扩展坞写ROS2程序,让它能够通过原厂遥控器击发开火功能。