STM32Cube + FreeRTOS 快速上手指南
简介
FreeRTOS 是一个 RTOS 类的嵌入式实时操作系统,掌握 FreeRTOS 是从单片机裸机编程过渡到更复杂的嵌入式Linux开发的重要一步。本文总结了笔者在学习FreeRTOS过程中的经验和见解,若有任何错误或建议,欢迎随时指正。
1. 操作系统简介
单片机程序设计分为两种,主要可以分为两大类:裸机编程和基于实时操作系统的编程。我们参考一些百问网的内容来进行讲解。
1.1. 裸机程序
裸机程序的设计模式可以分为:轮询、前后台、定时器驱动、基于状态机
1.1.1. 轮询
int main()
{
while (1)
{
task1();
task2();
task3();
}
}
使用轮询模式编写程序看起来很简单,但是要求while 循环里调用到的函数要执行得非常快,在复杂场景里反而增加了编程难度。
1.1.2. 前后台
int main()
{
// 后台程序
while (1)
{
Backend();
}
}
// 前台程序
void EXTIx_IRQHandler(void)
{
Reception();
}
前后台是一种常见的架构模式,例如在平衡小车中,采用MPU6050每5ms产生的INT中断信号来触发前台任务处理,确保即时响应传感器数据的变化。与此同时,后台任务通过while(1)
运行,负责诸如屏幕更新等不需要严格实时性的操作。
1.1.3. 定时器驱动
定时器驱动模式,是前后台模式的一种,可以按照不用的频率执行各种函数。
int main(void)
{
// 后台程序
while (1)
{
Backend(); // 主循环可以在这里添加其他任务
}
}
// 前台程序
void TIMx_IRQHandler(void)
{
static int cnt = 0;
if (TIM_GetITStatus(TIMx, TIM_IT_Update) != RESET)
{
if (cnt % 2 == 0)
Reception1();
else if (cnt % 5 == 0)
Reception2();
cnt++;
TIM_ClearITPendingBit(TIMx, TIM_IT_Update); // 清除中断标志位
}
}
1.1.4. 基于状态机
无论使用前面的哪种设计模式,都会退化到轮询模式的缺点:函数相互之间有影响。可以使用状态机来解决这个缺点。
使用状态机模式,可以解决裸机程序的难题:假设有A、B 两个都很耗时的函数,怎样降低它们相互之间的影响。但是很多场景里,函数A、B 并不容易拆分为多个状态,并且这些状态执行的时间并不好控制。
所以这并不是最优的解决方法,需要使用多任务系统。
1.2. 多任务系统
多任务系统会依次给这些任务分配时间:你执行一会,我执行一会,如此循环。只要切换的间隔足够短,用户会“感觉这些任务在同时运行”。
2. 创建工程模板
接下来以STM32F103RCT6为例,创建FreeRTOS工程模板:
2.1. 配置STM32CubeMX
生成代码
2.2. 添加延时、外设代码
.\Drivers
新建文件夹,取名BSP
(或自拟);- 新建以下文件夹和对应的
.c
和.h
文件;
- delay.h
#include "main.h"
#ifndef __DELAY_H
#define __DELAY_H
uint64_t system_get_ns(void); // 获得系统时间
void delay_us(uint32_t nus); /* 延时nus */
void delay_ms(uint16_t nms); /* 延时nms */
#endif
- delay.c
#include "delay.h"
/**
* @brief 获得系统时间(单位ns)
* @param 无
* @retval 系统时间(单位ns)
*/
uint64_t system_get_ns(void)
{
extern TIM_HandleTypeDef htim6;
TIM_HandleTypeDef *hHalTim = &htim6;
uint64_t ns = HAL_GetTick(); // 获取系统启动以来的毫秒计数值
uint64_t cnt;
uint64_t reload;
cnt = __HAL_TIM_GET_COUNTER(hHalTim);
reload = __HAL_TIM_GET_AUTORELOAD(hHalTim);
ns *= 1000000;
ns += cnt * 1000000 / reload;
return ns;
}
/**
* @brief 延时nus
* @param nus: 要延时的us数.
* @retval 无
*/
void delay_us(uint32_t nus)
{
extern TIM_HandleTypeDef htim6;
TIM_HandleTypeDef *hHalTim = &htim6;
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = __HAL_TIM_GET_AUTORELOAD(hHalTim);
ticks = nus * reload / (1000); /* 假设reload对应1ms */
told = __HAL_TIM_GET_COUNTER(hHalTim);
while (1)
{
tnow = __HAL_TIM_GET_COUNTER(hHalTim);
if (tnow != told)
{
if (tnow > told)
{
tcnt += tnow - told;
}
else
{
tcnt += reload - told + tnow;
}
told = tnow;
if (tcnt >= ticks)
{
break;
}
}
}
}
// 延时nms
// nms:要延时的ms数
void delay_ms(uint16_t nms)
{
uint32_t i;
for (i = 0; i < nms; i++)
delay_us(1000);
}
LED部分代码可忽略,在STM32CubeMX中配置相关引脚输出即可。
2.3. 添加路径,编译验证
打开freertos.c
添加头文件
#include "delay.h"
#include "led.h"
找到函数StartDefaultTask(void *argument)
,添加以下内容:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, 0);
delay_ms(500);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, 1);
delay_ms(500);
编译下载验证
至此,我们的工程模板创建完毕。