我是怎样用CosyOS构建系统的
本帖最后由 leonling 于 2024-10-8 00:53 编辑先介绍一下背景,随着项目对实时响应的要求越来越高,原有的代码已经不能支持,本来计划在前后台框架下升级,但很快发现:几颗主芯片是用MCU自带的SPI和I2C控制的,正常运行时不停的对它们轮询,如果为了实时响应,在中断中用SPI和I2C改变主芯片状态,就要保证中断时轮询已经结束且释放了SPI和I2C的资源。此时我有两个选择:1. 仍用前后台系统,轮询时关闭状态变化发现中断(一个外部中断),轮询结束再打开,只要轮询时间足够短,就能保证不丢状态变化发现中断。如果发现状态变化了还要延后一个debounce时间再处理以防止状态跳变,可以再加一个定时器,在定时器中断中用SPI和I2C改变主芯片状态,轮询时关闭的也变成定时器中断。2. 采用RTOS系统,中断中激活用于改变状态的高优先级任务,通知轮询任务结束并释放资源。
我的选择是RTOS,原因有:1. 更通用,RTOS采用的设计理念是千锤百炼过的,适用于各种场景,不仅能解决我眼前的问题,也能解决我尚未看到的未来的问题。2. 不必重复造轮子,一个好的RTOS已经实现和验证了很多东西,是可以简化用户程序设计、减少调试工作的。3. 将来会需要对事件进行及时响应(比如通信接收错误)。4. RTOS能实现精确的超时重发机制(归根到底也是为了提高效率)。
所以,现在就需要找一个适用于51的抢占式的RTOS系统。我虽裸机编程多年,但未用过RTOS,于是上网查各种资料。正好在STC论坛中看到了CosyOS,感觉设计理念很好,便试用了一下,当时还是CosyOS-I;后来,我是在CosyOS-II-STC8H-TEST-V2.0.1-20240318的基础上,release了自己的产品,我认为先跑通测试代码,然后从测试代码入手开始加入自己的代码,这样上手最快。
一路走来,从接触RTOS到发布产品,1年多时间过去了。觉得有必要开一帖,给自己留个Memo,给别人参考,也看看大神们有什么意见建议。
接下来将枚举我在应用CosyOS时趟过的那些印象深刻的坎,描述时将尽量采用系统相关的模型,不涉及具体业务。
第1个坎,激活报文解析任务的方式。
本来计划用iResumeTask,因为CosyOS作者说过Suspend Resume任务的效率最高,但是和CosyOS作者讨论的结论是这样有潜在问题,会有解析任务先恢复再挂起,导致漏掉一次解析的可能性。感兴趣的请参考:
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=1807&page=11#pid22322
最终是采用二值信号量的方式:
void uart1ISR() interrupt 4 using 2
{
if (RI) {
UARTrxEnqueue(uartRxBuf, uartRxWrIdx, SBUF);
iGiveBin(binUart1);
RI = 0;
}
}
uCreateTask(tUart1, PRIORITY_LOW, TASKSTACKSIZE, 0, 0)
{
if(uTakeBin(binUart1, ~0)) {
…
}
uEndTasking;
}
后来看到一个uCOS的例程,也是采用的信号量。看来这样的场景就是该用信号量。
从中得到的教训是:先确定要做的是什么,根据这个选择解决方案,再看效率。问题的本质是当时对RTOS理解还不够。 第2个坎,using。
原来的裸机程序中断采用了using 1,当时未深究就这样用了,结果就出现了奇奇怪怪的问题。最终CosyOS的作者定位到了using。感兴趣的请参考:
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=1807&page=12#pid23079
解释的很好很详尽。我就是依据其规划了我的using。
不过现在想起来,觉得应该在项目之初不用using,等成形了,再加using做终极优化。 第3个坎,mutex。
其实我很早就注意到了需要加mutex。本来的计划是:
void i2cMcuWrite(uint8 devAddr, uint16 subAddr , uchar *buf, uint16 len)
{
uTakeMut(mutI2cMcu, ~0);
// i2cMcu registers operation
…
uBackMut(mutI2cMcu);
}
这样MCU资源被mutex保护,不会冲突。但是出问题了。
CosyOS作者很快发现该函数是不可重入的,解决方式是在业务层面加mutex,可以想象,在每个I2C和SPI操作前后都有uTakeMut和uBackMut,那肯定是mutex操作满天飞{:4_167:}。我按这个方式做了实现,工作嘛确实能工作的很好,优雅嘛则确实不够优雅。
请大神们看看,有什么解决方案吗? 第4个坎,size。
很快,我发现Flash不够用了。一方面RTOS本身要占用一定的空间,一方面CosyOS要求Large模式,我本来Small模式的代码编译后会变大。于是编译选项选favor size,代码优化再优化,但依然空间紧张。紧张到什么程度呢?这么说吧,8A8K64空间末尾会占用几个字节,导致我一个512字节的数组装不进去,我都颇耿耿于怀了一段时间{:4_167:},有帖为证:
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=1684&page=2#pid28659
不过现在8H8K64U是可以写全的,我最终产品也是用的这款,建议大家做新产品选型时也用8H系列,感觉成熟度更高。
反正穷尽了各种方法还是空间不足,终于咬咬牙,对CosyOS下手了。我的方法是先去掉NOOVERLAY,这时编译器就会告诉你哪些代码是没有用到的,把它们统统注释掉,往往一个函数的注释会引发新的不使用代码,几轮反复后,编译器没告警了,再恢复NOOVERLAY。终于,代码放得下了。此时有源代码的优势充分体现,绝对的可裁剪啊{:4_174:} 第5个坎,任务中止。
状态可能跳变,跳回去又跳回来,此时如果改变主芯片状态的任务已经开始则需要立即停止。此时用Suspend是不行的,因为后续任务恢复的时候是从Suspend处继续执行而不是从头执行;用Delete也不行,因为通常任务一开始就会uTakeMut,而Delete并不能让任务释放获取的mutex。
经仔细研究芯片行为,状态的跳变发生在8ms内,期间可能发生十几次状态变化,8ms后进入稳定态。于是最终决定增加debounce任务,最后一次状态变化8ms后再进行主芯片状态改变任务。
在帖子的开篇就提到过debounce,但那是从现在回顾历史的上帝之眼,实际上确定使用这种方法是经过了仔细的斟酌的,是目前我发现的最好方法,不需要发现改错了再回退,而是从一开始就预防好。但是,通用性不高,这种方法依赖于芯片的行为。
void chip0ISR() interrupt 0 using 3
{
iGiveBin(binDbc0); // 变化就给信号
}
bit debouncing0;
uCreateTask(tDbc0, PRIORITY_HIGHEST_USER, 128, 0, 0)
{
if (uTakeBin(binDbc0, debouncing0? (1000UL * 8 / SYSCFG_SYSTICKCYCLE): ~0)) {
debouncing0 = TRUE;
}
else {
uGiveBin(binMainBak); // 变化debounce后给信号
uGiveBin(binAlign0);// 变化debounce后给信号
debouncing0 = FALSE;
}
uEndTasking;
}
uTakeBin(binDbc0, debouncing0? (1000UL * 8 / SYSCFG_SYSTICKCYCLE): ~0) 根据不同的状态设定不同的超时值,这是个很通用的方法,在我的代码中多处用到。 还有一些小trick,比如:
1. LX51不支持中断号扩展插件,好在CosyOS也可用BL51(CosyOS作者确认可行),我目前就用的BL51+中断号扩展插件来使用IO中断,嘎嘎香。
2. 我的裸机程序中有很多功能模块,本来以为都要转化为任务,后来发现实时性要求不高的完全可以归并到一个任务中循环,像裸机程序中的大循环一样,如下:
uCreateTask(tMisc, PRIORITY_LOWEST, TASKSTACKSIZE, 0, 0)
{
uDelay_ms(100);
func1();
func2();
…
uEndTasking;
}
最终任务列表如下:
void start_hook(void)
{
uStartTask_Ready(tUart1);
uStartTask_Ready(tAS0);
uStartTask_Ready(tAS1);
uStartTask_Ready(tMainBak);
uStartTask_Ready(tAlign0);
uStartTask_Ready(tAlign1);
uStartTask_Ready(tDbc0);
uStartTask_Ready(tDbc1);
uStartTask_Ready(tTopo);
uStartTask_Ready(tSndGram);
uStartTask_Ready(tMisc);
}
这里面没提到的任务都是业务相关的,不多解释了。 从代码成形上机实测,到现在已有半年的时间,最长的一次连续工作过120小时,没有发现系统的问题。
我的结论是:CosyOS在大代码量(现在MCU Flash几乎是满的)的条件下可以正常工作。
后续有人有意采用CosyOS时,可以以这个项目为参考。我要特别提请注意:
1. 我进行的测试都是实际使用测试,而不是极限压力测试。
2. 这个项目开了11个任务,从RTOS的角度,可能并不算一个规模大的项目。
3. 这个项目仅用到二值信号量和mutex,其余的OS特性都被注释掉了。
目前想到的就是这些,大神们有什么意见建议,我欢迎之至。{:4_197:}
这是我的配置文件,供大家参考。
其中修改了内存池地址和大小,因我的程序占用了较多内存。
但这部分的配置有什么讲究吗?文档中没看到具体说明。请CosyOS作者解惑,谢谢。{:4_197:} 谢谢分享
页:
[1]
2