接着学习正点原子的FreeRTOS教程,涉及到一些详细的系统内文件代码
可以通过各种的宏定义来实现我们自己的RTOS配置(在FreeRTOSconfig.h)
分为静态和动态创建
动态任务创建:任务的任务控制块以及任务的栈空间所需的内存,均由自动从堆中分配
静态创建任务:任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供
任务创建:
返回值:
pdPASS:创建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:创建失败(内存分配失败)
如何成功创建:
任务控制块:
每个任务都有属于自己的任务控制块,类似身份证。
静态创建任务:
返回值:
NULL:用户没有提供相应的内存,任务创建失败
其他值:任务句柄,任务创建成功
如何成功创建:
任务删除:
void vTaskDelete(TaskHandle_t xTaskToDelete);
参数是待删除任务的任务句柄,用于删除已被创建的任务,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除
要点:
如何删除:
1.任务创建删除:(正点原子的代码)
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
利用该函数创建任务,从上到下分别是指向任务函数的指针,任务名,堆栈大小,任务指针参数,优先级,任务句柄。但是我感觉不需要强制类型转换,有时写错也不容易发现
一般系统初始化的操作是先创建一个任务,这个任务完成系统所有任务的创建,创建完成后把自己删除掉的结构,这样代码比较简洁,结构清晰。
/* 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 );
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(); //开始调度,否则不会执行start_task任务
}
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);
}
}
.................
.................
................
这里vTaskStartScheduler()函数执行后,系统就会开始执行任务,但是很明显,真正的任务都没开始创建,且因为系统按照从上往下执行,所以会先创建的任务先执行,不按照优先级走,等所有任务创建完成后,系统才开始真正按照优先级开始执行任务。
我觉得FreeRTOS的执行过程就是一个一个任务的执行,他是通过执行完一个任务后,再执行下一个,遇到优先级比它高的任务,会被打断,这也就是为什么创建task1后,系统就停下了start_task工作,执行task1的代码了,执行完task1后,又回去初始化task2。感觉 vTaskDelay()函数是系统调度的一个灵活工具。让系统从循环执行任务(还是很像裸机),开始进行灵活分配cpu了
这里用到了临界区,FreeRTOS临界区是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。进入临界区代码的时候需要关闭中断,当处理完临界区代码以后再打开中断。起到一个代码保护的作用。
感觉这个FreeRTOS和之间裸机开发时,看到很多人的程序利用uwTick的数值来执行程序很像,但当然FreeRTOS强大的多,如下:
__weak void HAL_IncTick(void) //利用滴答定时器(一般1ms累加一次)
{
uwTick += uwTickFreq;
}
void deme()
{
if(uwTick-led_tick>1000) //实现每隔1000个计数执行一次代码
{
led_tick=uwTick;
.........
.........
}
}
2.HAL库创建任务:
利用STM32CubeMX生成的FreeRTOS来实现,与正点原子(手动移植)有些区别
我这里简单写了一个点灯任务进行测试,可以正常运行
TaskHandle_t task1_handler;
void vTaskCode( void * pvParameters )
{
while(1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_4);
vTaskDelay(500);
}
}
void vOtherFunction( void )
{
xTaskCreate( vTaskCode, "tak1", 128, NULL, 1, &task1_handler );
//vTaskStartScheduler();
}
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* Init scheduler */
osKernelInitialize(); /* Call init function for freertos objects (in freertos.c) */
MX_FREERTOS_Init();
vOtherFunction(); //放在操作系统内核启动前
/* Start scheduler */
osKernelStart();
while (1)
{}
}
有些不懂的地方,首先我自己不需要启动任务调度了,当然执行这个函数vTaskStartScheduler()也没啥问题,但是这个任务创建放的位置需要在osKernelStart()前面,正点原子的如下,不是特别理解,为啥放在操作系统内核启动,开始任务调度后就不能运行了。
查了查资料,可能是任务调度开启之后,就正式进入FreeRTOS系统接管领域,之后程序只会跑在中断和任务函数中,也就是说如果放在后面,根本就不会执行,一个任务都没创建。而正点原子手动开启任务调度前已经创建了一个任务,所以他才成功了。
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */
delay_init(180); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
sdram_init(); /* SRAM初始化 */
lcd_init(); /* 初始化LCD */
my_mem_init(SRAMIN); /* 初始化内部内存池 */
my_mem_init(SRAMEX); /* 初始化外部内存池 */
my_mem_init(SRAMCCM); /* 初始化CCM内存池 */
freertos_demo(); //任务创建
}
静态任务创建比较复杂,如果后面有需要再补充