本帖最后由 caijie001 于 2018-8-12 10:51 编辑
创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。
开始今天的内容之前,先补充一下上篇**的一点点遗漏的知识点。 1BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
2 const char * const pcName,
3 uint16_t usStackDepth,
4 void *pvParameters,
5 UBaseType_t uxPriority,
6 TaskHandle_t *pvCreatedTask
7 );
8创建任务中的堆栈大小问题,在task.h中有这样子的描述:
9/**
10* @param usStackDepth The size of the task stack specified as the number of variables the stack * can hold - not the number of bytes. For example, if the stack is 16 bits wide and
11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.
12*/代码可左右滑动
当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。 这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)。 文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。 当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。
好啦,补充完毕。下面正式开始我们今天的主题。
我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。 其实我自己写**的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的**,我会讲一点底层,更多的是应用层,主要是用的方面。
按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。 1int main(void)
2{
3 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
4 Delay_Init(); //延时函数初始化
5 Uart_Init(115200); //初始化串口
6 LED_Init(); //初始化LED
7 KEY_Init();
8 //创建开始任务
9 xTaskCreate((TaskFunction_t )start_task, //任务函数
10 (const char* )"start_task", //任务名称
11 (uint16_t )START_STK_SIZE, //任务堆栈大小
12 (void* )NULL, //传递给任务函数的参数
13 (UBaseType_t )START_TASK_PRIO, //任务优先级
14 (TaskHandle_t* )&StartTask_Handler); //任务句柄
15 vTaskStartScheduler(); //开启任务调度
16}
来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。 注意:下面说的创建任务均为xTaskCreate(动态创建)而非静态创建。 1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */
3 if( pxStack != NULL )
4 {
5 /* Allocate space for the TCB. */
6 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
7 /*lint !e961 MISRA exception as the casts are only redundant for some paths. */
8 if( pxNewTCB != NULL )
9 {
10 /* Store the stack location in the TCB. */
11 pxNewTCB->pxStack = pxStack;
12 }
13 else
14 {
15 /* The stack cannot be used as the TCB was not created. Free
16 it again. */
17 vPortFree( pxStack );
18 }
19 }
20 else
21 {
22 pxNewTCB = NULL;
23 }
24 } 首先是利用pvPortMalloc给任务的堆栈分配空间,if( pxStack != NULL )如果内存申请成功,就接着给任务控制块申请内存。pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );同样是使用pvPortMalloc();如果任务控制块内存申请失败则释放 之前已经申请成功的任务堆栈的内存vPortFree( pxStack ); 然后就初始化任务相关的东西,并且将新初始化的任务控制块添加到列表中prvAddNewTaskToReadyList( pxNewTCB ); 最后返回任务的状态,如果是成功了就是pdPASS,假如失败了就是返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
1prvInitialiseNewTask( pxTaskCode,
2 pcName,
3 ( uint32_t ) usStackDepth,
4 pvParameters,
5 uxPriority,
6 pxCreatedTask,
7 pxNewTCB,
8 NULL );
9 prvAddNewTaskToReadyList( pxNewTCB );
10 xReturn = pdPASS;
11 }
12 else
13 {
14 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
15 }
16 return xReturn;
17 }
18// 相关宏定义
19#define pdPASS ( pdTRUE )
20#define pdTRUE ( ( BaseType_t ) 1 )
21/* FreeRTOS error definitions. */
22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ( -1 )
具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。
因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。
创建完任务就开启任务调度了: 1vTaskStartScheduler(); //开启任务调度
在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的) 1xReturn = xTaskCreate( prvIdleTask,
2 "IDLE", configMINIMAL_STACK_SIZE,
3 ( void * ) NULL,
4 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
5 &xIdleTaskHandle );
6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
7 }
8相关宏定义:
9#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )
10#ifndef portPRIVILEGE_BIT
11 #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )
12#endif
13#define configUSE_TIMERS 1
14 //为1时启用软件定时器
从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。 如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是: 1#if ( configUSE_TIMERS == 1 )
2 BaseType_t xTimerCreateTimerTask( void )
3
然后还要把中断关一下 1portDISABLE_INTERRUPTS();
至于为什么关中断,也有说明: 1/* Interrupts are turned off here, toensure a tick does not occur
2before or during the call toxPortStartScheduler(). The stacks of
3the created tasks contain a status wordwith interrupts switched on
4so interrupts will automatically getre-enabled when the first task
5starts to run. */
6/ *中断在这里被关闭,以确保不会发生滴答
7在调用xPortStartScheduler()之前或期间。堆栈
8创建的任务包含一个打开中断的状态字
9因此中断将在第一个任务时自动重新启用
10开始运行。*/
那么如何打开中断呢????这是个很重要的问题 别担心,我们在SVC中断服务函数里面就会打开中断的 看代码: 1__asm void vPortSVCHandler( void )
2{
3 PRESERVE8
4 ldr r3, =pxCurrentTCB /* Restore the context. */
5 ldrr1, [r3] /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */
6 ldrr0, [r1] /* Thefirst item in pxCurrentTCB is the task top of stack. */
7 ldmiar0!, {r4-r11} /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */
8 msrpsp, r0 /*Restore the task stack pointer. */
9 isb
10 movr0, #0
11 msr basepri, r0
12 orrr14, #0xd
13 bxr14
14}
1msr basepri, r0
就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。
1xSchedulerRunning = pdTRUE;
任务调度开始运行
1/* If configGENERATE_RUN_TIME_STATS isdefined then the following
2macro must be defined to configure thetimer/counter used to generate
3the run time counter time base. */
4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。
来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。 1if( xPortStartScheduler() != pdFALSE )
2 {
3 /*Should not reach here as if the scheduler is running the
4 functionwill not return. */
5 }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇**,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过**的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。
开始任务就按照套路模板添加自己的代码就好啦,很简单的。 先创建任务: 1 xTaskCreate((TaskFunction_t )led0_task,
2 (const char* )"led0_task",
3 (uint16_t )LED0_STK_SIZE,
4 (void* )NULL,
5 (UBaseType_t )LED0_TASK_PRIO,
6 (TaskHandle_t* )&LED0Task_Handler);
7 //创建LED1任务
8 xTaskCreate((TaskFunction_t )led1_task,
9 (const char* )"led1_task",
10 (uint16_t )LED1_STK_SIZE,
11 (void* )NULL,
12 (UBaseType_t )LED1_TASK_PRIO,
13 (TaskHandle_t* )&LED1Task_Handler);
创建完任务就开启任务调度: 1vTaskStartScheduler(); //开启任务调度
然后具体实现任务函数:
1//LED0任务函数
2void led0_task(void *pvParameters)
3{
4 while(1)
5 {
6 LED0=~LED0;
7 vTaskDelay(500);
8 }
9}
10//LED1任务函数
11void led1_task(void *pvParameters)
12{
13 while(1)
14 {
15 LED1=0;
16 vTaskDelay(200);
17 LED1=1;
18 vTaskDelay(800);
19 }
20}
好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~
原文链接:【连载】从单片机到操作系统④——FreeRTOS创建任务&开启调度详解
|