• 【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
    
    • 1
    • 2
    • 3

    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();
    
    • 1
    • 2
    • 原本第一个调用的内容如上,功能是获取系统时钟频率.一些单片机的可能在运行rtt系统前需要修改时钟频率才能运行.所以根据需要修改.

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

    1. 滴答定时器初始化 : 在board.c文件中的rt_hw_board_init()函数.
        /* System Tick Configuration */
        _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
    
    • 1
    • 2
    • 原本第二个调用的内容如上,功能是初始化滴答定时器,设定周期中断并开启.需要设置成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 是当前系统时钟频率
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 使用延时函数的时候,一定要注意是允许切换线程的还是不允许的.

    4. 最后

    • 至此,最基本的rtt系统移植就完成. 编译无误,下载进去,在线调试.看看能不能到主函数部分,再看看每调用一次延时,节拍变化是不是对应.主函数本身就是一个线程,如果能正常运行就算事半功倍了.
    void main(void)
    {
    	while (1)
        	rt_thread_mdelay(100);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 如果要用信号量,邮箱等功能,记得开启相应宏定义开关.

    三、finsh 控制台

    • 添加文件.

    在这里插入图片描述

    • 添加文件路径.
    $PROJ_DIR$\..\rtthread\components\finsh
    
    • 1
    • 现在可以先直接编译,会提示一个 报错和一堆警告,警告不管了,虽然想管,但是修改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();    //在中断中一定要调用这对函数,离开中断
    }
    
    // 略,最后面这个是没用的.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 完,最后能用,很开心,别忘记加回车.

    在这里插入图片描述

  • 相关阅读:
    Oracle 21版Database In-Memory LivaLabs实验(下)
    【Kubernetes】k8s集群资源调度
    【RocketMQ中生产者生产消息的高可用机制、消费者消费消息的高可用机制、消息的重试机制、死信队列于死信消息】
    软件测试黑马程序员基础班-定义介绍,核心课程,计算机的组成
    便携式手提万兆网络协议测试仪
    uniapp 冒泡
    Computational Protein Design with Deep Learning Neural Networks
    力扣:133. 克隆图(Python3)
    springcloud和基础服务的搭建以及封装
    介绍一下浏览器的缓存(Expires, Cache-Control等)
  • 原文地址:https://blog.csdn.net/Lovely_him/article/details/126301068