最近在学习RT-Thread的使用,同时也相当于在拿它评估做产品的软件开发周期,最终学习的目的也就是希望能在未来的项目上用起来,STM32CubeMX其实已经支持了RT-Thread Nano的配置了,但我还是希望手动移植一下,没想到移植RT-Thread Nano如此简单,必须分享出来,哈哈哈!
之前也分享过很多RT-Thread的学习笔记,链接如下:
RT-Thread UART设备驱动框架初体验(中断方式接收带\r\n的数据)
1、RT-Thread Nano版是啥?
RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位
对比RT-Thread完整版:
以上内容摘自RTT官网文档中心。
详情查看网址:
https://www.rt-thread.org/document/site/tutorial/nano/an0038-nano-introduction/
很明显Nano版本已经裁剪了很多东西,比如设备驱动及很多组件还有软件包,Nano版本更适合给客户做自由定制,客户在开发上自由度更大一些,但如果是重新开发一个新产品,我还是建议使用完整版,这么多轮子都造好了,而且RT-Thread的社区如此活跃,大佬们随时随地的线上支持,不用难道不可惜么?
文档中心也提供了如何移植RT-Thread Nano的笔记,但笔记归笔记,毕竟要自己动手去做才能了解这个过程,方便以后分析调试。
2、移植RT-Thread Nano到小
2.1 在官网上下载RT-Thread Nano
解压后得到如下文件:
2.2 使用stm32CubeMX生成一个基础工程
由于之前已经写了很多CubeMX配置的文章,所以这里不详细写了,只写我配置了哪些东西,具体看下面这篇链接,写得非常详细:
超轻量级网红软件定时器multi_timer(51+stm32双平台实战)
2.2.1 时钟配置
2.2.2 调试接口配置
2.2.3 调试串口配置
2.2.4 配置调试LED灯
2.2.5 去掉这三个处理函数,以防止重复定义报错
因为RT-Thread已经有相关定义和基于RT-Thread内核特性进行实现了,这里如果勾选相当于重复定义,所以不需要,这点在官网的开发文档里也有介绍,可以去瞧一瞧。
2.2.6 工程生成配置
这里选择不生成main函数,后面我们自己写,然后点Generate Code生成基础裸机工程代码。
2.3、移植RT-Thread Nano到基础工程
2.3.1 往工程中添加RT-Thread Nano代码
2.3.2 对RT-Thread Nano进行裁剪以及工程配置
(1)将BSP目录下除board.c、rtconfig.h以外的所有文件删除
(2)删除无关内核适配
由于小熊派的CPU是基于ARM Cortex M4架构,所以在arm下只保留cortex-m4文件夹,其余都删除。
(3)删除工程自带的finsh组件
由于finsh需要RT-Thread的串口设备驱动支持,这里我们只需要一个最小的RT-Thread工程,所以这里把finsh组件去掉。
(4)在Keil MDK中添加裁剪过后的RT-Thread源代码
(5)根据需求配置rtconfig.h文件
rtconfig.h
/* RT-Thread config file */ #ifndef __RTTHREAD_CFG_H__ #define __RTTHREAD_CFG_H__ #if defined(__CC_ARM) || defined(__CLANG_ARM) #include "RTE_Components.h" #if defined(RTE_USING_FINSH) #define RT_USING_FINSH #endif //RTE_USING_FINSH #endif //(__CC_ARM) || (__CLANG_ARM) // <<< Use Configuration Wizard in Context Menu >>> // <h>Basic Configuration // <o>Maximal level of thread priority <8-256> // <i>Default: 32 /*线程优先级数*/ #define RT_THREAD_PRIORITY_MAX /**8**/ 32 // <o>OS tick per second // <i>Default: 1000 (1ms) /*定义时钟节拍 1个tick为1ms 1000个tick每秒 1个tick为1ms*/ #define RT_TICK_PER_SECOND 1000 // <o>Alignment size for CPU architecture data access // <i>Default: 4 /*设定字节对齐个数为4字节*/ #define RT_ALIGN_SIZE 4 // <o>the max length of object name<2-16> // <i>Default: 8 /*内核对象名称长度,大于该长度会被内核裁剪*/ #define RT_NAME_MAX 8 // <c1>Using RT-Thread components initialization // <i>Using RT-Thread components initialization /*如果定义该宏,则会开启自动初始化机制,不定义的话就会关闭*/ #define RT_USING_COMPONENTS_INIT // </c> /*如果定义该宏,则设置应用入口为main函数*/ #define RT_USING_USER_MAIN // <o>the stack size of main thread<1-4086> // <i>Default: 512 /*设置main线程的大小*/ #define RT_MAIN_THREAD_STACK_SIZE 256 // </h> // <h>Debug Configuration // <c1>enable kernel debug configuration // <i>Default: enable kernel debug configuration //#define RT_DEBUG // </c> // <o>enable components initialization debug configuration<0-1> // <i>Default: 0 #define RT_DEBUG_INIT 0 // <c1>thread stack over flow detect // <i> Diable Thread stack over flow detect //#define RT_USING_OVERFLOW_CHECK // </c> // </h> // <h>Hook Configuration // <c1>using hook // <i>using hook //#define RT_USING_HOOK // </c> // <c1>using idle hook // <i>using idle hook //#define RT_USING_IDLE_HOOK // </c> // </h> // <e>Software timers Configuration // <i> Enables user timers #define RT_USING_TIMER_SOFT 0 #if RT_USING_TIMER_SOFT == 0 #undef RT_USING_TIMER_SOFT #endif // <o>The priority level of timer thread <0-31> // <i>Default: 4 #define RT_TIMER_THREAD_PRIO 4 // <o>The stack size of timer thread <0-8192> // <i>Default: 512 #define RT_TIMER_THREAD_STACK_SIZE 512 // </e> // <h>IPC(Inter-process communication) Configuration // <c1>Using Semaphore // <i>Using Semaphore #define RT_USING_SEMAPHORE // </c> // <c1>Using Mutex // <i>Using Mutex //#define RT_USING_MUTEX // </c> // <c1>Using Event // <i>Using Event //#define RT_USING_EVENT // </c> // <c1>Using MailBox // <i>Using MailBox #define RT_USING_MAILBOX // </c> // <c1>Using Message Queue // <i>Using Message Queue //#define RT_USING_MESSAGEQUEUE // </c> // </h> // <h>Memory Management Configuration // <c1>Dynamic Heap Management // <i>Dynamic Heap Management //#define RT_USING_HEAP // </c> // <c1>using small memory // <i>using small memory #define RT_USING_SMALL_MEM // </c> // <c1>using tiny size of memory // <i>using tiny size of memory //#define RT_USING_TINY_SIZE // </c> // </h> // <h>Console Configuration // <c1>Using console // <i>Using console #define RT_USING_CONSOLE // </c> // <o>the buffer size of console <1-1024> // <i>the buffer size of console // <i>Default: 128 (128Byte) #define RT_CONSOLEBUF_SIZE 128 // </h> #if defined(RT_USING_FINSH) #define FINSH_USING_MSH #define FINSH_USING_MSH_ONLY // <h>Finsh Configuration // <o>the priority of finsh thread <1-7> // <i>the priority of finsh thread // <i>Default: 6 #define __FINSH_THREAD_PRIORITY 5 #define FINSH_THREAD_PRIORITY (RT_THREAD_PRIORITY_MAX / 8 * __FINSH_THREAD_PRIORITY + 1) // <o>the stack of finsh thread <1-4096> // <i>the stack of finsh thread // <i>Default: 4096 (4096Byte) #define FINSH_THREAD_STACK_SIZE 512 // <o>the history lines of finsh thread <1-32> // <i>the history lines of finsh thread // <i>Default: 5 #define FINSH_HISTORY_LINES 1 #define FINSH_USING_SYMTAB // </h> #endif // <<< end of configuration section >>> #endif
2.4 验证RT-Thread Nano是否移植成功
2.4.1 在board.c中添加GPIO以及USART等初始化函数
board.c
#include "gpio.h" #include "usart.h" /** * This function will initial your board. */ void rt_hw_board_init() { //添加HAL初始化函数 HAL_Init(); /* System Clock Update */ SystemCoreClockUpdate(); /* System Tick Configuration */ _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); /* Call components board initial (use INIT_BOARD_EXPORT()) */ #ifdef RT_USING_COMPONENTS_INIT rt_components_board_init(); #endif #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); #endif //添加初始化函数 MX_GPIO_Init(); MX_USART1_UART_Init(); }
2.4.2 添加fputc函数,以支持printf
int fputc(int ch,FILE *fp) { return HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,1000); }
2.4.3 实现rt_hw_console_output函数
//重新实现rt_hw_console_output打印函数 void rt_hw_console_output(const char *str) { /* empty console output */ printf("%s",str); }
只要实现了这个函数,在工程中才可以调用rt_kprintf来打印,这一点通过阅读分析rt_kprintf函数源代码得知:
/** * This function will print a formatted string on system console * * @param fmt the format */ void rt_kprintf(const char *fmt, ...) { va_list args; rt_size_t length; static char rt_log_buf[RT_CONSOLEBUF_SIZE]; va_start(args, fmt); /* the return value of vsnprintf is the number of bytes that would be * written to buffer had if the size of the buffer been sufficiently * large excluding the terminating null byte. If the output string * would be larger than the rt_log_buf, we have to adjust the output * length. */ length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args); if (length > RT_CONSOLEBUF_SIZE - 1) length = RT_CONSOLEBUF_SIZE - 1; #ifdef RT_USING_DEVICE if (_console_device == RT_NULL) { rt_hw_console_output(rt_log_buf); } else { rt_uint16_t old_flag = _console_device->open_flag; _console_device->open_flag |= RT_DEVICE_FLAG_STREAM; rt_device_write(_console_device, 0, rt_log_buf, length); _console_device->open_flag = old_flag; } #else rt_hw_console_output(rt_log_buf); #endif va_end(args); } RTM_EXPORT(rt_kprintf); #endif
如上代码所示,当定义了RT_USING_DEVICE宏时,这里是调用了串口设备驱动,而我们这个Nano没有移植串口设备驱动,所以直接调用的rt_hw_console_output
,而rt_hw_console_output
是一个弱函数,本身并没有在内核里实现,所以我们要重新去实现它。
2.4.4 编写main函数
RT-Thread此时已经移植好了,接下来我们要编写main函数,实现以500ms的频率翻转LED灯以及通过打印Hello RTT_NANO
字符串,通过这个例子,验证移植是否成功!
main.c
int main(void) { while(1) { rt_kprintf("Hello RTT_NANO\n"); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); rt_thread_mdelay(500); } }
3、运行结果
对刚刚编写的程序编译然后下载到小熊派上:
如果采用-O0优化等级进行编译,通过编译生成的RT-Thread_Nano_For_BearPi.map
查看:
本工程移植的代码编译采用默认的-O3最大优化等级:
能够看到,采用-O3等级优化后的代码体积:通过编译生成的RT-Thread_Nano_For_BearPi.map查看:
优化等级越高,则相应的编译时间就越长,一般采用-O0进行验证,没有问题后再选择-O3编译,如果出现变量被优化;导致系统明明逻辑正确却有一些莫名其妙的问题也能够根据实际调试情况进行修改。
这里顺便说说这里各个字段的含义:
- Code:是我们编写代码占用的空间。
- RO data(Read Only) :只读常量的大小,如const。
- RW data(Read Write):初始化了的可读写变量的大小。
- ZI data(Zero Initialize):没有初始化的可读写变量的大小,ZI-data不会被算做代码里因为不会被初始化。
FLASH中的被占用的空间为:Code+RO Data + RW Data
芯片内部RAM使用的空间为:RW Data + ZI Data
足够轻量了吧!接下来下载到小熊派上可以看到:
不少朋友可能第一次移植RTT后会有疑问:为啥main函数里没看到其它硬件外设、时钟的初始化的东西呢?
那是因为RT-Thread拓展了main函数,在main函数之前就把这些工作都做好啦!还记得前面为什么要在board.c中添加外设等初始化函数的步骤吧?如下图所示,这是RT-Thread的启动流程图:
关于RT-Thread初始化流程描述文档如下:
https://www.rt-thread.org/document/site/programming-manual/basic/basic/
官网上描述启动流程的文档非常详细,而且我认为RT-Thread已经做得足够傻瓜化了,什么文档、例程啥都给你弄好了,如果认真花点时间好好学习研究是可以学习得非常透彻的,不懂随时论坛和RT-Thread的QQ群及微信群都可以提问,有众多的开发者一起解决问题。
4、移植工程案例下载
公众号后台回复:rttnano
即可获取移植案例的下载链接。
5、题外话
看到公众号后台有人问:C语言#和##连接符在项目中的应用(漂亮) 这篇文章里老外代码的出处,这是我很早之前在Github上看到的,这是来自Github上的Tilen Majerle
大佬写的,代码质量非常优秀,值得我们学习,他本人就职于ST意法半导体公司。
链接:https://github.com/MaJerle/stm32fxxx-hal-libraries
Github链接:https://github.com/MaJerle