从本文开始,记录自己的RT-Thread学习笔记,基于STM32L475VET6讲解,相关开发板用RTT&正点原子的潘多拉IoT Board开发板。本文先从Nano开始学起,个人觉得对于初学者,还是先学会Nano的移植,把内核部分向学一遍,再去学组件和设备驱动以及其他的东西,这里包括RT-Thread的内核移植、FinSH移植,相关代码到GitHub下载:https://github.com/sanjaywu/STM32L475_PANDORA_RT-Thread_DEMO

一、获取裸机工程

1、裸机工程可到GitHub下载:https://github.com/sanjaywu/STM32L475_PANDORA_DEMO,下载完成之后,打开工程文件夹,可以发现如下文件:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义

2、接着我们把HARDWARE、SYSTEM和USMART这三个文件删除,HARDWARE文件夹是裸机的外设驱动,在讲解移植的时候不需要用到,SYSTEM文件夹有delay延时、串口驱动和相关类型宏定义,在移植RT-Thread的时候,我们会重新实现delay延时和串口驱动以及类型宏定义。

 

二、下载 RT-Thread Nano 源码

1、RT-Thread Master 的源码可从 RT-Thread GitHub 仓库下载,Nano 就是从里面扣出来的,去掉了一些组件和各种开发板的 BSP,保留了 OS的核心功能,但足够我们使用。RT-Thread 官方并没有将抠出来的Nano 放 到 他 们 的 官 方 网 站 , 而 是 作 为 一 个 Package 放 在 了 KEIL 网 站:http://www.keil.com/dd2/pack/,目前最新的是3.1.1版本,打开这条连接,然后拉到下面找到RT-Thread的Package:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_串口_02

2、点击箭头下载,弹出窗口点击OK,然后开始下载:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_03

3、下载完成之后双击安装这个pack,安装的路径和你安装MDK5的时候是一样的,我安装的是默认路径。

4、安装完成之后,找到你安装MDK5的路径,然后按这个路径找到RT-Thread的源码:C:\Keil_v5\ARM\PACK\RealThread\RT-Thread\3.1.1:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_04

 三、往裸机工程添加 RT-Thread 源码

1、在前面下载好的裸机工程里,再新建一个文件夹为RT-Thread的,然后将上面下载好的Nano版源码拷贝到这个文件:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_05

2、对于Nano源码各个文件内容删减:

(1)打开bsp,这里RT-Thread是放底层驱动的东西:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_串口_06

除了board.c和rtconfig.h这两个文件,其他都删除,然后再新建一个board.h头文件。

(2)打开components,RT-Thread组件放置的地方,只有一个finsh,保留它,这个finsh非常好用:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_串口_07

(3)打开libcpu —> arm,因为用的是STM32L4xx,是cortex-m4,所以只需保留cortex-m4即可,其它都删除:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_串口_08

(4)剩下的include和src文件设RT-Thread的头文件和内核源码,不能删除,保留完整。

