找回密码
 立即注册
查看: 1064|回复: 14

我是怎样用CosyOS构建系统的

[复制链接]
  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:41:37 | 显示全部楼层 |阅读模式
本帖最后由 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,给别人参考,也看看大神们有什么意见建议。

3 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:42:48 | 显示全部楼层
接下来将枚举我在应用CosyOS时趟过的那些印象深刻的坎,描述时将尽量采用系统相关的模型,不涉及具体业务。

第1个坎,激活报文解析任务的方式。
本来计划用iResumeTask,因为CosyOS作者说过Suspend Resume任务的效率最高,但是和CosyOS作者讨论的结论是这样有潜在问题,会有解析任务先恢复再挂起,导致漏掉一次解析的可能性。感兴趣的请参考:
https://www.stcaimcu.com/forum.p ... mp;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理解还不够。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:43:49 | 显示全部楼层
第2个坎,using。
原来的裸机程序中断采用了using 1,当时未深究就这样用了,结果就出现了奇奇怪怪的问题。最终CosyOS的作者定位到了using。感兴趣的请参考:
https://www.stcaimcu.com/forum.p ... mp;page=12#pid23079
解释的很好很详尽。我就是依据其规划了我的using。
不过现在想起来,觉得应该在项目之初不用using,等成形了,再加using做终极优化。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:45:00 | 显示全部楼层
第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
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:45:59 | 显示全部楼层
第4个坎,size。
很快,我发现Flash不够用了。一方面RTOS本身要占用一定的空间,一方面CosyOS要求Large模式,我本来Small模式的代码编译后会变大。于是编译选项选favor size,代码优化再优化,但依然空间紧张。紧张到什么程度呢?这么说吧,8A8K64空间末尾会占用几个字节,导致我一个512字节的数组装不进去,我都颇耿耿于怀了一段时间,有帖为证:
https://www.stcaimcu.com/forum.p ... amp;page=2#pid28659
不过现在8H8K64U是可以写全的,我最终产品也是用的这款,建议大家做新产品选型时也用8H系列,感觉成熟度更高。
反正穷尽了各种方法还是空间不足,终于咬咬牙,对CosyOS下手了。我的方法是先去掉NOOVERLAY,这时编译器就会告诉你哪些代码是没有用到的,把它们统统注释掉,往往一个函数的注释会引发新的不使用代码,几轮反复后,编译器没告警了,再恢复NOOVERLAY。终于,代码放得下了。此时有源代码的优势充分体现,绝对的可裁剪啊

点评

先去掉 NOOVERLAY,未引用的代码编译器会告警,注释掉后再编译,重复这个过程直到没有告警再恢复 NOOVERLAY,可最大程度的裁剪。推荐大家采用!  发表于 2024-10-10 15:38
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:47:55 | 显示全部楼层
第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) 根据不同的状态设定不同的超时值,这是个很通用的方法,在我的代码中多处用到。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:49:03 | 显示全部楼层
还有一些小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);
}
这里面没提到的任务都是业务相关的,不多解释了。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 00:50:27 | 显示全部楼层
从代码成形上机实测,到现在已有半年的时间,最长的一次连续工作过120小时,没有发现系统的问题。
我的结论是:CosyOS在大代码量(现在MCU Flash几乎是满的)的条件下可以正常工作。
后续有人有意采用CosyOS时,可以以这个项目为参考。我要特别提请注意:
1.        我进行的测试都是实际使用测试,而不是极限压力测试。
2.        这个项目开了11个任务,从RTOS的角度,可能并不算一个规模大的项目。
3.        这个项目仅用到二值信号量和mutex,其余的OS特性都被注释掉了。

目前想到的就是这些,大神们有什么意见建议,我欢迎之至。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:4
  • 最近打卡:2024-10-10 17:18:25

2

主题

17

回帖

306

积分

中级会员

积分
306
发表于 2024-10-8 01:14:42 | 显示全部楼层
这是我的配置文件,供大家参考。

其中修改了内存池地址和大小,因我的程序占用了较多内存。
但这部分的配置有什么讲究吗?文档中没看到具体说明。请CosyOS作者解惑,谢谢。

mcucfg_8051.h

12.65 KB, 下载次数: 68

点评

如果用户需用到 动态内存,应在此正确配置内存池指针和大小, CosyOS会自动初始化内存池。 什么时候会用到动态内存呢? 1、调用动态内存分配的标准库函数,malloc、calloc、realloc、free。 2、调用CosyOS动态内存  详情 回复 发表于 2024-10-10 16:16
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:391
  • 最近打卡:2025-04-30 00:26:42

0

主题

336

回帖

1490

积分

金牌会员

积分
1490
发表于 2024-10-8 01:17:24 | 显示全部楼层
谢谢分享
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-2 06:42 , Processed in 0.157298 second(s), 106 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表