草履车
草履虫只能进行简单且本能的生命活动,没有智慧。草履车也是如此,它只能进行极其简单的机械运动。作为个人第一个项目,用于练手。
开源地址:https://github.com/wearexc/startup/tree/main/草履车
前言
这车按规划,应该是上学期完成。当初的目标是个遥控小车,看看能不能装上能装的外设。
白驹过隙。
还是在这个学期,差不多期中的时候才正式开始。好在平时闲的时候进行规划,做什么样的车,心中有个大致的规划。有实验室位置的时候,则可以快马加鞭。实验室效率太高了,一天顶我一周。
虽然车不是啥稀奇的玩意,网上代码的项目多如牛毛,但我还是决定自己重头写一个。自己布置线路,分配资源,写代码。
开始挺顺利,组装车,调试。遥控车辆,也不费啥劲。
然后,然后就开始遇到了奇奇怪怪的问题。有次把程序跑飞了,即使是看寄存器,也定位不到错误代码,然后又莫名好了。可能因为我今天左脚迈进电梯吧。
然后就开始碰壁,前期想当然的规划,实行后才发现有很多bug以及不切实际。很多时候需要推倒重来,这部分主要是数据处理。先前的数据格式用着用着发现有更好的,优化一下吧。然后就大改啊大改。如果只是蓝牙遥控小车或者避障,实在乏味。所以按照自己的设想,有至少6种模式可以进行切换。每种模式尽量设计的全面些,最后整理好代码,记录好项目,归档,最后彻底摆烂。
这些要在一个月之内完成,必须。
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆分割线☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
11月23日:
总算完成了个大概,完成后发现和预想差的还是蛮多的。项目越到尾声,反而越糟心。它并没有给我带来什么成就感,很多时候我都自我怀疑,我怎么花了这么久的时间,做了一坨屎一样的玩意。不过作为练手来说,还算彳亍吧。给代码上了注释,传了github,拍照并写博客记录一下,就该让它吃灰了。后续也不会再整了,最多调整一下参数。
成品
太乱而且太随意了。






概述
草履车使用STM32,NRF24L01蓝牙模块,电机驱动,舵机。电机驱动电源由两块18650串联,芯片则是用STlink配合PDD9.9包邮小充电宝供电。
主机端也是使用了STM32,还有摇杆,四个按钮以及一块OLED屏。
原计划用W25Q64丢到小车,用它来记录操作,后续改用芯片内部闪存。
运行情况参考状态机:

