RT-Thread移植 与 使用 笔记
Posted 兴趣使然_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RT-Thread移植 与 使用 笔记相关的知识,希望对你有一定的参考价值。
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 文件夹
- rtt系统的相关
.h
头文件集合.
5. libcpu 文件夹
- 里面装着单片机内核平台文件.根据单片机内核和ide平台选择.
- 以我选择为例: HC32L196JCTA + IAR; 选择的就是下面2个高亮文件.
6. src 文件夹
- rtt系统的相关
.c
脚本文件集合.
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()
函数.
/* System Clock Update */
SystemCoreClockUpdate();
- 原本第一个调用的内容如上,功能是获取系统时钟频率.一些单片机的可能在运行rtt系统前需要修改时钟频率才能运行.所以根据需要修改.
比如我使用的hc32,裸机例程默认4Mhz,如果不修改成全速的48Mhz,貌似不能正常运行.
关于嵌入式单片机时钟树的知识我比较薄弱,而且不同单片机平台差异巨大,请自己找资料尝试修改吧.
- 滴答定时器初始化 : 在
board.c
文件中的rt_hw_board_init()
函数.
/* System Tick Configuration */
_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++);
// SystemCoreClock 是当前系统时钟频率
- 使用延时函数的时候,一定要注意是允许切换线程的还是不允许的.
4. 最后
- 至此,最基本的rtt系统移植就完成. 编译无误,下载进去,在线调试.看看能不能到主函数部分,再看看每调用一次延时,节拍变化是不是对应.主函数本身就是一个线程,如果能正常运行就算事半功倍了.
void main(void)
while (1)
rt_thread_mdelay(100);
- 如果要用信号量,邮箱等功能,记得开启相应宏定义开关.
三、finsh 控制台
- 添加文件.
- 添加文件路径.
$PROJ_DIR$\\..\\rtthread\\components\\finsh
- 现在可以先直接编译,会提示一个 报错和一堆警告,
警告不管了,虽然想管,但是修改rtt系统源文件不好.
- 提示没有添加头文件,手册里说在
rtconfig.h
文件内去掉注释,但是我没看到我下载的rtconfig.h
文件有这行注释,所以自行添加算了.
- 还有记得开启finsh控制台的宏定义开关.
- 再编译的话就更多警告,还有一个错误,提示接口函数没实现.我们这里直接把它屏蔽了,之后再实现这个函数.屏蔽之后再编译就并不会有错误了.只剩下警告.
-
接下来开始实现接口函数,总结来说就是要实现finsh控制台的 发送字符串函数,接收字符串函数,进行串口通信初始化.
-
串口初始化属于板级功能,而且要在rtt系统运行前执行,所以是和系统时钟初始化放一起就可以了.也就是
rt_hw_board_init()
函数里.rtt也提供隐式执行,调用宏定义即可. -
发送字符串函数功能的实现拷贝单片机库例程即可.接收字符串函数也是.唯一需要注意的是接收字符串要在接收完所有后再传入,而不是一个个传入,不然不能正确识别指令.
-
finsh控制台如果接收到不能识别内容会原模原样返回发送,用于自行判断.如果发现指令没错,那可能是最后没有加换行符,指令的最后要加换行才会被识别.
例子
- 其实官方有例子,直接拷贝即可.
推荐参考 : 移植示例代码
- 其中分2部分,第一部分完全不用修改,第二部分只修改亿点点.
/* 第一部分:ringbuffer 实现部分 */
// 略,不需要修改
/* 第二部分:finsh 移植对接部分 */
// 略,变量定义部分
/* 初始化串口,中断方式 */
static int uart_init(void)
/* 初始化串口接收 ringbuffer */
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);
/* 移植控制台,实现控制台输出, 对接 rt_hw_console_output */
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')
// 发送字符串 '\\r' ,更改为自己单片机的
// 发送字符 str[i] ,更改为自己单片机的
/* 移植 FinSH,实现命令行交互, 需要添加 FinSH 源码,然后再对接 rt_hw_console_getchar */
/* 中断方式 */
// 略,不需要修改
/* uart 中断 */
void USART2_IRQHandler(void)
int ch = -1;
rt_base_t level;
/* enter interrupt */
rt_interrupt_enter(); //在中断中一定要调用这对函数,进入中断
// 略,判断中断标志位,并去除中断标志位
ch = -1;
// 略 获取字符
/* 读取到数据,将数据存入 ringbuffer */
rt_ringbuffer_putchar(&uart_rxcb, ch);
// 这比较特殊,需要获取完所有字符后,再调用释放信号量.
// 如果能一次中断获取完毕就在中断里调用,
// 如果不能,可以另外开一个定时器,记录接收超时,超时了就认为是接收完毕并调用即可.
rt_sem_release(&shell_rx_sem);
/* leave interrupt */
rt_interrupt_leave(); //在中断中一定要调用这对函数,离开中断
// 略,最后面这个是没用的.
- 完,最后能用,很开心,别忘记加回车.
以上是关于RT-Thread移植 与 使用 笔记的主要内容,如果未能解决你的问题,请参考以下文章
《嵌入式系统 - RT-Thread开发笔记》 第三部分 RT-Thread 移植与设备驱动开发 - 第1章 RT-Thread 开发环境搭建 (Keil+env)