STM32 —— RT-Thread Nano 移植
STM32 —— RT-Thread Nano 移植
实验目的
主程序采用多任务框架,通过移植 RTOS 系统进行实现,比如RT-thread Nano
实验原理
这里主要是通过对 RT-Thread Nano 系统的移植,实现系统多任务的执行,前面我们已经实现了对 UCOS III 系统的移植,这里的原理相似,可以直接从官网下载代码进行移植,也可以通过 STM32CubeMX 中下载添加 RT-Thread 系统进行移植,这里我们选用后一种方法进行实现
这里我们通过板载灯泡闪烁和获取温度传感器来测试我们是否成功移植
HAL 库方法
安装 RT-Thread 的方法有两种,一种是通过 CubeMX 进行安装,另一种是通过 Keil 进行安装,这里我们分别介绍两种安装方法
CubeMX 安装 Nano Pack
这里我们可以参考官方的教程:基于 CubeMX 移植 RT-Thread Nano
要获取 RT-Thread Nano 软件包,需要在 CubeMX 中添加 https://www.rt-thread.org/download/cube/RealThread.RT-Thread.pdsc
这里我们需要使用在 CubeMX 中添加硬件包同样的方式来添加我们要使用的 RT-Thread Nano 软件包,过程如下:
后面我们选择通过 Url 进行添加我们的软件包
添加成功之后,直接点击 OK 系统就会自动下载好我们需要使用的软件包,这里我们就能够看到多出了一个 RT-Thread 的选项,我们直接选中我们需要使用的软件包,点击安装即可自动安装好
Keil 安装 RT-Thread Nano
我们直接打开 keil 不需要打开任何项目,按照如下步骤进行添加即可:
安装完成如下:
在配置好之后,打开工程项目我们可以将 RT-Thread 添加到程序中:
注意:这里我们选择安装的版本一定要与 CubeMX 中安装的版本相同,不然会由于版本差异导致一些函数报错
CubeMX 项目配置
添加 RT-Thread Nano 软件包
我们需要使用 RT-Thread Nano 软件包,则可以直接在创建项目的时候进行选择添加即可,我们创建好项目之后,按照如下步骤就可以添加我们所使用的软件包,剩下的部分就按照我们需要进行的实验进行配置即可
添加了软件包之后,我们需要在我们的设备中选择启用 RTOS:
RCC 配置
SYS 配置
USART1 配置
I2C 配置
NVIC 配置
时钟配置
引脚配置
代码设计
我们在这里选择使用 CubeMX 安装配置 RT-Thread
注意:这里一定不要重复添加 RT-Thread Nano Pack ,否则会出现重复定义等各种各样的报错无法解决
这里我们选择适用 CubeMX 添加 RT-Thread 比较方便
首先我们要获取温度传感器的数据,需要引入硬件驱动代码,这里我们可以参考我前面的博客:STM32 —— IIC 读取 ATH20(DTH20) 温度传感器
首先我们需要新建一个 app_rt_thread.c 源文件,作为我们 RT-Thread 的代码文件,然后我们需要在当前文件下添加我们的进程代码
我们这里需要引用如下头文件:
#include "rtthread.h"
#include "main.h"
#include "stdio.h"
#include "usart.h"
#include "gpio.h"
#include "AHT20.h"
首先我们这里使用 printf 进行输出,所以这里需要重写 fputc 函数
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);
//等待发送结束
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET){
}
return ch;
}
然后由于我们需要使用 RT-Thread 系统进行多线程运行,所以我们需要设计线程相关代码如下:
struct rt_thread led1_thread; // 定义线程结构体
rt_uint8_t rt_led1_thread_stack[128]; // 设置栈空间
//初始化线程函数
void MX_RT_Thread_Init(void)
{
//初始化LED1线程
rt_thread_init(&led1_thread,"led1",led1_task_entry,RT_NULL,&rt_led1_thread_stack[0],sizeof(rt_led1_thread_stack),3,20);
//开启线程调度
rt_thread_startup(&led1_thread);
}
//主任务
void MX_RT_Thread_Process(void)
{
printf("Hello RT_Thread!!!");
rt_thread_delay(2000);
}
//LED1任务
void led1_task_entry(void *parameter)
{
while(1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_RESET); // 这里使用板载灯泡
rt_thread_delay(500);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_SET);
rt_thread_delay(500);
}
}
这样我们就能够通过 RT-Thread 实现多任务系统,完整 app_rt_thread.c 代码如下:
点击查看完整app_rt_thread.c代码
#include "rtthread.h"
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "AHT20.h"
struct rt_thread led1_thread;
struct rt_thread usart1_thread;
rt_uint8_t rt_led1_thread_stack[128];
rt_uint8_t rt_usart1_thread_stack[256];
void led1_task_entry(void *parameter);
void usart1_task_entry(void *parameter);
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);
//等待发送结束
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET){
}
return ch;
}
//初始化线程函数
void MX_RT_Thread_Init(void)
{
//初始化LED1线程
rt_thread_init(&led1_thread,"led1",led1_task_entry,RT_NULL,&rt_led1_thread_stack[0],sizeof(rt_led1_thread_stack),3,20);
//开启线程调度
rt_thread_startup(&led1_thread);
//初始化USART1线程
rt_thread_init(&usart1_thread,"usart1",usart1_task_entry,RT_NULL,&rt_usart1_thread_stack[0],sizeof(rt_usart1_thread_stack),3,20);
//开启线程调度
rt_thread_startup(&usart1_thread);
}
//主任务
void MX_RT_Thread_Process(void)
{
printf("Hello RT_Thread!!!\r\n");
rt_thread_delay(2000);
}
//LED1任务
void led1_task_entry(void *parameter)
{
while(1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_RESET);
rt_thread_delay(500);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_SET);
rt_thread_delay(500);
}
}
//读取温度任务
void usart1_task_entry(void *parameter)
{
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
uint32_t CT_data[2]={0,0}; //
volatile int c1,t1;
rt_thread_delay(50);
AHT20_Init();
rt_thread_delay(2500);
while(1)
{
AHT20_Read_CTdata_crc(CT_data); //经过CRC校验,读取AHT20的温度和湿度数据 推荐每隔大于1S读一次
c1 = CT_data[0]*1000/1024/1024; //计算得到湿度值c1(放大了10倍)
t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)
printf("正在检测");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
printf("\r\n");
printf("温度:%d%d.%d",t1/100,(t1/10)%10,t1%10); // 这里需要对温度进行计算后才能得到我们需要的温度值
printf("湿度:%d%d.%d",c1/100,(c1/10)%10,c1%10); // 这里同样需要对适度进行计算
printf("\r\n");
printf("等待");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
rt_thread_delay(100);
printf(".");
printf("\r\n");
}
}
然后我们就需要在主函数中进行初始化并调用上述系统进程,这里我们只需要在主函数中引用我们的进程初始化和主任务函数即可:
#include <rtthread.h> // 这里我们还需要引用必要的头文件
extern void MX_RT_Thread_Init(void);
extern void MX_RT_Thread_Process(void);
然后我们只需要在主函数中进入循环前初始化线程,并在主函数中调用主任务函数即可:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
MX_RT_Thread_Init(); // 初始化线程
while (1)
{
MX_RT_Thread_Process(); //执行主任务
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
前面我们已经完成了代码的编写,但是我们还不能够正确执行,我们需要修改一下两处代码之后,才能够正确执行程序:
注意:这里一定要增大我们的 IRAM,并且需要重载为 STM32F103C8 的 MAP 才能够保证程序的栈空间充足,确保程序正常运行
运行测试
接线示例
运行结果
错误解决方法
报错如下:
RTE\RTOS\board.c(47): error: #35: #error directive: "TODO 1: OS Tick Configuration."
解决方法:
将报错的位置使用 #error 的代码行直接注释掉即可
报错如下:
Undefined symbol rt_hw_console_getchar (referrred from shell.o)
解决方案:
着说面我们漏掉了,没有将 rtconfig.h 中 #include "finsh_config.h" 的注释符号去掉,我们直接去掉 #include "finsh_config.h" 前面的注释符号即可
报错如下:
如果在程序运行时,上位机收到如下数据:
thread pri status sp stack size max used left tick error
则说明我们对当前某一进程开的栈空间不足,需要加大栈空间即可
解决方法:
我们直接在代码中增加栈空间即可