RT-Thread移植 与 使用 笔记

Posted L建豪 忄YH

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.crtconfig.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. 最后

  • 最后根据需要进行删减.includesrc 不需要改动,板级只保留两个代码文件,组件只保留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. 接口函数

  • 还需要设置三样东西, 系统时钟初始化 , 滴答定时器初始化 , 系统延时函数修改.
  1. 系统时钟初始化 : 在board.c文件中的rt_hw_board_init()函数.
    /* System Clock Update */
    SystemCoreClockUpdate();
  • 原本第一个调用的内容如上,功能是获取系统时钟频率.一些单片机的可能在运行rtt系统前需要修改时钟频率才能运行.所以根据需要修改.

比如我使用的hc32,裸机例程默认4Mhz,如果不修改成全速的48Mhz,貌似不能正常运行.
关于嵌入式单片机时钟树的知识我比较薄弱,而且不同单片机平台差异巨大,请自己找资料尝试修改吧.

  1. 滴答定时器初始化 : 在board.c文件中的rt_hw_board_init()函数.
    /* System Tick Configuration */
    _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
  • 原本第二个调用的内容如上,功能是初始化滴答定时器,设定周期中断并开启.需要设置成1ms.原型就在board.c文件中.这个根据单片机而异.直接找厂家例程里的滴答定时器例程,复制粘贴即可.
  1. 系统延时函数修改 : 大部分单片机库中都有自带延时函数,一般都是使用滴答定时器.但是滴答定时器已经被用作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开发笔记》 第三部分 RT-Thread 移植与设备驱动开发 - 第1章 RT-Thread 开发环境搭建 (Keil+env)

RT-Thread 时钟管理

单片机笔记基于Cotex-M3的RT-Thread的移植

单片机笔记基于Cotex-M3的RT-Thread的移植

CH579M移植RT-THREAD实战笔记(KEIL5环境)