本贴是冲着9/10月DIY分享活动来的,正好有个DIY发了一部分,那么接下来就在这里一次性都发完吧。
之前发的帖子:
这是一个用手机蓝牙遥控的平衡小车,没有用到什么高深的技术和复杂的算法,也没有硬件电路的制作,主要是模块的操作与程序的编写。整个系统还有许多的BUG,毕竟不是做产品,只是做个玩具来玩玩,自己开心就好。
整个系统的硬件拆解图图下图所示:
小车的PCB弄得太差,大家就看看原理图就好了,而且还有很多BUG,PCB中没有任何元器件,只是将一些模块连起来而已,上面的拨码开关用来开关喇叭等外设,两个船型开关,一个是总的电源开关,一个是电机电源开关。
小车能够实现的功能请点击上面的连接查看之前的帖子,有视频和图片。
整个系统可大致分为4个部分,分别是:小车部分,上位机接收机,PC上位机,安卓手机控制端。4个部分的关系如下图所示:
其中以平衡小车为中心,能将信息通过无线发送给上位机接收端,上位机接收端再将数据通过串口的形式发送给PC上位机。同时上位机亦可以发送调参与控制指令给上位机接收端,上位机接收端再将信息发送给平衡小车。安卓APP也能通过蓝牙传输信息给CC2541模块,将信息通过串口的形式发送给平衡小车进行控制。
外设的选择 处理器,这个用NucleoSTM32F411核心板,这是因为这块板子有很多外设,例如传感器模块采用Nucleo IKS01A1,蓝牙模块X-Nucleo-IDB04A1,只是后来蓝牙模块坏了,最后采用CC2541蓝牙4.0模块替代了。
电机驱动用TB6612模块,因为直流减速电机的电流相对没有减速的电机来说电流较小,虽然BTN7971电机驱动电流大但是体积也大,TB6612能胜任当然选择它。
测距采用夏普2Y0A21红外测距模块,这里不采用通常用的超声波模块是因为超声波模块对细小物体比如铁丝是直接忽视的,这对于需要避障的小车来说是相当不利的,还有就是超声波模块触发信号必须在80ms以上,这个时间对于不断移动的小车来说太长了,而红外测距是模拟信号,理论是可以任意时刻读取,但是红外测距也有缺点,值与距离成非线性,需要将其线性化,再一个是测距范围在80cm以内,不过这个距离对于小车来说足够了。
无线通信采用NRF24L01+模块,因为仅此模块具有带数据的ACK功能,能十分方便的进行数据的双向传输。这是CC2500和CC1101不能比的。
语音模块采用SYN6658语音模块,这款芯片是最近发布的,性能较之前的SYN6288提高不少,语音也更加流畅自然,支持多种声音,而蓝牙玩具小车的播报声音自然是童声较好,因此选择SYN6658。
电池选择狮子11.1V 2200mAh电池,电压选择11.1V是因为小车电机的额定电压为12V,容量选择2200hAm是因为在该电压下此容量最小、也是最轻的,这对于小车的平衡控制十分有利。这里有个小插曲,其实最开始买的TCB的电池后来坏了,就买了狮子的电池。
电源模块采用LM2596,这款电源芯片能够满足将11.1V的电压转成5V的要求,且带负载能力大,该芯片也最常使用,稳定性好。
支架这里采用诗凯支架,光电编码器为13440线每轮转,AB向输出,电机为3530,性能相当不错。
手机APP
之前的帖子已经讲了,如何实现蓝牙4.0的收发,那么接下来就是通信协议的制定与界面的编辑了,下图是手机APP的截图:
在文章的最后有代码下载,我自己编写的相当相当的乱,请见谅,添加了很多无用的东西,其中有借鉴了其他的人的代码,请先看懂我之前关于蓝牙BLE例程的分析。
界面的上方显示连接的蓝牙模块的名字、地址以及连接状态,同时还能显示RSSI,当RSSI显示并跳动时说明APP正在发送数据,一旦RSSI停止说明发送不成功,或者没有发送。通过点击左上角的后退三角形或者点击最右边的连接按钮就能够实现重新连接。实测在7米以内能够实现不丢包的流畅控制。安卓APP上面有两个开关按钮,用来开启手动/开启自动,开启预警/关闭预警。D1~3为设置的3个舞姿,“ID”为自我介绍,“Humi”为湿度播报,“Pres”为压强播报,“Temp”为温度播报,“Dist”为前方障碍物距离播报,“Yes”为“是”的播报,同时点头,“No”为“没有”的播报同时摇头,“Then”为“然后呢”的播报,“OK”为“好的”播报,“Shake”为摇头,“Read”为念诗。“STOP”为停止的控制,“CENTER”用来设置朝向,“LEFT”为向左转90度,“RIGHT”为向右转90度,四个滑动条分别控制方向、速度、舵机1和舵机2。
APP运行流程:运行搜索设备,如果搜索到“MYFUN”名称的设备则自动连接上,没有搜索到指定名字的设备也可以通过点击设备名称进入操作界面。进入操作界面后发送一个连接信号给小车,小车收到该信号后会响一下,如果小车成功的收到的信息,则进入成功发送的回调函数中,在回调函数中继续发送相关控制参数,每次发送的数据包为7个字节,一个字节的帧头,两个个字节的控制字,,4个字节的滑动条量,滑动条设置为0到100因此一个字节就可以搞定。实测控制周期大约为100ms一次,对于这种运行速度不高的场合已足够。
PC上位机端 上位机采用VC6.0编写,PC上位机主要是用来进行参数调整、虚拟示波器显示波形、3D姿态显示。下图是上位机截图。采用MSComm串口控件进行数据的发送与接收。能够进行端口扫描,并连接串口,实现数据发送,错误显示,实现了4个整形数字与4个浮点数的调整,并且能够读取系统的原始数据,显示发送状态,且能够显示信号强弱,同时能够进行示波器显示,调节显示间隔。由于采用的是NRF24L01+带数据的ACK模式进行数据传输,所以上位机不发送数据的时候,小车并不会发送回来,所以上位机能够掌握显示数据的间隔。示波器采用的IPlot控件,能够实现4个数据波形显示,且能够让任意一组数据显示、实现轴的缩放、在顶端显示数值。3D姿态显示采用的是OpenGL库,画出小车模型,设置为半透明,能实时清晰的显示小车的姿态。
上位机的编写其中很大一部分在我的日志中有讲解:
看完上面的这些,就基本上知道了上位机的制作了,上面整个过程没有详细的涉及原理,都是一步一步的图文说明,相信和我一样的程序小白能很快的编写出来,其中OpenGL的编写没有接着之前的来,不过也大体差不多啦,多了点数据传输,窗口的缩放等,然后主界面有有所改变,添加了原始数据的读取功能,数据的修改等功能,这里使其很好理解,自己设定控制命令字,然后接收方通过判断接收的控制字来执行相关操作,上位机每次接收4个整形数用于显示波形,接收4个浮点数显示姿态,若是点击“读取原始数据”则将相关数据显示在数据修改参数中,点击“修改参数”则将参数发送给小车。上位机默认每隔50ms发送一个控制字给小车,小车收到数据后将数据以ACK的形式发送给上位机,很多人对这种方式不理解,接下来在上位机接收端中我将详细介绍这种方式。其他的请见源码。
上位机接收端
上位机接收端采用CCS5.1编写,上位机接收端作用相当于一个中继器,它并不改变内容,作用是将NRF24L01+的数据转成串口数据,同时也能将串口数据转成NRF24L01+数据,芯片采用的是MSP430G2553,这款芯片拥有UART能够和电脑通信,有SPI能够和NRF24L01+通信,性能也够用。这个部分的物品请看硬件拆解图的右上角部分,用的是LaunchPad开发板,USB转串口模块和一个带有功率放大和天线的NRF24L01+模块, NRF24L01+是用的带数据的ACK模式,之前我写过一篇相关的日志:NRF24L01+设置为带数据的ACK功能 这里我还是要详细介绍下这个模式,进行无线通信时,我们如何知道发出的信息对方已经收到呢?很常见的思维就是接收方收到后收到再告知发送方,芯片厂商当然也知道,因此ACK(确认字符)模式就出现了,它的工作模式是这样子的:当设为ACK模式的发送方A发送时,其INT引脚一直为高电平(也可以通过读取状态标志位),当接收方B收到数据同时,接收方B会自动发送一个ACK信号给发送方A,这个过程不需要人为操作,当发送发A收到这个ACK信号的时候其INT引脚就变为低电平了,清除标志位就又能够使INT引脚变高电平,又可以接着发送了。从整个过程能够看出ACK的模式是相当方便的,我见过不少的通信程序中,设置为ACK模式却在接收方B收到数据后再次切换为发送模式给发送方A发送一个自定义的应答信号,这个实在是画蛇添足,做了无用功。回到这个带数据的ACK模式上面来,芯片厂商确实是很聪明,既然需要发送一个应答信号,那么何不使这个应答信号带一些数据过去呢,带数据的ACK模式就这样诞生了,它的工作模式是这样的:接收方B先将需要发送的数据放到指定寄存器中,等待发送方A发送数据,然后接收到发送方A发过来的数据,接收方B自动将指定寄存器中的数据连同ACK信号一起发送到发送方A,发送方A的INT引脚变低电平然后就读取接收寄存器中的数据,收到的数据就是接收方B之前放到指定寄存器中的数据。这个模式最大的好处就是不用切换发送接收模式就能实现数据的交互,因为NRF24L01+是半双工的,但是同时也会造成数据的延迟,因为发送方A收到的ACK数据是之前接收方B放进去的。
上位机接收端的实现过程:在MSP430主函数中有个while(1)循环,不断检测INT引脚电平和串口标志位,如果串口标志位置位,那么运行发送函数,将串口中的数据发送出去,如果INT引脚电平变低,那么执行NRF24L01+的相关操作。详细的程序内容请参考源代码吧。
平衡小车
平衡小车程序采用IAR7.3编写,芯片是STM32F411,小车需要处理的任务包括:小车的平衡、转向转速、前进后退以及速度、跳舞、语音播报、舵机控制、红外测距、传感器参数的读取、光电编码器的读取、NRF24L01无线收发、蓝牙CC2541接收,碰撞检测,倒下检测等。这些任务分布在主循环和定时中断中。下图是平衡小车软件流程图,图左边为主函数的流程图,主函数中主要运行蓝牙数据接收函数,以及语音播报跳舞等任务。右边为2.5ms定时器中运行的任务。此处只将主要任务列出,其中直立控制、速度控制、自动模式下转向控制、预警任务、碰撞检测、 NRF34L01+任务等。直立控制的原理是先采用Mahony姿态解算得出姿态欧拉角,然后用陀螺仪的值与Pitch值进行相关运算得出电机PWM输出值。图中的直立控制中同时也包含了速度控制、方向控制。
上面的流程图还只是个大概,整个软件的编写没有上操作系统,因此任务运行先后比较浅显易见,但是同时又造成任务比较分散。其实整个小车上的程序编写我最想分享的是相关芯片功能的实现,例如:PWM占空比的改变,光电编码器的读取,串口DMA等功能实现,因为这些都是这款芯片的例程中没有的。
下面我就上面的重要任务简要的讲下:
姿态解算,整个程序的编写中最耗时间的是姿态解算算法的选取,我试过有卡尔曼滤波法,互补滤波法, Mahony姿态解算法, Madgwick姿态解算法,ST的iNEMO运动引擎,每一种都实验并调整参数,最后用的是Mahony姿态解算法,这个算法我没有融合磁力计,因此造成Yaw角度的不准,不过对于小车的控制来说是没有影响的。其他的算法在源码中都有,被我注释掉了,以后如果有时间我会来做一个各种姿态解算算法的对比。所谓的姿态解算就是利用陀螺仪加速度计的值进行计算最后得出姿态角度,可以是四元素表示也可以是欧拉角表示。在飞控上通常是用的EKF(扩展卡尔曼滤波),但是这个相当的耗时间,而且文件庞大不好理解,要学过捷联惯导才能理解,ST的iNEMO运动引擎也是用的EKF算法,耗时多,可调参数多。Mahony姿态解算法和Madgwick姿态解算法非常相似,先解算出四元素,然后再将四元素转换为欧拉角,两个算法的调节参数少,但是我一直用的是6轴融合,9轴融合一直有问题。在这里参数的调节,前面讲到的PC上位机就派上大用场了,用虚拟示波器能够查看融合的前后的角度,并且用修改参数功能实时无线调参,直道调节到满意为止,大大提高了开发速度。
小车的控制全都是PID控制,直立控制周期为2.5ms,采用PD控制,用姿态解算出来的角度减去理想姿态角度乘以P,然后陀螺仪的值乘以D,注意:这里陀螺仪与角度使用的不是同一个轴。速度控制周期为50ms,采用PI控制,也就是实际速度与理想速度的差乘以P,然后速度的积分乘以I,并对积分值限幅。小车速度控制和直立控制是直接线性融合的,之所以速度控制周期为直立的控制的20倍,那是因为速度控制实际上是对直立控制的一种破坏,例如:正在向前走的小车若要使其向后走,那么就要使小车加速向前运行,这样会使得小车向后倾斜,那么小车就会向后运行来保持平衡,这样就达到了向后运行的目的。车方向控制就比较简单了,在原来速度和直立控制线性叠加后的结果上增减一个相同的值分别给两个电机,我想这个很容易理解。这里需要调节的参数一共有7个:理想直立角度,直立控制P,直立控制D,速度控制P,速度控制I,速度设定值,转向设定值。这7个参数相互影响,这里同样采用PC上位机进行无线调参,调参顺序为:先直立小车,通过3D姿态显示查看平衡点的直立角度值,修改其数值,然后调节直立控制的P值,直到其能够等幅振荡,然后慢慢的修改D值,最后实现能够匀速的向一个方向行走,整个过程最好将小车的姿态信息放到示波器上,边看示波器边调节参数。然后速度设定值为0,调节速度控制P,使其能够等幅在原地振荡,然后缓慢的调节I,使其能够在原题不动,且能够抵抗一定的干扰,调节速度设定值,感受不同的值下小车的速度大小,如果发现小车走走停停,那么请重新调节速度控制的PI值,最后调节转向设定值,修改这个值能实现小车在原地转圈,感受不同的值下小车转圈的速度,并记下小车极限转速,此时最好是在示波器上显示PWM波形的占空比,切记不要达到100%的占空比。
蓝牙串口数据的读取,这个也是我耗时较多的一部分,大家也许觉得不可思议,串口是很简单的东西,单片机入门就学会用串口,可是大家有考虑过在多任务下用串口接收数据如何处理吗?串口有这么几种读取方式,一个是等待方式,就是一直等待并查询是否有数据的到来,但是这样我除了接收串口数据我啥也不用干了,而且不能保证不丢包,这个方式显然是不行的,还有就是中断方式,每接到固定数目的数据就进入中断,这个看似挺好,但是如果设定为每接收一个就进入一次中断那么会严重打乱我的定时中断的运行,使得程序流程变得复杂,同时一个一个的接收也不利于数据的解码。如果设定为收到固定数据进入中断,那么就需要发送方每次都是发送固定字节数目的包过来,而且一旦中间有错误,那么收到的包就会变成一个前一个包的尾部加后一个包的包头,这样就永远的收不到正确的包了,而且我买的这个CC2541模块每次连接成功它都会发送出来一大堆的信息,因此这个方式也不可用,那么接下来就只有唯一的一个DMA传输方式了,这个方式最大的好处就是不会丢包,设定好储存地址,它会自动自增的将收到的数据放到内存中。那么这样一大波问题又来了,首先收到数据的内存要设计为多大,什么时候又开始从头开始存储,如何从这么多的数据中找出最近收到的数据,如何确定收到的数据是否就是一个数据包,如果发生错误怎么办?等等的问题,不过问题总是能够解决的,我的处理方式是这样的:首先设置好串口的DMA传输,然后将接收到的数据放到一个512字节的大数组里,在main函数的while(1)循环中编写一个函数,此函数不断的判断从上一次正确的收到数据后至现在收到的数据,如果大于等于8就将这8个数据放到另一个数组里,接下来判断帧头是否正确,如果正确那就说明正确收到了包。此程序具有自动纠错功能,本小车用的CC2541在连接成功后会发送一些信息,代码会过滤掉这些信息,然后接收到正确的数据包。
舵机的控制,舵机的控制确实是非常的简单,不同的PWM占空比就对应着不同的转动角度,但是本小车却是实现了小车车身转动,舵机头一直望着前方,还有点头、摇头、大笑、念诗时候的舵机动作,这些都是运用不同的策略实现的,首先是舵机头一直望着前方,这个是与小车的转动角度Yaw值关联的,开始转动时记下当前Yaw值,然后小车左转90度,舵机根据小车左转的度数向右旋转相应的度数,这样就实现了小车头一直望着前方。这其中需要处理好从-180度到+180的越界问题。然后是舵机的点头、摇头、大笑这是利用数组编码,数组中将舵机需要转动的度数变成数字依次放到数组中,然后从第一个开始向后执行,直道遇到结束符,这样就能实现任何舵机的动作了。念诗的摇头是一个简单的同时运动,利用for循环使两个舵机同时从最大到最小然后又从最小到最大,第一次点击时开始摇头,再次点击停止要摇头。然后舵机控制还有一个重要的部分那就是自动模式下的摇头,云台上面安装着红外测距模块,需要在舵机不同的角度时将红外测距的值放到数组中保存下来,这就需要舵机控制的准确性,也就是不能让舵机还没有到达预定的角度,就开始下一次转动了。由于舵机是没有反馈的,因此摇头十分的缓慢,对应的小车的速度也不能太快。同时舵机还能通过手机遥控,这就增加需多标志位,因为在自动模式,或其他需要舵机运动的情况下是需要屏蔽手机对舵机的控制的。
自动行走模式,这个模式下小车会向前直走,遇到障碍物会自己向左或是向右壁障,卡住会自己后退。这一切都需要准确的知道小车前方环境。而小车只有一个红外距离传感器,其实板子上面还留有超声波模块的插口,但是超声波模块有个致命的缺点那就是不能感知细小的物体,本小车可以躲避细小的凳子腿,超声波是无法做到的,而且超声波模块需要间隔至少80ms才能第二次度数,而且读数还需要进行脉宽检测,脉宽检测就需要TIM模块,本小车已经将芯片上的TIM模块用完了,用中断检测脉宽方法也可以,源代码中同时有TIM脉宽检测与中断脉宽检测的代码,最后我将超声波的方案舍弃,采用红外模块,红外模块采用模拟数值输出,理论上可以无间隔读取数据,而且能够感知十分细小的物体,原计划是利用三个红外测距,分别安装在小车的左,中,右来感知前面的物体,同样被我舍弃,最后采取现在摇头来构建前方距离地图的方式。红外测距模块输出值并不是与距离成线性的,距离远数值小,距离近数值大,舵机从左至右扫描的数据放到一个数组,从右至左扫描的数据放到另一个数组中,每次扫描一次完成后开始分析数据并控制小车转向,分析方式为:如果是从左向右扫描,那么从扫描到的数组中间靠右一点的地方向左判断数组中的数值,如果数值有大于阈值的那么就判定左边有障碍物,则向右转,另一边同样的原理。
跳舞任务,这个其实就是小车4个自由度与舵机2个自由度的各种组合,其程序的编写方式也是非常有特点的,是这样实现的,一个跳舞的任务是由若干个小的任务组成的,例如其中一个小车先向左转90度然后向右转180度最后向左转90度,整个过程一直保持头部望着一个方向,整个过程可以分为四个部分,1,向左转90度,2,向右转180度,3向左转90度,4,结束。设置两个标志位A和B,标志位A是动作的步骤,每完成一个任务部分标志位就减一,标志位B是转动参数,数值代表以基准的为零点转动的角度,实际程序中B是用两个参数表示的,这里为了简化用一个表示,(安卓APP中“CENTER”按钮可以设置当前朝向为基准点)。当启动这个任务时,标志位A设为4,标志位B设为0,然后进入判断程序:
If(A==4&&B==0){B=90;A--;}
else If(A==3&&B==0){B=-180;A--;}
else If(A==2&&B==0){B=90;A--;}
else If(A==1&&B==0){A--;}
然后根据B的值执行转动控制任务,到达预定的转动角度时,B的值复位0,接着又能运行上面的程序了。
碰撞检测原理是当小车速度连续小于一定的值一定的时间后就判定为碰撞到物体了,那么就使小车后退1S,其中涉及到的标志位就见上面的流程图了。
倒下自动停止任务原理是当小车倾斜的角度大于一个阈值的时候就给电机的输出一直为零。
预警模式,其实是不断的用红外测距模块检测正前方的障碍物距离,如果发现距离很近就停止向前的行走,并且播报“危险”,如果多次连续检测距离前方障碍物很远那么就解除停止锁定,并播报“安全”
中断运行占有率任务,这个任务在这里并没有什么实际的作用,但是却是一个能够使程序健壮的重要任务,中断中的程序运行时间是不定的,但是定时中断却是设定的2.5ms,如果中断函数中运行的时间大于2.5ms那样程序就会出问题了。实际上中断定时我设为2.5ms,也是根据这个任务来的,因为直立的控制周期越短越好,但是同时又要保证在中断中运行完所有的任务,经过实测,中断中的任务平均时间为2ms,因此这才定时2.5ms。其原理是这样的:首先在主函数中开启一个定期器,定时器的周期要大于定时中断的周期,然后在定时器中断函数进入的开始读取一下定期器计数,将数值保存到变量A中,并将A值减去上一次进入到定时函数时的A值,其结果保存到B中,这里需要处理好数字溢出问题,然后在定时器最后在读取一下定时器计数,将数值保存到变量C中,那么(C-A)/B就是中断的占用率。
语音的播报任务,语音播报是设置的比较巧妙的,语音播报采用的是SYN6658芯片,这个芯片的特点是输入文本加上需要读的字符数就能读出来,而且正在读的时候输入文本无效。基于这样的特点,读整段的文字还好,如果有数字就比较麻烦了,因为数字是会改变的,而且不是字符,因此我编写了两个朗读函数,一个是直接读文本的,另一个是前后都是文本中间是数字,编写了一个数字转字符的函数,并且能够知道数字转化为字符后的长度,这样就解决了语音播报能够播报变化的数字的问题,例如“现在的温度是37.5度”。则可以分解成“现在的温度是”然后是数字的37.5转化为字符,最后是“度”,并计算出三段的字符总数然后将整句话一起输入到语音芯片中。
全部源代码下载:
上位机源代码.zip
(4.52 MB)
(下载次数: 1799, 2015-10-3 23:04 上传)
原理图与PCB.zip
(618.06 KB)
(下载次数: 149, 2015-10-4 12:11 上传)
本帖最后由 lb8820265 于 2015-10-4 20:17 编辑