目录
在Int目录下创建Key.c、Key.h、Led.c、Led.h四个文件
在项目根目录下的Core/Inc目录下创建FreeRTOS_demo.h
在项目对象中添加Int组 ,并在该组下添加Key.c和Led.c
在Application/User/Core组下添加FreeRTOS_demo.c
切换后可以看到KEIL UVISION PROJECT,如下
动态创建,堆栈是在FreeRTOS管理的堆内存里,注意任务不要重复创建。
xxxxx_STACK_SIZE 128
uxTaskGetStackHighWaterMark()获取指定任务的任务栈的历史剩余最小值,根据这个结果适当调整启动任务的大小。
实验目标
学会 xTaskCreate( ) 和 vTaskDelete( ) 的使用:
- start_task:用来创建其他的三个任务。
- task1:实现LED1每500ms闪烁一次。
- task2:实现LED2每500ms闪烁一次。
- task3:判断按键KEY1是否按下,按下则删掉task1。
CubeMX环境准备
SysMode配置
HAL和FreeRTOS都依赖SysTick,保险起见,所有项目都将HAL库的时钟源替换为TIM7。
RCC配置
LED1引脚配置
LED2引脚配置
KEY1引脚配置
串口USART1配置
NVIC配置
将TIM7调整为HAL时钟源后,其中断默认开启,且不能关闭,为了避免使用HAL库时钟源时被FreeRTOS调度中断导致卡死,TIM7的中断优先级配置为1,下文同理。
项目管理
代码生成配置
生成代码
Keil配置
打开项目:
生成代码后,在弹出的串口选择“Open Project”,用Keil打开项目
或者点击“Open Folder”打开目录
也可以直接在文件资源管理器中找到CubeMX中指定的项目文件路径。
双击MDK-ARM目录下后缀为.uvprojx的文件即可在Keil中打开生成的项目
配置使用微库
我们要重定向printf(),需要调用微库实现。
配置每次烧录后“复位并运行”
打开Target选项窗口
打开ST-Link Debugger的配置
配置每次烧录后“复位并运行”
确定
OK
FreeRTOS移植
配置即可,需要注意的是,最后一步建议将时钟源由SysTick替换为其它定时器,此处可以不做。
移植配置完成后的包含目录如下
工程对象窗口如下
编译
编译后出现如下结果则移植成功
创建外设及测试程序文件
在项目根目录下创建Int目录
在Int目录下创建Key.c、Key.h、Led.c、Led.h四个文件
在项目根目录下的Core/Inc目录下创建FreeRTOS_demo.h
在Core/Src目录下创建FreeRTOS_demo.c
在Keil中将Int添加为头文件包含目录
确认即可
在项目对象中添加Int组 ,并在该组下添加Key.c和Led.c
确认即可
在Application/User/Core组下添加FreeRTOS_demo.c
项目导入Keil Assistant
通过Code打开工作目录
进入项目根目录下的MDK-ARM目录,右键空白处
选择VSCode配置文件
确保该配置下包含Keil Assistant插件。
切换后可以看到KEIL UVISION PROJECT,如下
导入项目
切换配置文件后稍待片刻点击KEIL UVISION PROJECT标签即可看到以下内容
如果项目没有导入,可以多次点击KEIL UVISION PROJECT标签,若此法不奏效,可以手动导入,步骤如下。
自动导入失败时手动导入
单击导入项目按钮
在弹出的窗口中选择打开项目文件即可
此时会提示是否切换工作区,选择OK即可
展开项目对象列表
有时某些项目对象未被加载,如Int/,此时可以在Keil中重新编译项目即可加载。
在项目中显示头文件
在FreeRTOS_demo.c中写入以下内容
#include "FreeRTOS_demo.h"
Key.c写入内容
#include "Key.h"
Led.c写入内容
#include "LED.h"
重新编译
展开源文件可以看到引用的头文件列表
Led.h和Key.h同理。
处理“without a newline”警告
上述警告是因为编译器要求每行以\n结尾,因此,文件末尾要有空行
添加空行并重新编译,即可消除警告。
重定向printf
usart.h代码清单
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
要注意,用户自定义的头文件应该在USER CODE BEGIN Includes注释标签对之间,这样在CubeMX重新生成代码时,这部分内容才不会被清除。
usart.c代码清单
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return 0;
}
/* USER CODE END 0 */
同理,重写的fputc()函数代码应置于USER CODE BEGIN标签之间。
外设文件代码清单
Key.h
#ifndef __KEY_H
#define __KEY_H
#include "main.h"
#define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)
/* 读取KEY1引脚状态(上拉输入) */
#define KEY1_PRESS 1
uint8_t Key_Detect(void);
#endif
key.c
#include "Key.h"
/**
* @description: 检测按键
* @return {*} 按下的按键值
*/
uint8_t Key_Detect(void)
{
uint8_t res = 0;
if (KEY1 == GPIO_PIN_RESET)
{
HAL_Delay(10); /* 去抖动 */
/* 按这个顺序,如果多个按键同时按,优先级:KEY4>KEY3>KEY2>KEY1 */
if (KEY1 == GPIO_PIN_RESET) res = KEY1_PRESS;
}
return res;
}
Led.h
#ifndef __LED_H
#define __LED_H
#include "gpio.h"
#define LED uint16_t
void LED_Turn_On(LED led);
void LED_Turn_Off(LED led);
void LED_Toggle(LED led);
void LED_Turn_Off_All(LED led[], uint8_t len);
#endif
Led.c
#include "Led.h"
/**
* @description: 点亮LED
* @param {LED} led
*/
void LED_Turn_On(LED led)
{
HAL_GPIO_WritePin(GPIOA, led, GPIO_PIN_RESET);
}
/**
* @description: 熄灭LED
* @param {LED} led
*/
void LED_Turn_Off(LED led)
{
HAL_GPIO_WritePin(GPIOA, led, GPIO_PIN_SET);
}
/**
* @description: 翻转LED的状态
* @param {LED} led
*/
void LED_Toggle(LED led)
{
HAL_GPIO_TogglePin(GPIOA, led);
}
/**
* @description: 关闭所有LED
* @param {LED} led
* @param {uint8_t} len
* @return {*}
*/
void LED_Turn_Off_All(LED led[], uint8_t len)
{
uint8_t i;
for (i = 0; i < len; i++)
{
LED_Turn_Off(led[i]);
}
}
FreeRTOSConfig.h代码清单
#define configSUPPORT_DYNAMIC_ALLOCATION 1
实际上,配置项configSUPPORT_DYNAMIC_ALLOCATION的默认值为1,上述代码可以省略。在FreeRTOS.h的959-962行有如下代码
#ifndef configSUPPORT_DYNAMIC_ALLOCATION
/* Defaults to 1 for backward compatibility. */
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#endif
由此可知,configSUPPORT_DYNAMIC_ALLOCATION默认值为1。
FreeRTOS_demo.h代码清单
#ifndef __FREERTOS_DEMO_H__
#define __FREERTOS_DEMO_H__
void FreeRTOS_Start(void);
#endif
FreeRTOS_demo.c代码清单
引入头文件
#include "FreeRTOS_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "Key.h"
#include "Led.h"
#include <stdio.h>
任务设置
/* 启动任务函数 */
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_DEPTH 128
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);
/* Task1 任务 配置 */
#define TASK1_PRIORITY 2
#define TASK1_STACK_DEPTH 128
TaskHandle_t task1_handler;
void Task1(void *pvParameters);
/* Task2 任务 配置 */
#define TASK2_PRIORITY 3
#define TASK2_STACK_DEPTH 128
TaskHandle_t task2_handler;
void Task2(void *pvParameters);
/* Task3 任务 配置 */
#define TASK3_PRIORITY 4
#define TASK3_STACK_DEPTH 128
TaskHandle_t task3_handler;
void Task3(void *pvParameters);
入口函数
/**
* @description: FreeRTOS入口函数:创建任务函数并开始调度
* @return {*}
*/
void FreeRTOS_Start(void)
{
xTaskCreate((TaskFunction_t)Start_Task,
(char *)"Start_Task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handler);
vTaskStartScheduler();
}
启动任务函数
void Start_Task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) Task1,
(char * ) "Task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) Task2,
(char * ) "Task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &task2_handler );
xTaskCreate((TaskFunction_t ) Task3,
(char * ) "Task2",
(configSTACK_DEPTH_TYPE ) TASK3_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIORITY,
(TaskHandle_t * ) &task3_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
task1函数
/**
* @description: LED1每500ms翻转一次
* @param {void *} pvParameters
* @return {*}
*/
void Task1(void * pvParameters)
{
while(1)
{
printf("task1运行....\r\n");
LED_Toggle(LED1_Pin);
vTaskDelay(500);
}
}
task2函数
/**
* @description: LED2每500ms翻转一次
* @param {void *} pvParameters
* @return {*}
*/
void Task2(void * pvParameters)
{
while(1)
{
printf("task2运行....\r\n");
LED_Toggle(LED2_Pin);
vTaskDelay(500);
}
}
task3函数
/**
* @description: 按下KEY1删除task1
* @param {void *} pvParameters
* @return {*}
*/
void Task3(void * pvParameters)
{
uint8_t key = 0;
while(1)
{
printf("task3正在运行...\r\n");
key = Key_Detect();
if(key == KEY1_PRESS)
{
if(task1_handler != NULL)
{
printf("删除task1任务...\r\n");
vTaskDelete(task1_handler);
task1_handler = NULL;
}
}
vTaskDelay(10);
}
}
main.c代码清单
引入头文件
/* USER CODE BEGIN Includes */
#include "FreeRTOS_demo.h"
/* USER CODE END Includes */
调用入口函数
/* USER CODE BEGIN 2 */
FreeRTOS_Start();
/* USER CODE END 2 */
测试
编译并烧录后,可以看到LED1和LED2同时闪烁
打开串口工具可以看到以下内容
运行日志
task1和task2约半秒执行一次,task3约10ms执行一次,每隔半秒可以看到task2和task1运行日志,如下。
按下KEY1,可以看到LED1不再闪烁
此外,task1不再输出日志
代码逻辑
主体代码逻辑,首先启动任务task---创建任务开始调度---创建task1会抢占task,执行task1---在task1里进行阻塞,在阻塞过程会让出cpu---task继续执行---创建task2---task2抢占task---task2也有延迟---task继续执行---创建task3---task3同样有延迟---task继续执行---删除task---task3因为延迟低优先级高会先刷屏---然后等task1和task2阻塞结束后,根据优先级task2比task1高先执行task2---再执行task1---按下按键---删除task1---只剩下task2和task3运行。