微型坦克
DIY是我从小的兴趣,学生时代的梦想是自己能够亲自动手造一辆智能车、遥控飞机,可惜由于各种现实原因一直未能亲手实现。工作后也因为工作繁忙等原因未能下定决心而搁浅。直到前段时间看到差评君分享油管的一位老外朋友做的智能坦克实在太漂亮了,顿时激发了我心中封存多年的DIY乐趣,下定决心要干一场!于是买好材料说干就干。由于工程序猿工作性质的原因,平时上班项目非常忙,只能下班或者周末抽时间做,经过大概1个半月断续的捣鼓,已经实现基本功能,剩下的就是代码优化了。
全部代码地址:
git clone https://github.com/guomingyi/fury.git
上位机是Android手机使用java实现,下位机是raspberry Pi 的 linux OS使用 c实现,原理很简单就是开启服务实现Android手机和Linux 的 socket通信。
舵机控制部分由于raspberry Pi没有HW Pwm,开始试图使用linux开线程的SWpwm来实现却发现精度太低,舵机狂抖动,失败而告终。最后找了好久终于发现一个老外大牛写的ServoBlaster,直接通过mmap去操作gpio寄存器地址来实现,精度虽然达不到HW PWM,但是控制舵机已经足够了,代码非常经典!只有膜拜!
视频传输部分使用轻量级开源项目 mjpg-streamer做视频采集,默认的发送方式是基于TCP/IP的Http协议方式,但是经过实际测试发现,视频不是很流畅,大概有个1s左右的卡顿,即使用最小的320x240也还是一样。这个对于我这种追求流畅的人来说是无法忍受的,所以我使用udp协议改写了jpeg数据发送接收代码,解了不少bug后发现在640x800分辨率下很流畅基本没有卡顿.
jpeg 帧打包发送:
(*frame是待发送的jpeg图像帧)
static int sendToRemoteServer(struct sockaddr_in *client, char *frame, int len, int port) {
int size = sizeof(struct sockaddr_in);
client->sin_port = htons(port);
int i, j = 0;
int mj = len % PKG_BUF_SIZE;
int n = (mj > 0 ? 1 : 0);
if(len - PKG_BUF_SIZE > 0) {
n += (int)(len / PKG_BUF_SIZE);
}
char *p = frame;
int send_len = 0;
char s_buf[PKG_BUF_SIZE+1024] = {0};
printf("sendToRemoteServer:port:%d,len:%d,mj:%d,n:%d\n",port,len,mj,n);
for(i = 1; i <= n; i++) {
memset(s_buf, 0, TOTAL_SEND_SIZE);
s_buf[0] = n;
s_buf[1] = i;
if(i == n && mj > 0) {
if(len - PKG_BUF_SIZE > 0) {
send_len = (len - PKG_BUF_SIZE*(i-1) +1 +1);
}
else {
send_len = len;
}
memcpy((s_buf +TAG_SIZE), (void*)p, send_len);
}
else {
send_len = TOTAL_SEND_SIZE;
memcpy((s_buf +TAG_SIZE), (void*)p, (send_len -1 -1));
}
printf("%d/%d/%d\n",i,n,send_len);
if(sendto(send_socket_fd, s_buf, send_len, 0, (struct sockaddr*)client, size) < 0) {
pERROR("sendto");
return -1;
}
p = p + PKG_BUF_SIZE;
}
return 0;
}
Android 接收解包代码:
while(true) {
try {
DatagramPacket recv_pkt = new DatagramPacket(frame_buf, frame_buf.length);
recv_socket.receive(recv_pkt);
byte[] tmp = recv_pkt.getData();
//pkg idx,number.
if(tmp[1] <= tmp[0]) {
System.arraycopy(tmp,2,show_buf,show_length,recv_pkt.getLength());
show_length += recv_pkt.getLength()-2;
if(tmp[1] != tmp[0]){
continue;
}
}
...
}
上图:
舵机+摄像头,舵机很便宜,摄像头好贵...
硬件其实很简单,但是手工焊接需要很细心否则容易出各种问题..
电机驱动板是经典的 L298N,使用SWPWM实现调速,精度一般.
新加的OLED显示器采用模拟SPI通信,用于显示一些例如IP地址之类的简单信息.
手动打造
电源板的作用是实现3.7*4 v的电机供电和4.2v的充电转换,弄了4节锂电池,动力杠杠的!
两组电池分别给电机和raspberry Pi供电:
手机控制端截图:
左边控制坦克前后左右移动,右边控制舵机摄像头上下左右转动。
音量键控制电机转速。