RT-Thread 移植笔记
最近工作需要重新捡起 rtt 使用.之前使用的时候都是智能车比赛要求使用,用的也是逐飞科技移植好的工程,我自己都没有移植过.后来工作需要时发现自己一点都不懂,一脸懵逼.折磨一遍后再回顾,感觉其实也挺简单的.
1. 介绍
官方说明 (教程) 手册 : RT-Thread文档中心 (https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-nano/an0038-nano-introduction) 官方 api 手册 : RT-Thread API参考手册 (https://www.rt-thread.org/document/api/group___i_p_c.html)
- 官方有完善的教程,再写就有点多余,但是对于不太熟悉
rtos 的人来说,还是有点不够详细. - 我总结一下我的过程,并着重提醒踩过的坑,希望能帮助到你.
一、 nano 版源码
Nano 版, 俗称就是最简版,默认只包含最基本的线程 , 信号量 , 队列 , 邮箱等基础功能,不包含其他工具包, 比如finsh控制台.移植方便,因为只需要设置系统时钟部分的接口.
如果你还不了解线程,信号量,finsh控制台等概念. 先去看看说明视频,这部分不是本文重点,不赘述. 总有适合你的 : RT-Tread 视频中心 (https://www.rt-thread.org/page/video.html)
- 下载地址是giithub,下载项目,解压后打开
rt-thread 文件夹,得到下面的内容.看着眼花缭乱,很多东西,其它下方的非文件夹文件都是github 的相关文件,和rtt 没关系,忽略.主要看上面一排文件夹的内容.
1. bsp 文件夹
bsp : 是板级接口文件,也就是 board.c 和 rtconfig.h , 看名字翻译就知道,前者是板级接口,后者的rt系统配置,里面全是宏定义开关.而其它文件夹都是一些常用单片机移植好的例子,不需要参考就直接删除.想必多半没有你需要的,不然也不会自己移植了 .
2. components 文件夹
components : 看翻译就知道,里面装着一些rtt系统的组件,nano 版中,这里面就一个finsh 控制台组件和一个device 组件,后者没用过,好像是硬件接口,略.主要看finsh 控制台组件.最后移植好rtt后再根据需要添加组件.如果单片机SRAM 抓急,一般就放不下finsh .
3. docs 文件夹
- 说明文档,就一个文本文件,打开后是一堆教程链接.略.
4. include 文件夹
5. libcpu 文件夹
- 里面装着单片机内核平台文件.根据单片机内核和ide平台选择.
- 以我选择为例: HC32L196JCTA + IAR; 选择的就是下面2个高亮文件.
6. src 文件夹
7. 最后
- 最后根据需要进行删减.
include 和 src 不需要改动,板级只保留两个代码文件,组件只保留finsh文件夹,内核只保留需要的.最后得到的文件夹内容如下.然后整个文件夹丢到工程里即可.
二、修改工程
1. 添加文件
- 根据原本的目录结构,一模一样的分类,避免混乱.finsh文件先不添加,之后再加.
- 添加完后文件后别忘记添加头文件路径.目前只有这3个目录有头文件.
$PROJ_DIR$\..\rtthread\include\libc
$PROJ_DIR$\..\rtthread\include
$PROJ_DIR$\..\rtthread\bsp
2. 编译文件
- 添加文件和路径后,直接编译.一般只会有这3个错误,没有警告.这3个错误都是函数重名.
-
HardFault_Handler : 是单片机硬件错误中断函数, rtt系统有自带的硬件中断错误,如果线程跑飞会进入,同时反馈错误信息. -
SysTick_Handler : 是单片机滴答定时器中断函数.rtt系统会使用滴答定时器作为系统节拍.这意味着单片机运行时将不能另外使用滴答定时器,同时在main之前要初始化好系统时钟和滴答定时器.之后再修改. -
__low_level_init : 是单片机启动函数,就是调用main函数的函数.rtt在调用main之前会做一系列初始化操作. -
知道函数名字后就直接打开全局搜索,找函数位置.然后屏蔽掉就可以了.再编译就没有错了.安心~
3. 接口函数
- 还需要设置三样东西, 系统时钟初始化 , 滴答定时器初始化 , 系统延时函数修改.
- 系统时钟初始化 : 在
board.c 文件中的rt_hw_board_init() 函数.
SystemCoreClockUpdate();
- 原本第一个调用的内容如上,功能是获取系统时钟频率.一些单片机的可能在运行rtt系统前需要修改时钟频率才能运行.所以根据需要修改.
比如我使用的hc32,裸机例程默认4Mhz,如果不修改成全速的48Mhz,貌似不能正常运行. 关于嵌入式单片机时钟树的知识我比较薄弱,而且不同单片机平台差异巨大,请自己找资料尝试修改吧.
- 滴答定时器初始化 : 在
board.c 文件中的rt_hw_board_init() 函数.
_SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
- 原本第二个调用的内容如上,功能是初始化滴答定时器,设定周期中断并开启.需要设置成1ms.原型就在
board.c 文件中.这个根据单片机而异.直接找厂家例程里的滴答定时器例程,复制粘贴即可.
- 系统延时函数修改 : 大部分单片机库中都有自带延时函数,一般都是使用滴答定时器.但是滴答定时器已经被用作rtt系统节拍了.单片机库的自带延时函数必须修改.
- 这时有2个选择,如果延时函数是大于或等于毫秒级的,就可以调用rtt系统的延时函数.
官方说明手册 : 使线程睡眠 在实际应用中,我们有时需要让运行的当前线程延迟一段时间,在指定的时间到达后重新运行,这就叫做 “线程睡眠”。线程睡眠可使用以下三个函数接口: rt_err_t rt_thread_sleep(rt_tick_t tick); rt_err_t rt_thread_delay(rt_tick_t tick); rt_err_t rt_thread_mdelay(rt_int32_t ms);
- 注意,rtt系统的延时的允许切换线程的延时,而且一定要开启rtt系统调度才会起作用.如果并不想发生线程调度,就使用软件模拟延时.也可以使用其他通用定时器资源代替.不过还是推荐简单快捷的软件模拟.
void __delay10us(uint32_t u32Cnt)
{
for (int j=0; j<u32Cnt; j++)
for (int i=0; i<(SystemCoreClock/1000000); i++);
}
- 使用延时函数的时候,一定要注意是允许切换线程的还是不允许的.
4. 最后
- 至此,最基本的rtt系统移植就完成. 编译无误,下载进去,在线调试.看看能不能到主函数部分,再看看每调用一次延时,节拍变化是不是对应.主函数本身就是一个线程,如果能正常运行就算事半功倍了.
void main(void)
{
while (1)
rt_thread_mdelay(100);
}
- 如果要用信号量,邮箱等功能,记得开启相应宏定义开关.
三、finsh 控制台
$PROJ_DIR$\..\rtthread\components\finsh
- 现在可以先直接编译,会提示一个 报错和一堆警告,
警告不管了,虽然想管,但是修改rtt系统源文件不好 .
- 提示没有添加头文件,手册里说在
rtconfig.h 文件内去掉注释,但是我没看到我下载的rtconfig.h 文件有这行注释,所以自行添加算了.
- 再编译的话就更多警告,还有一个错误,提示接口函数没实现.我们这里直接把它屏蔽了,之后再实现这个函数.屏蔽之后再编译就并不会有错误了.只剩下警告.
-
接下来开始实现接口函数,总结来说就是要实现finsh控制台的 发送字符串函数,接收字符串函数,进行串口通信初始化. -
串口初始化属于板级功能,而且要在rtt系统运行前执行,所以是和系统时钟初始化放一起就可以了.也就是 rt_hw_board_init() 函数里.rtt也提供隐式执行,调用宏定义即可. -
发送字符串函数功能的实现拷贝单片机库例程即可.接收字符串函数也是.唯一需要注意的是接收字符串要在接收完所有后再传入,而不是一个个传入,不然不能正确识别指令. -
finsh控制台如果接收到不能识别内容会原模原样返回发送,用于自行判断.如果发现指令没错,那可能是最后没有加换行符,指令的最后要加换行才会被识别.
例子
推荐参考 : 移植示例代码
- 其中分2部分,第一部分完全不用修改,第二部分只修改亿点点.
static int uart_init(void)
{
rt_ringbuffer_init(&uart_rxcb, uart_rx_buf, UART_RX_BUF_LEN);
rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0);
return 0;
}
INIT_BOARD_EXPORT(uart_init);
void rt_hw_console_output(const char *str)
{
rt_size_t i = 0, size = 0;
char a = '\r';
size = rt_strlen(str);
for (i = 0; i < size; i++)
{
if (*(str + i) == '\n')
{
}
}
}
void USART2_IRQHandler(void)
{
int ch = -1;
rt_base_t level;
rt_interrupt_enter();
{
{
ch = -1;
rt_ringbuffer_putchar(&uart_rxcb, ch);
}
rt_sem_release(&shell_rx_sem);
}
rt_interrupt_leave();
}
|