基于STM32F103RCT6,请结合正点原子视频观看,源码百度网盘地址在文章末尾
参考材料:"D:\探索者F4 资料盘(A盘)\【正点原子】STM32F103最小系统板资料\6,软件资料\6,软件资料\13,FreeRTOS学习资料\13,FreeRTOS学习资料\FreeRTOS实时内核使用指南-中文.pdf"
我在这里不累述文档里已经有的概念,只是记录核心思想和困难。
任务的函数原型
void ATaskFunction( void *pvParameters )
{
/* 可以像普通函数一样定义变量。用这个函数创建的每个任务实例都有一个属于自己的iVarialbleExample变
量。但如果iVariableExample被定义为static,这一点则不成立 – 这种情况下只存在一个变量,所有的任务实
例将会共享这个变量。 */
int iVariableExample = 0;
/* 任务通常实现在一个死循环中。 */
for( ;; )
{
/* 完成任务功能的代码将放在这里。 */
}
/* 如果任务的具体实现会跳出上面的死循环,则此任务必须在函数运行完之前删除。传入NULL参数表示删除
的是当前任务 */
vTaskDelete( NULL );
}
宏 configSUPPORT_DYNAMIC_ALLOCATION 必须为 1
ps:
pcName包括’\0’结束符。如果传入的字符串长度超 过了这个最大值,字符串将会自动被截断
pxCreatedTask 用于传出任务的句柄。这个句柄将在 API 调用中对 该创建出来的任务进行引用,比如改变任务优先级,或者删除任务。 如果应用程序中不会用到这个任务的句柄,则 pxCreatedTask 可以 被设为 NULL。
如 果 要 使 用 此 函 数 的 话 需 要 将 宏 configSUPPORT_STATIC_ALLOCATION 定义为 1
任务删除函数vTaskDelete()
这里理解一下第二点,如果在TASK1中执行删除TASK2的函数,那么TASK2的堆栈在TASK1中执行时释放(TASK1和TASK2均为动态创建)
在正点原子下载的课程源码中找到xTaskCreate()函数
然后在task.h的257行可以找到这个函数的实例
也可以到官网看,官网的特别详细This page describes the RTOS xTaskCreate() FreeRTOS API function which is part of the RTOS task control API. FreeRTOS is a professional grade, small footprint, open source RTOS for microcontrollers.
pvTaskCode | 指向任务入口函数的指针(即 实现任务的函数名称,请参阅如下示例)。 任务通常 以 无限循环的形式实现;实现任务的函数决不能试图返回 或退出。 但是,任务可以 自我删除。 |
pcName | 任务的描述性名称。主要是为了方便 调试,但也可用于 获取任务句柄。 任务名称的最大长度由 中的 FreeRTOSConfig.hconfigMAX_TASK_NAME_LEN 定义。 |
usStackDepth | 要分配用于 任务堆栈的 字数(不是字节)。例如,如果堆栈的宽度为 16 位,usStackDepth 为 100,则将分配 200 字节用作该任务的堆栈。 再举一例,如果堆栈的宽度为 32 位,usStackDepth 为 400,则将分配 1600 字节用作该任务的堆栈。 堆栈深度与堆栈宽度的乘积不得超过 请参阅常见问题:堆栈应有多大? |
pvParameters | 作为参数传递给创建的任务的一个值。 如果 |
uxPriority | 创建任务执行的优先级 。 包含 MPU 支持的系统可在特权(系统)模式下选择性地创建任务, 方法是在 断言优先级低于 |
pxCreatedTask | 用于将句柄传递至由 xTaskCreate() 函数创建的任务 。 pxCreatedTask 是可选的,可设置为 NULL。 |
返回:如果任务创建成功,则返回 pdPASS。
否则返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
。
这里解释一下第三个参数usStackDepth 如果堆栈的宽度为16位,即每个堆栈元素占2个字节;如果堆栈的宽度为32位,即每个堆栈元素占4个字节
对于给定的堆栈宽度和usStackDepth,可以使用以下公式来计算任务堆栈的大小:
堆栈大小(字节)= 堆栈宽度(字节) x usStackDepth
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint32_t odr;
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
/* get current Output Data Register value */
odr = GPIOx->ODR;
/* Set selected pins that were at low level, and reset ones that were high */
GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);
}
通过操作 ODR
寄存器的位,我们可以改变相应引脚的电平状态,这是由微控制器芯片内部的驱动电路和逻辑实现的。
BSRR 只写寄存器:[color=Red]既能控制管脚为高电平,也能控制管脚为低电平。
对寄存器高 16bit 写1 对应管脚为低电平,对寄存器低16bit写1对应管脚为高电平。写 0 ,无动作
(11条消息) GPIO 配置之ODR, BSRR, BRR 详解_gpiob->odr_byhunpo的博客-CSDN博客
(odr & GPIO_Pin)
:这是一个按位与操作,保留ODR寄存器中对应的GPIO引脚的值的,然后左移GPIO_Pin位将BSRR
中与GPIO_Pin
相应引脚位置为对应的值,当GPIO->ODR为高电平时,对BSRR寄存器高十六位的对应引脚位写1会复位,也就是把该引脚复位为低电平。而(~odr & GPIO_Pin)则把ODR对应引脚的值保留在BSRR低十六位,如果原来ODR里对应引脚位为1是取反为0,则BSRR理赋值后不会对该引脚置位为高电平,然后相与就可以实现对引脚电平的取反了。
贴一下main.c
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 F407电机开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
void task3( void * pvParameters );
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
xTaskCreate((TaskFunction_t ) task3,
(char * ) "task3",
(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIO,
(TaskHandle_t * ) &task3_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{
while(1)
{
printf("task1正在运行!!!\r\n");
LED0_TOGGLE();
vTaskDelay(500);
}
}
/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{
while(1)
{
printf("task2正在运行!!!\r\n");
LED1_TOGGLE();
vTaskDelay(500);
}
}
/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{
uint8_t key = 0;
while(1)
{
printf("task3正在运行!!!\r\n");
key = key_scan(0);
if(key == KEY0_PRES)
{
if(task1_handler != NULL)
{
printf("删除task1任务\r\n");
vTaskDelete(task1_handler);
task1_handler = NULL;
}
}
vTaskDelay(10);
}
}
在这段代码中,通过调用taskENTER_CRITICAL()
和taskEXIT_CRITICAL()
函数,实现了对临界区的保护。
临界区是指在这段代码中需要保证原子性执行的部分。当进入临界区时,中断会被禁止,防止其他任务或中断干扰当前任务的执行。只有当前任务完成临界区的执行后,中断才会重新启用。
进入临界区的作用主要有两个方面:
在这段代码中,进入临界区的目的是保护任务创建过程的原子性。在创建任务的过程中,需要对任务句柄进行赋值,并且要确保任务创建的完整性,避免其他任务的干扰。因此,在进入临界区后,先创建了LED0任务和LED1任务,并删除了开始任务,最后退出临界区。因为TASK1的任务优先级比START_TASK高,所以要禁止中断。否则会出先创建完TASK1后TASK1的任务优先级比开始任务高,执行TASK1任务去了等到阻塞才会回来继续创建函数。
链接:https://pan.baidu.com/s/1RAlmLu6V9p3NCkuGYD_4gA?pwd=rtos
提取码:rtos