模式
数据结构
这个点踩坑最多,数据原来计划是一个8位数。前四位就是摇杆状态等等,后四位是时间。即000(状态)0(标志位) 0000(时间)。读取数据的标志位,如果置1了,则切换到下一个状态。后来发现了丢包问题,想要每次传输的数据对方都成功接收,是不可能的。标志位不可取。
然后为000(摇杆)000(标志位)00(速度),时间存到另外的数组。三个数,三个标志位,表示不同的模式。后来发现不够用,干脆改为真值表吧。
摇杆真值表
状态 | 输入 |
---|---|
000 | 无法读取摇杆,error |
001 | y+ |
110 | y- |
010 | x+,y+ |
101 | x-,y- |
011 | x+,y- |
100 | x-,y+ |
111 | 摇杆其它状态 |
模式真值表
状态 | 输入 |
---|---|
000 | 实时控制 |
001 | 观察模式 |
010 | 跟随模式 |
011 | 避障模式 |
100 | 睡眠模式 |
101 | 启动记录 |
110 | 启动回溯 |
111 | 记录操作 |
速度真值表
状态 | 输入 |
---|---|
00 | 无速度 |
01 | 1档 |
10 | 2档 |
11 | 3档 |
速度的改变是依靠按下的摇杆。但它的中断一言难尽。我原计划是 0 , 40 , 70 , 100。每按一次,切换一次档位,但摇杆的中断会迅速触发两次,在中断的代码上我无法规避。除非在中断里进行计数,根据计数来切换档位。感觉太麻烦了,所以实际情况是舍弃了0。以40为基础状态,每次按+15(因为它会触发两次,则是+30)。
最后让我纠结很久的是记录操作的时候,如何记录操作和时间,记录和存储的格式是怎样的。这个点卡了我很久,也让我多次推倒重来。
初始化
这个原计划是小车对蓝牙模块和W25Q64进行写入读取,确保设备正常。然后发送数据给主机,主机收到后进行判断,数据没问题,则显示初始化成功,并向小车回传数据。
废弃原因:太麻烦了(懒得写两蓝牙的收发切换互传),并且的确没啥设备需要检测。
实时遥控
读取摇杆状态并通过蓝牙发送,小车只需要傻傻接收数据,丢给驱动函数执行就行了。值得一提的是我给它设置了警告位,当它有一段时间没有收到蓝牙信号,则自动停下,并发出警告。只有复位后才能继续运行。算是防跑丢吧。
观察模式
舵机配合超声波,对各方向进行探测,数据传给主机。每n次探测,小车将切换为接收模式,看看有没有收到数据,如果收到,表示主机退出探测,小车也应该退出。退出后将超声波传感器复位。
其实一开始打算整个雷达图,然后实在实在是懒得画懒得调试。最后主机的界面感觉还不错。
跟随模式
根据超声波传感器的数据,加个if判断。距离小了就像前,距离大了就退后。值得注意的是需要设置一个阈值,否则会因为误差的原因,即使距离达到要求,小车也会鬼畜前后的运动。(目前我设的阈值好像低了。)
避障模式
赶鸭子上架,参数还需调试,有点摆烂,不想调了。而且因为超声波传感器的位置比较高,它的避障效果并不好,除非是墙。这没啥办法,传感器位置低了,没法进行观察模式(因为会扫到两侧轮子)。
睡眠模式
按照我的想法,它应该是关闭所有时钟,内核等等,只有蓝牙模块工作,收到信号后进行唤醒。
后来了解到STM32的低功耗模式,最后发现,只有睡眠合适比较合适。但睡前还是需要手动关闭时钟。其它模式“睡的太死”了。这个模式没有深究。仅是调用了一个睡眠函数。
记录操作
这玩意我整了老久了。
设想情境:
启动该模式,将记录用户操作,数据存储置W25Q64。记录完毕后,将回溯用户操作,比如前进的小车,回溯后将倒行。
数据格式为8位,高3位存储摇杆的状态,低5位存储记录时间。
方案1:
设想车辆控制端同步时序,在同一个200ms时间片内,控制发送命令数据,车辆存储,车辆状态200ms更新一次。因为无法同步时间片,而且担心传输丢包,卒。
方案2:
发送端同时发送命令和时间,车辆只需要处理,记录即可,命令更新时,车辆状态也随之更新。因为无法同时处理车辆状态,接收数据而卒。个人想法是边接收,边处理并放置缓存区。但因为某种原因,无法完成。本来不是啥复杂的玩意,但是遇到了奇怪的Bug,定义的全局变量会因为蓝牙模块的一个操作函数改变。摸不着头脑。
方案3:
发送端发送已处理的数据,车辆仅做傻瓜式接收,这总行了吧。原先不这样处理,是担心操作端的中断会打乱时序。并且操作端200ms才发送一次数据,这段时间足够车辆接收并处理数据了。
好吧好吧,我还是太理想了。
我现在两难了。
车辆接收数据,不能使用DMA。因为DMA太快了。会导致很多重复的数据被一直搬运,并且DMA只负责搬运,后续的处理会很麻烦。那咱退一步,每次接收数据后触发一次搬运不就行了?那不好意思,硬件触发DMA搬运,虽然每次可以很迅速的搬运,但是搬运过去的地址需要增加。而DMA似乎只能在初始化的时候配置次数,由此进行连续搬运自增地址。
这导致了如果想使用这种方法,则需要不断用软件更新DMA地址。这会导致数据无法迅速搬运,进而丢包。
那么就是有DMA却无法用吗?。。。
丢包率达到10%~20%,太夸张了。。。。。。
方案4:
下调发送端发送时间,改为500ms发送一次,测试丢包率。不行啊,即使调到1s发送一次,丢包仍不可避免。应该思考如何处理数据并进行优化了。似乎又要推倒重来。
当时:
大概是遇到了踩内存,全局变量只要涉及到蓝牙,就会被改。所以我的处理方法是定义一个局部变量,并使用while循环来事变量计次。记录完成后将使用DMA转运到另一个数组。作为缓存器数组还算够用,倒也用不到W25Q64,先实现功能,再完善吧。
目前是小车接收数据存至数组,操作记录完毕后使用DMA转运到另一个数组存起来。数据格式为原生数据,回溯时将数据的摇杆位取反。并使用定时器,每200ms将读取一次数据进行更新。
回溯勉强算是实现了。遇到踩内存BUG太难受了,所以代码很烂,用代码弥补BUG。除非能解决BUG,不然一优化就无法运行。
最后:
主机每200ms发送一次数据,小车接收后存放至临时数据,结束记录后,将用DMA转运至FLASH数组,并记录数据个数,放到FLASH数组首位。FLASH数组将数据记录到内部闪存。每次上电初始化,将读取FLASH至FLASH数组。播放和回溯操作将根据FLASH数组的数据进行。
启动记录
通过TIM2,每200ms读取一次FLASH数组数据,丢到驱动函数。然后自增,下一个200ms就能读取数组的下一个数据。至于读取次数,参考FLASH数组第一个数据,它记录了数据个数。
回溯模式
也是通过TIM2,操作过程和启动记录差不多。只是执行顺序是从后往前。
总结
大体上还是完成了,花了大致两周。有空就钻实验室捣鼓,花的时间还是蛮多的。增加了不少调试经验,吸取的教训就是数据格式这类了。项目没规划好,导致后续需要返工,为了更合理的代码。
草履车就像这鸟,当初出现踩内存的BUG,但还是硬着头皮写了下去。倒也能跑。后续发现是蓝牙接受数据的代码,数组越界了。后续修了BUG,大改了很多地方,一度觉得代码烂就烂吧,BUG能跑起来就行了,非常想摆烂。只是后续进度比我想象的快了很多。在写出了整体框架,规划并确定好了资源。后续也只需要随便往里面填些简单的代码。随便测试一下,然后觉得也就这样吧。感觉自己花了这么长时间,就整了个这么个没啥用的玩意。多少有点失望。
作为第一个练手的项目,选的还是比较经典的小车,尽管网上有很多实例可以参考,但还是自己一点点规划资源,写了出来。整个过程,第一个比较满意的就是数据规划了。一个8位,规划好了摇杆,模式和速度,个人觉得还是蛮合理。摇杆的真值表,相反的状况还特意取反,就是为了回溯做准备。第二个满意的就是观察模式。主机的界面还行。
尽管能记录操作并且执行,但我总觉得会有些BUG。我并没有对时间,数组这类进行严格的规划,当记录的操作过久或许会出现问题。自己也没有给它规划好上限。
它还有很多可以优化的地方,但就先这样吧。