赞
踩
stm32F407+k210视觉小车
暑假没事做,从学校带回了一些开发板(stm32F407和k210),在B站学习了串口的多位通信以及串级PID,制作了一个具有来拒去留、可控制位置、控制轮子转速以及舵机转向功能的视觉小车。在这里做一个开源,顺便讲解一下该视觉小车的制作思路和过程。演示视频戳这里https://www.bilibili.com/video/BV15Y4y1F7VQ?spm_id_from=333.999.0.0&vd_source=25a8aa89291db245e99a1e95bd6f2b19
(开源链接在文章最后)
小车最基本的就是它的轮子了,其实每个小车基本上都会用到读轮子的编码器以及用PID控制它的转速。知道了这一点,那么接下来便是操控它的转速,来靠近目标。而想要识别目标位置,则需要用到视觉模块(这里用的是k210)。
当识别到目标的时候,k210会将图中白框的面积以及中间的十字坐标通过串口传将数据包递给stm32,stm32获得数值(下图OLED屏幕最下面一行就是数据包)
上文说到,k210会回传解析包。单片机则会解析它们,具体方式也就是取余数或者是取百位/千位。比如我这里使用十位数据进行数据包传输,其中后三位数据是位置判断,那么就需要对十位数据DATA_NUM进行取余运算。
Orientation=DATA_NUM%1000;
Distance=(DATA_NUM-Orientation)/1000%1000000;
这样就得到了方位-Orientation,以及距离-Distance
通过白框的面积大小判断与物体的间距,通过中心十字判断左右。
距离控制:当白框变大且小车在不断靠近目标的时候,白框变大,轮子将会减速,并停在期望位置,此时再拉开小车与目标的距离,白框变小,轮子将会加速。
方向控制:假定期望数值是目标在中间的数值(我测出为190),那么小于190时,就该左转,大于时候要右转。
该怎样控制距离呢?是距离增大我就一股脑的往前冲,然后距离减小我就直接往后退?显然这样是不行的,假设目标距离为4000,这时候你靠近了,变成了3999,小车就会突然后退,而它一旦后退,3999就变化成大于4000的数字,这时又向前冲…这便产生了振荡。这并不是我们想要的现象,所以我们应该在目标刚靠近小车时,缓慢后退,靠近的越多,退的越快。靠近的时候如果手里的“目标物”突然停下,小车会从刚才的快速后退变成慢速后退,直到不断接近期望距离。这种思路便是PID了。PID这一部分的讲解,推荐各位去B站一个叫“天下行走”的UP那里学习,他讲的很详细,这里不在赘述。
该UP的个人空间链接
轮子速度控制部分代码
int Speed_PID_1(float true_speed)
{
hope_speed_1=Speed_PID();
err=hope_speed_1-true_speed;
integral += err;
if(integral<0)integral=0;
out= KP*err+
KI*integral+
KD * (err - err_last);
if(Speed_PID()==0||Distance==0)out=0;
if(out>=899)out=899;
return out;
}
小车位置控制部分代码
S_Distance=Hope_Distance-Distance; if(S_Distance<0) { GO_flag=0; S_err=Distance-Hope_Distance; S_integral += S_err/1000; if(S_integral<0)S_integral=0; if(S_integral>1000000)S_integral=1000000; S_out= S_KP*S_err+ S_KI*S_integral+ S_KD * (S_err - S_err_last); if(Distance==0)S_out=0; S_out=(S_out>0)?S_out:0; S_out=(S_out<99999)?S_out:99999; }
通信开始后,作为接收方,需要知道这一段数据在什么时候开始,以及在什么时候结束,那么发送方则需要在发送的时候有一个起始位和终止位,这便是一种“协议”,我这边以0x0A代表起始,0x0D代表终止,因为传输的只有数字,是数字0—9,其编码是0x30—0x39,所以不会和协议位冲突。
具体思路:
情况1:传输开始,假设我一开始就检测到了0x0A,则会将状态值RxState 置1,进入模式1,模式1通过循环,将接下来接收到的数字存放在数组中,再将数组的数值进行排列,获得原本的数据。在循环运算完毕后,将状态值置2,等待0x0D的出现。0x0D出现后,将状态值置0,等待0x0A出现,进入下一次循环。
情况2:假设传输开始,但是我没接收到0x0A,接收到了其他的东西,那么由于状态值依然是0,所以将会一直等待0x0A,直到它出现。
这里觉着讲的不是很清楚的话,可以去B站看这一期视频
部分代码如下(示例):
int j=0,i=0; int DATALONG=10; //修改长度 int DATA_NUM=0; int Distance; //距离 int Orientation; //方位 uint8_t Serial_TxPacket[4]; //FF 01 02 03 04 FE uint8_t Serial_RxPacket[10]; //修改长度 uint8_t Serial_RxFlag; void USART1_IRQHandler(void)//串口中断服务函数,在这里面获得信息并进行解析 { static uint8_t RxState = 0; static uint8_t pRxPacket = 0; if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { uint8_t RxData = USART_ReceiveData(USART1); if (RxState == 0) { if (RxData == 0x0A) { RxState = 1; pRxPacket = 0; } } else if (RxState == 1) { Serial_RxPacket[pRxPacket] = (RxData/16)*10+RxData%16-30;//十六进制转十进制 pRxPacket ++; if (pRxPacket >= DATALONG) { for(j=0;j<=DATALONG-1;j++) { //将信息包整理成一串数字 DATA_NUM=DATA_NUM*10+Serial_RxPacket[j]; //信息包解析 Orientation=DATA_NUM%1000; Distance=(DATA_NUM-Orientation)/1000%1000000; // OLED_ShowNum(0, 0,Orientation , 10,16,1); OLED_ShowChinese(0,32,11,16,1);//距 OLED_ShowChinese(16,32,15,16,1);//: OLED_ShowNum(24,32,Distance, 5,16,1); OLED_ShowChinese(70,32,14,16,1);//位 OLED_ShowChinese(86,32,15,16,1);//: OLED_ShowNum(94,32,Orientation, 3,16,1); OLED_ShowNum(0,48,DATA_NUM, 10,16,1); } RxState = 2; } } else if (RxState == 2) { if (RxData == 0x0D) { DATA_NUM=0; RxState = 0; Serial_RxFlag = 1; } } USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }
#一般写代码,我会用中景园电子的I2C例程作为底层代码进行搭建,里面含有数字、字符串和中文等的屏幕显示,以及有串口的初始化代码,这些比较基础的就不需要自己再手撕浪费时间了。
#同时,在编写的时候,写一步验证一步,确保这串代码能用之后,再去写下一串,不要一连写好几个C文件插进去,然后出了一些问题,都不知道出现在哪,原地歇逼。
#硬件电路上,一定要小心谨慎,插对没事,插错直接“嘎嘣脆,芯片味”。
#还有就是,有时候出bug不一定是软件上的,比如我这次的k210通信传输,我为了确保传输没问题(是正确传输而不是乱传一串数字),我在前面加上了“10”作为开头标志,但有时候依然会出现传输一串以其他数字组合为开头的数据包,后来才发现,我把k210的5V/GND插在了单片机上,导致供电不足,k210不能正常工作,而并不是stm32这边的代码出了问题。
链接:https://pan.baidu.com/s/1OzLqJF2pth75rx61Fuxnzw
提取码:2233
一些硬件引脚连接和注意事项写在txt以及main.c的开头了
┏━━━━━━━━━━━┓
#### 加油!!####
┗━━━━━━━━━━━┛
(\ヽ
\\ Λ ## Λ
\(.@—@.)
> ⌒ヽ
/ へ\
/ / \\
レ ノ ヽ_つ
/ /
/ /|
( (ヽ
| |、\
| 丿 \ ⌒)
| | ) /
`ノ ) Lノ
(/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。