使用CubeMX快速创建FreeRTOS工程到蓝桥杯开发板(STM32G431RBT6)
- CubeMX配置
- CubeMX基础工程的配置
- ☆FreeRTOS相关配置
- FreeRTOS配置选项卡的解释
- 软件工程架构与程序设计
- 小综合:☆任务的创建删除、挂起与恢复设计
- cubexMX配置创建任务
- 软件程序设计
- 底层功能驱动函数实现
随着CubeMX软件的不断推广使用,STM32HAL库的使用也不断增加,并且某些系列开发板只支持HAL开发。最近参加完蓝桥杯后,继续深入学习FreeRTOS操作系统,但在移植RTOS的时候,大多数教程都是基于正点原子F系列开发板根据固件库或者HAL例程进行移植,移植过程相对比较复杂,因此特此整理一篇直接通过CubeMX生成的FreeRTOS工程教程到蓝桥杯的STM32G431RB系列的开发板中,方便后续通过蓝桥杯开发板深入学习RTOS操作系统。
- 对于手动移植过程复杂且繁琐,对新手不友好。如有需要手动移植,可参照以下文章:
1. FreeRTOS移植到STM32
2. FreeRTOS移植stm32详细步骤介绍
CubeMX配置
对于CubeMX创建基础工程这里仅进行简单概述,详细步骤可参考:
1. STM32 CubeMX新建工程+GPIO的研究
2. Cubemx新建工程引脚配置与点亮LED
CubeMX基础工程的配置
使能外部高速时钟:
配置LED与按键引脚
配置开启调试串口
工程相关配置
☆FreeRTOS相关配置
更改/配置时钟源:
- 在 SYS 选项里,将 Debug 设为 Serial Wire,并且将 Timebase Source 设为TIM6(其它定时器也行)。
- 裸机的时钟源默认是 SysTick,但是开启FreeRTOS后,FreeRTOS会占用SysTick(用来生成1ms 定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。
使能FREERTOS选项
将 Interface 配置为CMSIS_V1,V2的内核版本更高,功能更多,在大多数情况下V1版本的内核完全够用
创建第二个任务
至此,FreeRTOS多任务工程已配置完毕,可直接生成工程代码!!!
FreeRTOS配置选项卡的解释
FreeRTOS 各配置选项卡的解释
- Events:事件相关的创建
- Task and Queues: 任务与队列的创建
- Timers and Semaphores: 定时器和信号量的创建
- Mutexes: 互斥量的创建
- FreeRTOS Heap Usage: 用于查看堆使用情况
- config parameters: 内核参数设置,用户根据自己的实际应用来裁剪定制FreeRTOS 内核
- Include parameters: FreeRTOS 部分函数的使能
- User Constants: 相关宏的定义,可以自建一些常量在工程中使用
- Advanced settings:高级设置
内核配置、函数使能的一些翻译
查看用户堆的使用情况
FreeRTOS部分函数使能配置
内核参数设置,用户可根据自己的实际应用来裁剪定制FreeRTOS 内核
内核参数的理解内容非常多,可以参考以下文章:FreeRTOS内核配置说明
软件工程架构与程序设计
软件工程架构
主函数中初始化RTOS并且开启内核任务调度:
在app_freertos.c中创建任务,并实现对任务的具体实现
程序设计
通过CubeMX生成的FreeRTOS工程创建了2个任务,通过程序设计实现对两个LED灯分别以不同的时间周期进行闪烁。
LED翻转功能函数
void led_toggle(uint8_t led)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8<<(led-1));
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
任务的具体实现
//任务1控制LED1小灯每秒闪烁一次
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
led_toggle(1);
osDelay(250);
}
/* USER CODE END StartDefaultTask */
}
//任务2控制LED2小灯每500ms闪烁一次
void StartTask02(void const * argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
led_toggle(2);
osDelay(500);
}
/* USER CODE END StartTask02 */
}
小综合:☆任务的创建删除、挂起与恢复设计
通过对FreeRTOS的理论基础知识及其系统的移植,通过设计按键控制任务的创建与删除,挂起与恢复巩固系统的移植与相关理论知识。
- 在此之前需掌握RTOS任务的调度、任务的状态以及任务的创建与删除相关理论基础知识:RTOS理论基础知识快速入门
- 同时需要掌握通过CubeMX移植FreeRTOS工程,因为该实验建立在上述移植的工程上进行修改
设计要求描述:
创建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:
taskLED1:间隔 500ms 闪烁 LED1;
taskLED2:间隔 1000ms 闪烁 LED2;
taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1 ;
taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2
cubexMX配置创建任务
通过设计要求分析,在tasks选项中创建四个任务,分别为taskLED01,taskLED02,taskKEY01,以及taskKEY02,将任务优先级均设置为相同的Normal优先级,代码创建定义设置为若定义,方便后续用户在user.c内对函数进行重写,其中创建方式如下图所示。
注意:由于创建的任务过多,会使得堆空间不够,因此这里将Interface设置为CMSIS_V2版本。
软件程序设计
任务一和任务二分别控制LED小灯以不同的时间周期进行闪烁
void taskLED01(void * argument)
{
uint task1_num = 0;
while(1)
{
led_toggle(1);
osDelay(250);
task1_num++;
printf("task1 num:%d\r\n",task1_num);
}
}
void taskLED02(void * argument)
{
uint task2_num = 0;
while(1)
{
led_toggle(2);
osDelay(500);
task2_num++;
printf("task2 num:%d\r\n",task2_num);
}
}
按键一任务控制LED1任务的创建与删除
void taskKEY01(void * argument)
{
while(1)
{
//按下
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == 0)
{
//消抖
osDelay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == 0)
{
//等待抬起
while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == 0);
printf("key1 按下\r\n");
if(taskLED1Handle != NULL)
{
//删除任务1
osThreadTerminate(taskLED1Handle);
taskLED1Handle = NULL;
led_show(1,0);
printf("taskLED01 删除成功\r\n");
}
else
{
//创建任务
taskLED1Handle = osThreadNew(taskLED01, NULL, &taskLED1_attributes);
if(taskLED1Handle != NULL)
printf("taskLED01 创建成功\r\n");
else
printf("taskLED01 创建失败\r\n");
}
}
}
osDelay(10);
}
}
按键二任务控制LED2任务的挂起与恢复
void taskKEY02(void * argument)
{
static uchar key2_flag = 0;
while(1)
{
//按下
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == 0)
{
//消抖
osDelay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == 0)
{
//等待抬起
while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == 0);
printf("key2 按下\r\n");
//挂起
if(key2_flag == 0)
{
osThreadSuspend(taskLED2Handle);
printf("任务2已挂起\r\n");
}
//恢复
else
{
osThreadResume(taskLED2Handle);
printf("任务2已恢复\r\n");
}
key2_flag = !key2_flag;
}
}
osDelay(10);
}
}
底层功能驱动函数实现
LED功能函数的实现
/*****************LED与按键的底层驱动函数*****************************/
/*
功能: LED翻转
参数: LED编号 1~8
*/
void led_toggle(uint8_t led)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8<<(led-1));
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
/*
功能: LED亮灭控制
参数1: LED编号 1~8
参数2: LED亮灭模式 1:亮 0:灭
*/
void led_show(uint8_t led, uchar mode)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
if(mode)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8<<(led-1),GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8<<(led-1),GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
/*
功能: LED亮灭控制
参数: 无
*/
void led_offAll(void)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
串口重定向
#include "stdio.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1] = {ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
按键扫描功能函数的实现
/*
功能: 按键扫描
参数: 无
返回值: 对应按键按下宏
*/
//头文件宏定义
/* 读取KEY引脚 */
#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
/* KEY按下 */
#define KEY1_PRES 1
#define KEY2_PRES 2
#define KEY3_PRES 3
#define KEY4_PRES 4
uint8_t key_scan(void)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
/* 按键松开标志为1, 且有任意一个按键按下了 */
if (key_up && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0))
{
//消抖
osDelay(10);
key_up = 0;
if (KEY1 == 0)
keyval = KEY1_PRES;
else if (KEY2 == 0)
keyval = KEY2_PRES;
else if (KEY3 == 0)
keyval = KEY3_PRES;
else if (KEY4 == 0)
keyval = KEY4_PRES;
}
/* 没有任何按键按下, 标记按键松开 */
else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1)
{
key_up = 1;
}
return keyval;
}