(5)接着,新建一个文件夹来放设备驱动,命名device_drivers。这里为什么要用RT-Thread设备驱动呢,因为RT-Thread的finsh功能实现需要串口,这里就用先只设备驱动里面的串口驱动来实现,自己从RT-Thread的master版本中整理出来,代码可以看工程(https://github.com/sanjaywu/STM32L475_PANDORA_RT-Thread_DEMO)里面的,这样既能实现finsh也能实现rt_kprintf。当然你也可以自己写一个串口驱动,只不过后面一直finsh就会很麻烦,读写函数都要改掉,而且容易出错。

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_09

最终移植整理好之后,RT-Thread的文件如下:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_10

 四、修改rtconfig.h

 

1 #ifndef __RTTHREAD_CFG_H__
 2 #define __RTTHREAD_CFG_H__
 3  
 4 /* RT-Thread内核部分 */
 5 #define RT_NAME_MAX 8                    //内核对象名称最大长度,大于该长度的名称多余部分会被自动裁掉
 6 #define RT_ALIGN_SIZE 4                    //字节对齐时设定对齐的字节个数。常使用ALIGN(RT_ALIGN_SIZE)进行字节对齐
 7 #define RT_THREAD_PRIORITY_MAX 32        //定义系统线程优先级数;通常用RT_THREAD_PRIORITY_MAX-1定义空闲线程的优先级
 8 #define RT_TICK_PER_SECOND 1000            //定义时钟节拍,为1000时表示1000个tick每 秒,一个tick为1ms
 9 #define RT_USING_OVERFLOW_CHECK            //检查栈是否溢出,未定义则关闭
10 #define RT_DEBUG                        //定义该宏开启debug模式,未定义则关闭
11 #define RT_DEBUG_INIT 0                    //开启debug模式时:该宏定义为0时表示关闭打印组件初始化信息,定义为1时表示启用
12 #define RT_DEBUG_THREAD 0                //开启debug模式时:该宏定义为0时表示关闭打印线程切换信息,定义为1时表示启用
13 #define RT_USING_HOOK                    //定义该宏表示开启钩子函数的使用,未定义则关闭
14 #define IDLE_THREAD_STACK_SIZE 256        //定义了空闲线程的栈大小
15  
16 /* 
17 线程间同步与通信部分,
18 该部分会使用到的对象有信号量、
19 互斥量、事件、邮箱、消息队列、信号等 
20 */
21 #define RT_USING_SEMAPHORE                //定义该宏可开启信号量的使用,未定义则关闭
22 #define RT_USING_MUTEX                    //定义该宏可开启互斥量的使用,未定义则关闭
23 #define RT_USING_EVENT                    //定义该宏可开启事件集的使用,未定义则关闭
24 #define RT_USING_MAILBOX                //定义该宏可开启邮箱的使用,未定义则关闭
25 #define RT_USING_MESSAGEQUEUE            //定义该宏可开启消息队列的使用,未定义则关闭
26 #define RT_USING_SIGNALS                //定义该宏可开启信号的使用,未定义则关 闭
27  
28 /* 内存管理部分 */
29 #define RT_USING_MEMPOOL                //定义该宏可开启静态内存池的使用,未定义则关闭
30 #define RT_USING_MEMHEAP                //定义该宏可开启两个或以上内存堆拼接的使用,未定义则关闭
31 #define RT_USING_SMALL_MEM                //定义该宏可开启开启小内存管理算法,未定义则关闭
32 //#define RT_USING_SLAB                    //定义该宏可开启SLAB内存管理算法,未定义则关闭
33 #define RT_USING_HEAP                    //定义该宏可开启堆的使用,未定义则关闭
34  
35 /* 内核设备对象 */
36 #define RT_USING_DEVICE                    //表示开启了系统设备的使用,使用设备驱动
37 #define RT_USING_CONSOLE                //定义该宏可开启系统控制台设备的使用,未定义则关闭
38 #define RT_CONSOLEBUF_SIZE 128            //定义控制台设备的缓冲区大小
39 #define RT_CONSOLE_DEVICE_NAME "uart1"    //控制台设备的名称
40  
41 /* 自动初始化方式 */
42 #define RT_USING_COMPONENTS_INIT        //定义该宏开启自动初始化机制,未定义则关闭
43 #define RT_USING_USER_MAIN                //定义该宏  启设置应用入口为main函数
44 #define RT_MAIN_THREAD_STACK_SIZE 2048    //定义main线程的栈大小
45  
46 /* FinSH */
47 #define RT_USING_FINSH                    //定义该宏可开启系统FinSH调试工具的使用,未定义则关闭
48 #ifdef RT_USING_FINSH
49 #define FINSH_THREAD_NAME "tshell"        //开启系统FinSH时:将该线程名称定义为tshell
50 #define FINSH_USING_HISTORY                //开启系统FinSH时:使用历史命令
51 #define FINSH_HISTORY_LINES 5            //开启系统FinSH时:对历史命令行数的定义
52 #define FINSH_USING_SYMTAB                //开启系统FinSH时:定义该宏开启使用Tab键,未定义则关闭
53 #define FINSH_THREAD_PRIORITY 20        //开启系统FinSH时:定义该线程的优先级
54 #define FINSH_THREAD_STACK_SIZE 4096    //开启系统FinSH时:定义该线程的栈大小
55 #define FINSH_CMD_SIZE 80                //开启系统FinSH时:定义命令字符长度
56 #define FINSH_USING_MSH                    //开启系统FinSH时:定义该宏开启MSH功能
57 #define FINSH_USING_MSH_DEFAULT            //开启系统FinSH时:开启MSH功能时,定义该宏默认使用MSH功能
58 #define FINSH_USING_MSH_ONLY            //开启系统FinSH时:定义该宏,仅使用MSH功能
59 #endif
60  
61 /* 关于 MCU */
62 #define STM32L475VE                        //定义该工程使用的MCU为STM32L475VE
63 #define RT_HSE_VALUE 8000000            //定义时钟源频率
64 #define RT_USING_LED                    //定义该宏开启LED的使用
65 #define RT_USING_SERIAL                    //定义该宏开启串口的使用
66 #define BSP_USING_UART1                    //定义该宏开启UART1的使用
67  
68  
69 #endif/* __RTTHREAD_CFG_H__ */

 

 五、修改board.c

1、添加系统时钟初始函数,这里使用HAl库将系统初始化为80MHz:

1 void _Error_Handler(char *file, int line)
 2 {
 3     /* USER CODE BEGIN Error_Handler_Debug */
 4       /* User can add his own implementation to report the HAL error return state */
 5       while(1)
 6       {
 7       }
 8       /* USER CODE END Error_Handler_Debug */
 9 }
10  
11 /*
12   * @brief System Clock Configuration
13   * @retval None
14 */
15 void SystemClock_Config(void)
16 {
17     RCC_OscInitTypeDef RCC_OscInitStruct;
18       RCC_ClkInitTypeDef RCC_ClkInitStruct;
19  
20     __HAL_RCC_PWR_CLK_ENABLE();
21     
22     /* Initializes the CPU, AHB and APB busses clocks */
23       RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
24       RCC_OscInitStruct.HSEState = RCC_HSE_ON;
25       RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
26       RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
27     RCC_OscInitStruct.PLL.PLLM = 1;
28       RCC_OscInitStruct.PLL.PLLN = 20;
29       RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
30       RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
31       RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
32       if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
33       {
34         _Error_Handler(__FILE__, __LINE__);
35       }
36  
37     /* Initializes the CPU, AHB and APB busses clocks */
38       RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1| RCC_CLOCKTYPE_PCLK2;
39       RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
40       RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
41       RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
42       RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
43  
44       if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
45       {
46         _Error_Handler(__FILE__, __LINE__);
47       }
48  
49     /* Configure the main internal regulator output voltage */
50       if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
51       {
52         _Error_Handler(__FILE__, __LINE__);
53       }
54 }

 

2、修改Tick相关函数,初始化SysTick,:

1 /**
 2  *  HAL adaptation
 3  */
 4 HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
 5 {
 6     /* Return function status */
 7     return HAL_OK;
 8 }
 9  
10 uint32_t HAL_GetTick(void)
11 {
12     return rt_tick_get() * 1000 / RT_TICK_PER_SECOND;
13 }
14  
15 void HAL_SuspendTick(void)
16 {
17     return ;
18 }
19  
20 void HAL_ResumeTick(void)
21 {
22     return ;
23 }
24  
25 void HAL_Delay(__IO uint32_t Delay)
26 {
27     return ;
28 }
29  
30 void SysTick_Handler(void)
31 {
32     /* enter interrupt */
33     rt_interrupt_enter();
34  
35     rt_tick_increase();
36  
37     /* leave interrupt */
38     rt_interrupt_leave();
39 }

 

3、修改rt_hw_board_init函数,初始化SysTick:

1 /**
 2  * This function will initial your board.
 3  */
 4 void rt_hw_board_init()
 5 {    
 6     /* 使用HAL库,初始化HAL */
 7     HAL_Init();
 8  
 9     /* 初始化系统时钟和SysTick */
10     SystemClock_Config();
11     SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
12  
13     /* 硬件 BSP 初始化统统放在这里,比如 LED,串口,LCD 等 */
14 #ifdef RT_USING_LED
15     led_init();
16 #endif
17  
18 #ifdef RT_USING_SERIAL
19     stm32_hw_usart_init();
20 #endif
21     
22     
23     /* Call components board initial (use INIT_BOARD_EXPORT()) */
24 #ifdef RT_USING_COMPONENTS_INIT
25     rt_components_board_init();
26 #endif
27     
28 #if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)
29     rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
30 #endif
31     
32 #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
33     rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
34 #endif

 

4、增加RT-Thread堆空间大小,因为finsh需要和其他线程需要,我这里先修改为16K,后期使用具体看MCU的RAM和实际需要调节:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_11

六、新建MDK工程

1、新建一个BSP文件夹用于放自己写的外设驱动:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_12

2、然后新建一个MDK工程,往工程里面加入相关文件,如下:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_串口_13

这里我们重点关注几个文件夹: BSP、RT-Thread_bsp、RT-Thread_device_drviers、RT-Thread_libcpu。

(1)BSP,放用户自己写的驱动,如LED驱动、LCD驱动等:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_14

(2) RT-Thread_bsp:放RT-Thread做的BSP、board.c、board.h、rt_config.h,放RT-Thread做的BSP可以是串口驱动等:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_15

(3)RT-Thread_device_drviers:放RT-Thread的设备驱动框架,如串口、I2C、SPI等,我们目前只先用到串口:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_串口_16

(4)RT-Thread_libcpu:放CPU 架构,我们用的是SMT32L4xx,因此这里是cortex-m4,在添加cpuport.c和context_rvds.S:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_17

3、添加相关头文件到工程:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_18

 

七、修改main.c

1、将main函数修改如下:

1 #include "main.h"
 2 #include "board.h"
 3 #include "rtthread.h"
 4  
 5  
 6 int main(void)
 7 {    
 8     u32 count = 1;
 9     
10     while(count > 0)
11     {
12         LED_R(0);
13         rt_kprintf("led on, count: %d\r\n", count);
14         rt_thread_mdelay(500);
15         LED_R(1);
16         rt_thread_mdelay(500);
17         rt_kprintf("led off\r\n");
18         count++;
19     }
20  
21     return 0;
22 }

 

2、保存工程,然后编译工程,下载到开发板,观察LED灯情况,会亮500ms后再灭500ms,同时串口打印相关信息:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_19

3、测试finsh功能:

为更加直观看finsh相关功能,将main函数的串口打印代码注释掉,然后重新编译,下载到开发板:

1 #include "main.h"
 2 #include "board.h"
 3 #include "rtthread.h"
 4  
 5  
 6 int main(void)
 7 {    
 8     u32 count = 1;
 9     
10     while(count > 0)
11     {
12         LED_R(0);
13         //rt_kprintf("led on, count: %d\r\n", count);
14         rt_thread_mdelay(500);
15         LED_R(1);
16         rt_thread_mdelay(500);
17         //rt_kprintf("led off\r\n");
18         count++;
19     }
20  
21     return 0;
22 }

 

接着打开串口,打印如下信息:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_20

按tab键:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_21

输入list_thread:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_串口_22

更多finsh的讲解到RT-Thread官方看相关文档。

八、RT-Thread 启动流程

当你拿到一个移植好的 RT-Thread 工程的时候,你去看 main 函数,只能在 main 函数里面看到创建线程和启动线程的代码,硬件初始化,系统初始化,启动调度器等信息都看不到。那是因为 RT-Thread 拓展了 main 函数,在 main 函数之前把这些工作都做好了。

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_23

1、在components.c中有如下代码:

1 /* $Sub$$main 函 数 */
2 int $Sub$$main(void)
3 {
4 rtthread_startup();
5 return 0;
6 }

 

在这里 $Sub$$main 函数仅仅调用了 rtthread_startup() 函数,在 components.c 的代码中找到rtthread_startup() 函数,如下:

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_未定义_24

启动流程如下(图片来源RT-Thread编程指南):

cubemx 移植rtthread没有HAL_UART rtthread移植到stm32_#define_25

启动流程图——来源RT-Thread编程指南

这部分启动代码,大致可以分为四个部分:
(1)初始化与系统相关的硬件;
(2)初始化系统内核对象,例如定时器、调度器、信号;
(3)创建 main 线程,在 main 线程中对各类模块依次进行初始化;
(4)初始化定时器线程、空闲线程,并启动调度器。
rt_hw_board_init() 中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定到这个串口,后续系统运行信息就会从串口打印出来。

 

参考文献:

1、[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》

2、RT-THREAD 编程指南