5.4.3 实验三:任务切换设计(时间片调度带浮点)
实验目的:
1. 学习SVC,PendSV和简易时间片调度的设计
实验内容:
1. 工程中创建了四个LED闪烁的任务,并设置嘀嗒定时器中断频率是1ms
2.实现一个简单的时间片调度,每1ms进入嘀嗒定时器后更改要执行的任务并使能PendSV中断。这样退出嘀嗒定时器中断后就能进入PendSV中断实现任务的切换。
3.任务0打印浮点运行的结果。
实验现象:
1. 四个LED按照不同的频率进行闪烁。
2. 请用USB转串口线连接PC机和开发板。PC机上运行SecureCRT软件,波特率设置为115200bps,无硬件流控。从PC机的软件界面观察程序执行结果:
程序设计: 本程序主要分为五个部分: Ø 主程序和四个创建的任务 Ø SVC中断 Ø PendSV中断 Ø 滴答定时器中断 Ø 硬件异常 1. 主程序和四个创建的任务 /* 字访问 */ #define HW32_REG(ADDRESS) (*((volatile unsigned long *)(ADDRESS))) /* 当检测到错误时,用Breakpoint来停止任务(MDK特有的)*/ /* 根据需要可以改成用while(1)来实现 */ #define stop_cpu __breakpoint(0) /* 4个任务 */ void task0(void); void task1(void); void task2(void); void task3(void); void HardFault_Handler_C(unsigned int * svc_args); void SVC_Handler_C(unsigned int * svc_args); void __svc(0x00) os_start(void); // OS 初始化 // 计数,用于切换任务 volatile uint32_t systick_count=0; // 任务堆栈 (8Kbytes each - 1024 x 8 bytes),8字节对齐 long long task0_stack[1024], task1_stack[1024], task2_stack[1024], task3_stack[1024]; // 任务切换用到的数据 uint32_t curr_task=0; // 当前执行任务 uint32_t next_task=1; // 下一个任务 uint32_t PSP_array[4]; // 用于在任务切换时记录PSP uint32_t svc_exc_return; // EXC_RETURN use by SVC /* 仅允许本文件内调用的函数声明 */ static void PrintfLogo(void); /* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参:无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程信息到串口1 */ os_start(); /* 进入主程序循环体 */ while (1) { stop_cpu; } } /* ********************************************************************************************************* * 函 数 名: task0 * 功能说明: 任务0实现LED1的闪烁 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void task0(void) { float Temp = 1.111111f; while (1) { if (systick_count& 0x80) { bsp_LedOn(1); Temp =Temp + 0.000001f; printf("Temp= %f\\r\\n", Temp); } else { bsp_LedOff(1); } }; } /* ********************************************************************************************************* * 函 数 名: task1 * 功能说明: 任务1实现LED2的闪烁 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void task1(void) { while (1) { if (systick_count& 0x100) { bsp_LedOn(2); } else { bsp_LedOff(2); } }; } /* ********************************************************************************************************* * 函 数 名: task2 * 功能说明: 任务2实现LED2的闪烁 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void task2(void) { while (1) { if (systick_count& 0x200) { bsp_LedOn(3); } else { bsp_LedOff(3); } }; } /* ********************************************************************************************************* * 函 数 名: task3 * 功能说明: 任务3实现LED4的闪烁 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void task3(void) { while (1) { if (systick_count& 0x400) { bsp_LedOn(4); } else { bsp_LedOff(4); } }; }
2. SVC中断
任务堆栈的创建是在SVC中断中实现的。这个过程可以用下面的图来表示:
寄存器在堆栈中的存储顺序和实验二是一样的,只是多了EXC_RETURN和CONTROL。/* ********************************************************************************************************* * 函 数 名: SVC_Handler * 功能说明: SVC异常中断服务程序 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ __asm void SVC_Handler(void) { TST LR, #4 // 获取使用的MSP还是PSP ITE EQ MRSEQ R0, MSP MRSNE R0, PSP LDR R1, =__cpp(&svc_exc_return) // 保存当前 EXC_RETURN STR LR, [R1] BL __cpp(SVC_Handler_C) // 运行 SVC_Handler 的C程序 LDR R1, =__cpp(&svc_exc_return) // 加载新的 EXC_RETURN LDR LR, [R1] BX LR ALIGN 4 } /* ********************************************************************************************************* * 函 数 名: SVC_Handler_C * 功能说明: 在SVC异常中运行的C代码 * 形 参:无 * 返 回 值: 无********************************************************************************************************* */ void SVC_Handler_C(unsigned int * svc_args) { uint8_t svc_number; svc_number = ((char *)svc_args[6])[-2]; // Memory[(Stacked PC)-2] switch(svc_number) { /* 开始任务调度 */ case (0): /* 创建任务0的堆栈 */ PSP_array[0]= ((unsigned int) task0_stack) + (sizeof task0_stack) - 18*4; HW32_REG((PSP_array[0]+ (16<<2))) = (unsigned long) task0; // 初始化PC HW32_REG((PSP_array[0]+ (17<<2))) = 0x01000000; // 初始化 xPSR HW32_REG((PSP_array[0] )) = 0xFFFFFFFDUL; // 初始化 EXC_RETURN HW32_REG((PSP_array[0]+ ( 1<<2))) = 0x3;// 初始化 CONTROL : unprivileged,PSP, no FP /* 创建任务1的堆栈 */ PSP_array[1]= ((unsigned int) task1_stack) + (sizeof task1_stack) - 18*4; HW32_REG((PSP_array[1]+ (16<<2))) = (unsigned long) task1; // 初始化 Program Counter HW32_REG((PSP_array[1]+ (17<<2))) = 0x01000000; // 初始化 xPSR HW32_REG((PSP_array[1] )) = 0xFFFFFFFDUL; // 初始化 EXC_RETURN HW32_REG((PSP_array[1]+ ( 1<<2))) = 0x3;// 初始化 CONTROL : unprivileged,PSP, no FP /* 创建任务2的堆栈 */ PSP_array[2]= ((unsigned int) task2_stack) + (sizeof task2_stack) - 18*4; HW32_REG((PSP_array[2]+ (16<<2))) = (unsigned long) task2; // 初始化 Program Counter HW32_REG((PSP_array[2]+ (17<<2))) = 0x01000000; // 初始化 xPSR HW32_REG((PSP_array[2] )) = 0xFFFFFFFDUL; // 初始化 EXC_RETURN HW32_REG((PSP_array[2]+ ( 1<<2))) = 0x3;// 初始化 CONTROL : unprivileged,PSP, no FP /* 创建任务3的堆栈 */ PSP_array[3]= ((unsigned int) task3_stack) + (sizeof task3_stack) - 18*4; HW32_REG((PSP_array[3]+ (16<<2))) = (unsigned long) task3; // 初始化 Program Counter HW32_REG((PSP_array[3]+ (17<<2))) = 0x01000000; // 初始化 xPSR HW32_REG((PSP_array[3] )) = 0xFFFFFFFDUL; // 初始化 EXC_RETURN HW32_REG((PSP_array[3]+ ( 1<<2))) = 0x3;// 初始化 CONTROL : unprivileged,PSP, no FP curr_task= 0; // 切换到 task #0 svc_exc_return= HW32_REG((PSP_array[curr_task])); // 返回线程模式,PSP __set_PSP((PSP_array[curr_task]+ 10*4)); // PSP = R0 NVIC_SetPriority(PendSV_IRQn,0xFF); // 设置PSP为最低优先级 SysTick_Config(SystemCoreClock/1000);//中断频率1000Hz __set_CONTROL(0x3); // 切换到使用PSP,非特权级 __ISB(); break; default: puts("ERROR: Unknown SVC service number"); printf("-SVC number 0x%x\\n", svc_number); stop_cpu; break; } }
3. PendSV中断
这个中断比较重要,重点看浮点寄存器的入栈和出栈实现。/* ********************************************************************************************************* * 函 数 名: PendSV_Handler * 功能说明: 任务切换的实现,根据需要加载或者存储浮点寄存器 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ __asm void PendSV_Handler(void) { // 保存当前任务的寄存器 MRS R0, PSP //得到PSP R0 = PSP TST LR, #0x10 //检测bit 4. 如果是0的话,需要保存浮点寄存器 IT EQ VSTMDBEQ R0!, {S16-S31} // 将浮点寄存器入栈 MOV R2, LR MRS R3, CONTROL // R3 = CONTROL STMDB R0!,{R2-R11} // 保存 LR,CONTROL andR4 to R11 in task stack (10 个寄存器) LDR R1,=__cpp(&curr_task) LDR R2,[R1] //得到当前任务ID LDR R3,=__cpp(&SP_array) STR R0,[R3, R2, LSL #2] // 保存PSP到相应的PSP_array中 // 加载下一个任务堆栈中的数据到相应寄存器 LDR R4,=__cpp(&next_task) LDR R4,[R4] //得到下一个任务 ID STR R4,[R1] //设置 curr_task =next_task LDR R0,[R3, R4, LSL #2] // 从 PSP_array 中加载PSP LDMIA R0!,{R2-R11} // 加载 LR, CONTROL 和 R4 - R11 从任务堆栈中 (10 个寄存器) MOV LR, R2 MSR CONTROL, R3 // CONTROL = R3 TST LR, #0x10 //检测bit 4. 如果是0的话,需要保存浮点寄存器 IT EQ VLDMIAEQ R0!, {S16-S31} // 加载浮点寄存器 MSR PSP, R0 //PSP =R0 BX LR //返回 ALIGN 4 }
4. 滴答定时器中断/* ********************************************************************************************************* * 函 数 名:SysTick_Handler * 功能说明: 嘀嗒定时器中断 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void SysTick_Handler(void) { systick_count++; // 简单的 round robin 调度器,时间片是1ms // 每ms切换一次。 switch(curr_task) { case 0: next_task=1; break; case 1: next_task=2; break; case 2: next_task=3; break; case 3: next_task=0; break; default: next_task=0; printf("ERROR:curr_task= %x\\n", curr_task); stop_cpu; break; //程序不该运行到这里 } if (curr_task!=next_task) { SCB->ICSR |=SCB_ICSR_PENDSVSET_Msk; // Set PendSV to pending } }
5. 硬件异常/* ********************************************************************************************************* * 函 数 名:HardFault_Handler * 功能说明: 硬件故障中断服务程序 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ __asm void HardFault_Handler(void) { TST LR, #4 ITE EQ MRSEQ R0, MSP MRSNE R0, PSP B __cpp(HardFault_Handler_C) } /* ********************************************************************************************************* * 函 数 名:HardFault_Handler_C * 功能说明: 进入硬件异常后,打印相关变量 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void HardFault_Handler_C(unsigned int * svc_args) { puts("[HardFault]"); printf ("curr_task = %d\\n",curr_task); printf ("next_task =%d\\n", next_task); printf ("SP #0 =%x\\n", PSP_array[0]); printf ("SP #1 =%x\\n", PSP_array[1]); printf ("SP #2 =%x\\n", PSP_array[2]); printf ("SP #3 =%x\\n", PSP_array[3]); printf ("Stacked PC =%x\\n", svc_args[6]); stop_cpu; }
5.5 实验总结
本期教程相对于前面几期要难一些,需要多花点时间去学习,掌握好了,后面学习μCOS-III事半功倍。
参考资料: 1. Patterns fortime-triggered embedded systems英文版和中文版 2. Cortex-M3权威指南中文版 3. TheDefinitive Guide to Arm Cortex-M3 and Cortex-M4 Processors(M4权威指南) |