14.1 硬件初始化
板级初始化->所有的初始化函数都在这里运行
/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* LED初始化 */
LED_GPIO_Init();
/* 串口初始化 */
USART_Config();
}
在硬件初始化好之后,对硬件进行测试:
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* LED初始化 */
LED_GPIO_Init();
LED1_ON;
/* 程序停止在这里 */
while(1);
/* 串口初始化 */
USART_Config();
}
结果:如果LED点亮,说明程序没有问题。
14.2 创建单任务-SRAM静态内存
这里,我们创建一个单任务,任务使用的栈和任务控制块都使用静态内存,即预先定义好的全局变量,这些预先定义好的全局变量都存在内部的 SRAM 中。
14.2.1 定义任务函数
任务实际上就是一个无限循环且不带返回值的 C 函数。目前,我们创建一个这样的任务,让开发板上面的 LED 灯以 500ms 的频率闪烁。
/**********************************************************************
* @ 函数名 : LED_Task
* @ 功能说明: LED_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void LED_Task(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500); /* 延时500个tick */
printf("LED_Task Running,LED1_ON\r\n");
LED1_OFF;
vTaskDelay(500); /* 延时500个tick */
printf("LED_Task Running,LED1_OFF\r\n");
}
}
- 任务必须是一个死循环,否则任务将通过 LR 返回,如果 LR 指向了非法的内存就会产生 HardFault_Handler,而 FreeRTOS 指向一个死循环,那么任务返回之后就在死循环中执行,这样子的任务是不安全的,所以避免这种情况,任务一般都是死循环并且无返回值的。我们的 AppTaskCreate 任务,执行一次之后就进行删除,则不影响系统运行,所以,只执行一次的任务在执行完毕要记得及时删除。
- 任务里面的延时函数必须使用 FreeRTOS 里面提供的延时函数,并不能使用我们裸机编程中的那种延时。这两种的延时的区别是 FreeRTOS 里面的延时是阻塞延时,即调用 vTaskDelay()函数的时候,当前任务会被挂起,调度器会切换到其它就绪的任务,从而实现多任务。如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务。
14.2.2 空闲任务与定时器任务堆栈函数实现
使用了静态创建任务的时候, configSUPPORT_STATIC_ALLOCATION 这个宏定 义 必 须为 1 (在 FreeRTOSConfig.h 文 件 中 ) , 并且 我 们需 要 实 现两 个 函数 :vApplicationGetIdleTaskMemory()与 vApplicationGetTimerTaskMemory(),这两个函数是用户设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而不能是动态分配。
/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;
/**
**********************************************************************
* @brief 获取空闲任务的任务堆栈和任务控制块内存
* ppxTimerTaskTCBBuffer : 任务控制块内存
* ppxTimerTaskStackBuffer : 任务堆栈内存
* pulTimerTaskStackSize : 任务堆栈大小
* @author fire
* @version V1.0
* @date 2018-xx-xx
**********************************************************************
*/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &Idle_Task_TCB;/* 任务控制块内存 */
*ppxIdleTaskStackBuffer = Idle_Task_Stack;/* 任务堆栈内存 */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}
/**
*********************************************************************
* @brief 获取定时器任务的任务堆栈和任务控制块内存
* ppxTimerTaskTCBBuffer : 任务控制块内存
* ppxTimerTaskStackBuffer : 任务堆栈内存
* pulTimerTaskStackSize : 任务堆栈大小
* @author fire
* @version V1.0
* @date 2018-xx-xx
**********************************************************************
*/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer = &Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer = Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}
14.2.3 定义任务栈
在freeRTOS中,任务独立。静态创建需要创建一个栈,也就是全局变量。栈运用的是RAM,能创建多少任务和RAM是有关的。
/* AppTaskCreate 任务任务堆栈 */
static StackType_t AppTaskCreate_Stack[128];
/* LED 任务堆栈 */
static StackType_t LED_Task_Stack[128];
在大多数系统中需要做栈空间地址对齐,在 FreeRTOS 中是以 8 字节大小对齐,并且会检查堆栈是否已经对齐,其中 portBYTE_ALIGNMENT 是在 portmacro.h 里面定义的一个宏,其值为 8,就是配置为按 8 字节对齐,当然用户可以选择按 1、 2、 4、 8、 16、 32 等字节对齐,目前默认为 8。
注意:实际就是设置对应的掩码。
#define portBYTE_ALIGNMENT
#if portBYTE_ALIGNMENT == 8
#define portBYTE_ALIGNMENT_MASK ( 0x0007 )
#endif
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) &( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* 检查计算出的堆栈顶部的对齐方式是否正确。 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack &( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
14.2.4 定义任务控制块
任务控制块就是一个结构体,里面有非常多的成员,这些成员共同描述了任务的全部信息。
/* AppTaskCreate 任务控制块 */
static StaticTask_t AppTaskCreate_TCB;
/* AppTaskCreate 任务控制块 */
static StaticTask_t LED_Task_TCB;
14.2.5 静态创建任务
一个任务的三要素是任务主体函数,任务栈,任务控制块,那么怎么样把这三个要素联合在一起? FreeRTOS 里面有一个叫静态任务创建函数 xTaskCreateStatic(),它就是干这个活的。 它将任务主体函数, 任务栈(静态的)和任务控制块(静态的)这三者联系在一起,让任务可以随时被系统启动。
AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t )AppTaskCreate, //任务函数
(const char* )"AppTaskCreate", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )3, //任务优先级
(StackType_t* )AppTaskCreate_Stack, //任务堆栈
(StaticTask_t* )&AppTaskCreate_TCB); //任务控制块
同理创建LED闪烁的任务
14.2.6 启动任务
FreeRTOS 中启动任务调度器的函数是 vTaskStartScheduler()。
/* 启动任务,开启调度 */
vTaskStartScheduler();
14.2.7 main.c函数
该节比较简单,贴个代码就OK了
/* 包含头文件 ----------------------------------------------------------------*//* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_usart.h"
#include "bsp_led.h"
/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
/* 私有变量 ------------------------------------------------------------------*/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle;
/* LED任务句柄 */
static TaskHandle_t LED_Task_Handle;
/* 空闲任务任务堆栈 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
/* 定时器任务堆栈 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
/* 空闲任务控制块 */
static StaticTask_t Idle_Task_TCB;
/* 定时器任务控制块 */
static StaticTask_t Timer_Task_TCB;
/* AppTaskCreate 任务任务堆栈 */
static StackType_t AppTaskCreate_Stack[128];
/* AppTaskCreate 任务控制块 */
static StaticTask_t AppTaskCreate_TCB;
/* LED 任务堆栈 */
static StackType_t LED_Task_Stack[128];
/* AppTaskCreate 任务控制块 */
static StaticTask_t LED_Task_TCB;
/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
static void BSP_Init(void);
static void LED_Task(void* parameter);
static void AppTaskCreate(void);
/* 函数体 --------------------------------------------------------------------*/
/**
* 函数功能: 主函数.
* 输入参数: 无
* 返 回 值: 无
* 说 明: 无
*/
int main(void)
{
BSP_Init();
/* 创建LED_Task任务 */
AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t )AppTaskCreate, //任务函数
(const char* )"AppTaskCreate", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )3, //任务优先级
(StackType_t* )AppTaskCreate_Stack, //任务堆栈
(StaticTask_t* )&AppTaskCreate_TCB); //任务控制块
if ( NULL != AppTaskCreate_Handle) /* 创建成功 */
{
vTaskStartScheduler(); /* 启动任务,开启调度 */
}
}
/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
**********************************************************************/
static void AppTaskCreate(void)
{
taskENTER_CRITICAL(); //进入临界区
/* 创建LED_Task任务 */
LED_Task_Handle = xTaskCreateStatic((TaskFunction_t )LED_Task, //任务函数
(const char* )"LED_Task", //任务名称
(uint32_t )128, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )4, //任务优先级
(StackType_t* )LED_Task_Stack, //任务堆栈
(StaticTask_t* )&LED_Task_TCB); //任务控制块
if(NULL != LED_Task_Handle)/* 创建成功 */
printf("LED_Task任务创建成功!\n");
else
printf("LED_Task任务创建失败!\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
/**********************************************************************
* @ 函数名 : LED_Task
* @ 功能说明: LED_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void LED_Task(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500); /* 延时500个tick */
printf("LED_Task Running,LED1_ON\r\n");
LED1_OFF;
vTaskDelay(500); /* 延时500个tick */
printf("LED_Task Running,LED1_OFF\r\n");
}
}
/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* LED初始化 */
LED_GPIO_Init();
LED1_ON;
/* 程序停止在这里 */
while(1);
/* 串口初始化 */
USART_Config();
}
/**
**********************************************************************
* @brief 获取空闲任务的任务堆栈和任务控制块内存
* ppxTimerTaskTCBBuffer : 任务控制块内存
* ppxTimerTaskStackBuffer : 任务堆栈内存
* pulTimerTaskStackSize : 任务堆栈大小
* @author fire
* @version V1.0
* @date 2018-xx-xx
**********************************************************************
*/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &Idle_Task_TCB;/* 任务控制块内存 */
*ppxIdleTaskStackBuffer = Idle_Task_Stack;/* 任务堆栈内存 */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;/* 任务堆栈大小 */
}
/**
*********************************************************************
* @brief 获取定时器任务的任务堆栈和任务控制块内存
* ppxTimerTaskTCBBuffer : 任务控制块内存
* ppxTimerTaskStackBuffer : 任务堆栈内存
* pulTimerTaskStackSize : 任务堆栈大小
* @author fire
* @version V1.0
* @date 2018-xx-xx
**********************************************************************
*/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer = &Timer_Task_TCB;/* 任务控制块内存 */
*ppxTimerTaskStackBuffer = Timer_Task_Stack;/* 任务堆栈内存 */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;/* 任务堆栈大小 */
}
14.3 下载验证
在当前这个例程, 任务的栈, 任务的控制块用的都是静态内存, 必须由用户预先定义,这种方法我们在使用 FreeRTOS 的时候用的比较少,通常的方法是在任务创建的时候动态的分配任务栈和任务控制块的内存空间,