赞
踩
在完成业务裸机的过程中,会用到freertos的任务通知和队列这两个内容
回顾下我们一开始的项目需求:
现在来看这个需求,在上了前面的课程后,相信大家心里已经有了实现它的方法脉络了
我们对于mini开发板和阿里云服务器的MQTT设备建立通信,有两种方案来实现:单任务和多任务
在只使用一个任务的这个情况下,代码逻辑和裸机其实差不多了
而多任务方案,就是将控制的外设都各自分配一个任务来控制
这两个方案的优缺点是基于我们这次的项目需求而言的。比如按键,我们只需要判断是否按下即可,所以可以改进到和MQTT任务合成一个任务
但是如果我们面临一个需求:需要判断按键的长按、短按、短按次数、长按次数
那是不是将这样一个对按键复杂分析的业务,独立分配一个任务来执行是更好的方案。
所以我们可以看到,虽然用一个任务也可以完成我们这次项目的业务需求,但那是因为我们的业务需求实在太简单了
既然一个任务就可以实现业务逻辑,为何不用裸机模式而要用RTOS模式?
在常见的物联网系统中,通常都是对多传感器的数据进行传输处理,用FreeRTOS的多任务调度、多任务消息传递机制,才能更好的应对物联网系统中的复杂需求
我在视频中,创建多任务的时候,一开始随意分配了三个任务的栈大小,导致了堆栈溢出。
算一算我创建这三个任务且开启任务调度后,实际上FreeRTOS会占用多少的内存?
我们在之前应该提到过,创建任务
这个分配给任务的栈深度 stackdepth,单位是字,也就是4个字节
而且我们开启任务调度的时候,会创建一个空闲任务
分配给空闲任务的栈大小是我们在FreeRTOSConfig.h里面设置的configMINIMAL_STACK_SIZE
所以如果我那样创建任务分配栈空间的话,实际FreeRTOS会占用的内存就是(2048 + 128 + 512 + 128)*4 bytes
11264个bytes
但是我们在FreeRTOSConfig.h设置的给FreeRTOS使用的最大堆空间是 10240
明显超出了,而且在创建按键任务分配512 stack depth的时候就超出了
所以创建按键任务的时候就会失败
所以,我们在使用FreeRTOS的时候,一定要对自己的内存分配进行精打细算才行
其实也不能这么说
应该是在我们实际工作的开发中,都要对内存空间进行精打细算的分配
至少在单片机开发中是需要的
像跑Linux系统的那些设备,一般都会外接DDR等内存设备,内存贼大……
我们的内存通常都会被分成几个区:
对于这几个区的作用,非常喜欢在面试题中出现
freertos管理内存有5种算法
heap1现在基本很少用了
常用的是heap 4/5
我们在成功创建任务后,第一步去做的还是我们最熟悉的,点灯
点灯的控制是在MQTT的订阅消息处理回调函数中,通过判断topic和payload,发送通知给LED任务去控制LED亮灭的
使用的任务通知API是:
对于单任务控制LED和读取按键我没有多说,因为和裸机控制差不多,比较简单
我想给大家讲的是FreeRTOS下多任务消息、数据互通的机制以及我在调试过程中因为疏漏遇到的bug
发出任务通知的API,其参数的解释我给大家翻译成中文放到表里面了
我们认为控制LED,只需要让LED任务能够获取到一个具体确切的控制通知即可,不需要复杂的置位、递增等
而且,即便led任务漏掉一次通知,也无所谓,所以采用了覆盖写任务通知的那个动作
但是我们可以完善,我们可以在上层发出控制LED指令后,去读取LED的状态,看LED是否真的根据控制指令做出了对应的响应
这就需要给LED加一个能读取状态的底层驱动以及在上层平台层做对应封装了
而且也要考虑我们是否可以用【不过滤写任务通知 】来规避下这种情况
获取任务通知值的API:
相比于
总是能获取到一个任务通知值,我们更倾向于使用
函数来等待获取一个新的任务通知值
既可以让我们获取到一个更新后的任务通知值,又可以顺便让任务进入阻塞状态,在大多数场合下,效果更佳
我在写完MQTT通知LED任务控制LED时,遇到了一个问题:LED任务根本没有被调度执行
这是由于我们在创建任务的时候:
我在MQTT任务和按键任务中没有做任何让步动作,又我们在FreeRTOSConfig.h中使能了抢占调度算法:
就导致最高优先级任务MQTT任务在一直运行
低优先级的任务根本没有机会被调度
所以我在MQTT任务的while(1)中加了一个vTaskDelay
按键任务的while(1)中也加了个延时
这样这个bug就解决了
然后是去写按键的控制任务
按键我们的做法是将按键信息读取到之后写入到队列中,MQTT任务的while(1)中读取队列消息,然后publish给服务器
队列的使用有三个动作:创建队列、写队列和读队列
创建队列,队列的单位可以是一个结构体大小,也可以是常规数据类型大小
我们任务中要创建的队列大小就是按键事件结构体的大小
写队列有三个API供我们使用
当前版本的FreeRTOS已经不推荐使用了
它的效果和是一样的
这两个API的接口,功能差异就是绿色注释所说的了
我们在任务中的使用:
其实这个还可以优化下
没有读到我们才调用延时让步调度,如果读取到按键了就通过写队列的API进入延时让步
我们是从ringbuffer里面读取出来放到队列里面,通过队列传输到另外个任务
读取队列的API:
我们现在用的这些API都是在非硬件中断中调用的
如果是在硬件中断中调用FreeRTOS的API,有另外的API
如果支持在硬件中断服务函数中调用,那么这些API的后缀都会有FromISR
要注意区分使用
对于读取按键任务的队列,我一开是在MQTT任务中这样使用的:
但是调试程序发现,欸,hardfault了
还是任务优先级导致的
我们是在按键任务中创建了队列,使得队列xKeyQueue这个句柄不是NULL;
但是MQTT任务的优先级比按键任务要高
他会先执行到自己的while(1)那里,在没开始调度按键任务的时候就先去对一个为NULL的xKeyQueue队列进行了读
也就是去访问了一个空指针
那肯定就会导致一个hardfault了
所以我的处理方式是:
先来判断下队列是否创建好了,如果没有就让步,让按键任务去执行创建好队列了来
那LED通过任务通知控制,按键通过队列传递发布这样一个业务逻辑我们基本就实现好了
我就顺势去阿里云物联网平台新建了一个mobile设备
想要一次性搞定设备和设备之间的发布订阅
创建设备、连接设备、订阅主题这些已经没啥好说的了
需要关注下的就是阿里云的云流转规则
我们需要做的是mobile设备发送led控制指令给mini板子,mini板子发送按键信息给mobile设备
但是mqtt协议中,一个确切的clientID只能共一个设备使用
当有两个设备使用同一个cliendID进行连接时,先连接的那个设备会被后一个挤下线
所以如果我们妄想两个设备都使用一个clientID连接服务器,然后进行自我转发,那是行不通的
需要建立两个设备,得到两个clientID,然后设置一套topic转发规则:
我们需要将mobile的ledCmd这样一个topic转发给MiniBoard的getLedCmd这个topic
将MiniBoard的keyInfo topic转发给Mobile的getKeyInfo topic
当我们创建好一个规则之后,切记要去启动它,使其生效
在应用复杂情况下,创建的规则就更多了
弄好云流转规则后,尝试用MQTT X连接mobile设备,往MiniBoard发送控制LED的指令的时候,发现开发板会漏指令
录完视频调试后发现是读取网络数据那里有bug
读取网络接收数据实际调用的接口就是
我们在这里面调用的是
底层用了HAL库的延时
这就导致,我们从mobile发送指令到服务器,服务器再转发给开发板的时候可能会导致我们在
这个地方阻塞了某次接收
我们在HAL_Delay做超时的时候,freertos的systick也是在运行的,那么在
这里超时的时候,
这个timeout也会在计数的
当我们的
因为超时退出后或者占用太多时间的时候,也有可能已经计数递减到了0
退出
这个函数的时候,返回一个为0的接收长度值
导致MQTT这个库在后面的流程中不会将我们读取出来的
这个buffer放到外面在MQTT任务一开始初始化客户端的时候定义的那个readbuf中
那我们的MQTTYeild
就会在这次尝试读之后直接返回一个0
不管是在哪一步被阻塞返回一个0值,都是有可能会使得我们读不到一个完整的订阅消息的
所以我将底层读取网络数据的驱动改了下
我不在底层超时堵塞了
我直接返回实际读到的长度
如果实际读到的长度不等于预期想要读取到的长度,那就返回实际读到的长度
如果两者等于,我就返回预期想要读取的长度,将那个记录实际读到的长度清零,等待下一次开启读取
在那三个步骤过程中任意地方xTimeout已经计数成0了的话
就会随时中断读取消息,从而导致漏掉消息,进入不了消息处理回调函数中去处理消息了。
其实消息还是保存到最底层的ringbuffer里面的
只是因为在下层堵塞导致上面的软件定时器的超时计数xTimeout提前被计数到0了
导致MQTT没有继续读取,超时退出了
才导致的漏掉一次消息,没有得到处理
解决了这个bug后,我们本期的物联网智能家居就算是完成了
但是还有其它的小问题,比如,我们频繁复位然后去连接阿里云服务器的mqtt设备,是容易出问题的,需要我们在程序中去做异常处理
以上内容均来自百问网7天物联网智能家居训练营
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。