前言
RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。在 RT-Thread 系统中,任务通过线程实现的,RT-Thread 中的线程调度器也就是以上提到的任务调度器。 RT-Thread 主要采用 C 语言编写,浅显易懂,方便移植。它把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。针对资源受限的微控制器(MCU)系统,可通过方便易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 版本(NANO 是 RT-Thread 官方于 2017 年 7 月份发布的一个极简版内核);而对于资源丰富的物联网设备,RT-Thread 又能使用在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。 相较于 Linux 操作系统,RT-Thread 体积小,成本低,功耗低、启动快速,除此以外 RT-Thread 还具有实时性高、占用资源小等特点,非常适用于各种资源受限(如成本、功耗限制等)的场合。虽然 32 位 MCU 是它的主要运行平台,实际上很多带有 MMU、基于 ARM9、ARM11 甚至 Cortex-A 系列级别 CPU 的应用处理器在特定应用场合也适合使用 RT-Thread。
RT-Thread作为国产嵌入式操作系统,具有易移植,易上手,占用资源少等特点,适合用来做嵌入式操作系统的入门。基于STM32的工程如果能用好RT-Thread将大幅加快开发速度。本文基于STM32L476RG-nucleo开发板和STM32CubeMX软件移植RT-Thread 3.1.5 NANO 版本 带MSH控制台 不使用device框架
利用STM32CubeMX下载RT-Thread NANO第三方包并新建工程
NANO Pack获取与安装
要获取 RT-Thread Nano 软件包,需要在 CubeMX 中添加 https://www.rt-thread.org/download/cube/RealThread.RT-Thread.pdsc
具体步骤:进入打开 CubeMX,从菜单栏 help 进入 Manage embedded software packages 界面,点击 From Url 按钮,进入 User Defined Packs Manager 界面,其次点击 new,填入上述网址,然后点击 check,如下图所示: check 通过后,点击 OK 回到 User Defined Packs Manager 界面,再次点击 OK, CubeMX 自动连接服务器,获取包描述文件。回到 Manage embedded software packages 界面,就会发现 RT-Thread Nano 3.1.5 软件包,选择该软件包,点击 Install Now,如下图所示: 等待安装完成,成功安装后,版本前面的小蓝色框变成填充的黄绿色,现象如下图所示:
创建基础工程
在 CubeMX 主界面的菜单栏中 File 选择 New Project,如下图所示 新建工程之后,在弹出界面芯片型号中输入某一芯片型号,方便锁定查找需要的芯片,双击被选中的芯片,如下图所示
添加 RT-Thread Nano 到工程
选择 Nano 组件
选中芯片型号之后,点击 Softwares Packages->Select Components 进入组件配置界面,选择 RealThread, 然后根据需求选择 RT-Thread 组件,然后点击 OK 按钮,如下图所示:
配置 Nano
选择组件之后,对组件参数进行配置。在工程界面 Pinout & Configuration 中,进入Software Packs配置区,打开kernel和shell
关闭CubeMX生成的中断
RT-Thread 操作系统重定义了 HardFault_Handler、PendSV_Handler、SysTick_Handler 中断函数,为了避免重复定义的问题,在生成工程之前,需要在中断配置中,代码生成的选项中,取消选择三个中断函数(对应注释选项是 Hard fault interrupt, Pendable request, Time base :System tick timer),最后点击生成代码,具体操作如下图所示
其他配置(其他系列的MCU视情况而定)
RCC与SYS
为了系统时钟的高速和稳定运行。RCC中一般HSE选择Crystal/Ceramic Resonator
为了使用STLINK的SWD调试与下载,SYS的Debug我们选择Serial Wire,这样PA13和PA14就会成为调试引脚SWIO和SWCLK。 由于RT-Thread的OStick使用的是Systick定时器作为时钟源,而HAL库的delay函数用的也是Systick,这样两者在更新时钟的时候就会产生冲突,造成时钟紊乱,所以在这里将HAL库的时钟源改到其他的定时器上,为了少占用高级定时器,这里选择普通定时器TIM6
GPIO和USART设置
STM32L476RG-nucleo板载一个LED作为指示灯,位于GPIO的PA5口上,为了方便之后的验证,我们打开PA5,选择为GPIO_OUTPUT 这里我们还需要打开一个串口作为电脑和MCU通信的方式,用于MSH控制台交互。 STM32L476RG-nucleo开发板自带一个stlinkv2,其虚拟串口连在了USART2上。所以我们不需要CH340等串口转换模块就可以直接和电脑通信。只需要通过一根USB线就可以进行供电,烧录,串口通信。 打开USART2,选择Asynchronous,其他参数不用改
时钟树设置
在不追求低功耗的情况下,一般选择将频率拉满(电脑CPU都想着超频,这个自然是能拉满就拉满了)。 输入后按Enter,然后选择YES,就会自动生成解决方案
工程设置
Toolchain/IDE 选择MDK-ARM
1.为了减少文件量,一般选择第二个,只复制必要的库文件 2.为了方便外设管理,选择每个外设都生成一个.c和.h文件 3.个人习惯,重新生成的时候备份之前的 4.一定要选上Keep User Code when re-generating(否则CubeMX分分钟给你表演删库跑路) 都设置好之后点击Generate Code,打开KEIL工程。
RT-Thread适配
KEIL基本设置
在Keil的Options里的target选择编译器为V5版本,最新的V6版本会导致一些特性失效,甚至无法通过编译。 编译器优化选择Level 0,避免过度优化导致调试的时候出问题,C99 Mode个人习惯勾选 Debug硬件选择STLINK,不使用软件仿真。实际上keil只支持F103xxT6这一个系列的软件仿真,像是L4,F4,H7系列都不能仿真,会出现没有读写权限和MAP映射出错的问题。其实也不推荐软件仿真,硬件实操更有效果。
取消注释FINSH
编译工程,会提示1个error,15个warning,由于我们需要使用FINSH,所以得根据提示取消掉rtconfig.h的145行的注释
修改rt_hw_console_getchar()函数
再次编译,此时没有warning,但是有一个error,提示寄存器结构体中没有叫DR的寄存器,实际上,DR是STM32F103系列的串口数据寄存器,共用了数据接收和发送,而后面出的F4,L4,H7系列分开了数据接收和发送寄存器。接收寄存器叫RDR,发送寄存器叫TRD。 所以我们定位到问题,将DR改成RDR。
PS. rt_hw_console_getchar()和rt_hw_console_output()是RT所编写的接收串口输入,以及输出调试信息到控制台的函数,在3.1.3及之前的版本需要用户自己编写。 3.1.5已在工程中自带。 实际上,board.c中还自带了串口初始化函数,默认初始化USART2并作为通信方式,我们只需要在后面调用就可以。
再次编译,无错误,有两个warning,我们先无视。
系统时钟配置
查看startup.s汇编程序会发现,在上电的时候,引导程序创建完堆、栈,分配好中断向量表后进入的不是用户的main函数,而是__main,实际上执行的是board.c中的rt_hw_board_init(),进行板级初始化,然后系统初始化,最后进入用户的main.c,具体流程可以参照RTT的官方文档。 不同于3.1.3版本,3.1.5版本的时钟初始化以及串口初始化已经在board.c处理的比较好。我们可以看到在rt_hw_board_init()函数中extern了 HAL库的SystemClock_Config(),也就是在启动的时候就配置好了时钟(先于main.c) 所以我们只需要在SystemCoreClockUpdate();行下面加入uart_init(); 编译,此时无错误,有3个warning。
一些必要的设置与说明
在main.c中#include <rtthread.h> (废话,要用人家的东西不include怎么行)
将编译好的程序烧录进MCU,然后打开电脑的串口调试工具,打开STLINK Virtual COM port,然后Reset,可以看到成功启动的日志。离成功已经很近了 但是此时我们还有两个问题需要解决 1是为什么没有出现MSH>提示符 2是为什么发指令无效 实际上,问题一的答案是有关线程优先级的,main函数的优先级是10,高于msh的20,而我们可以看到,main.c中只有一个while(1)的死循环,并不会出让CPU,所以msh不会被调用,自然也就无法响应输入了。 解决办法是添加在while(1)中加入rt_thread_mdelay(10);
而问题二则涉及到串口缓存和回显的问题,简而言之,接受串口输入的函数一次只能接收一个字符,而国产软件一般都做成一次发一串的形式,接收端就无法形成完整的语句。这个解决办法有两种,一是在RTT的设置中关闭键盘回显(不推荐,容易改出问题),二是改用其他的串口助手,如putty软件。(推荐,有回显而且界面美观)
下载新编译的程序到MCU,使用putty打开串口,Reset,并尝试输入list_thread,可以看到成功显示运行的线程 到这里,移植的过程就结束了,今后RTT的具体教程与案例也会在博客上更新
总结
移植的过程需要对照官方手册的说明,加入自身的思考,才能解决过程中遇到的一个个问题 本篇博客是结合官方文档和所学的单片机的知识的成果,希望能帮助到更多的入门者。
|