完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
回顾:
Q3:关于RTOS编写,要解决哪些核心问题理清问题关键所在后,开始动手。 首先,最基本的功能要先保证,如SysTick初始化,任务堆栈初始化,上下文切换; 上面说的有几个问题:(当时刚接触操作系统和数据结构不久,望体谅) 1、SysTick ①SysTick的作用: 通过SysTick异常周期性地切入系统,进行任务调度。 (SysTick 的最大使命,就是定期地产生异常请求,作为系统的时基。 OS 需要这种“滴答” 来推动任务和时间的管理。—— CM3权威指南) ②关于SysTick,我们需要知道什么? 所有Cortex-M3芯片内部都包含了SysTick定时器(简化了在CM3器件间的软件移植工作),该定时器的时钟源可以是内部时钟,或者是外部时钟。 SysTick定时器是一个24位的倒计数定时器,当计数到0时,将从RELOAD寄存器中自动重载定时器初值,开始新的一轮计数。STM32的延时一般就是通过内部的SysTick来实现的。 STM32基础例程中有关SysTick定时器的初始化设置,可以在delay.c文件(源自正点原子FreeRTOS例程)中查看。delay_init()中也有关于SysTick时钟源的说明:SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); 由此可知,SysTick的时钟源是HCLK。 HCLK :AHB总线时钟,由系统时钟SYSCLK分频得到,一般不分频,等于系统时钟(72MHZ),HCLK是高速外设时钟,是给外部设备的,比如内存,flash,DMA。SysTick控制及状态寄存器: 初始化代码: void SysTick_Init(void) { char *Systick_priority = (char *)0xe000ed23; //Systick中断优先级寄存器 *Systick_priority = 0x00; //设置SysTick中断优先级最高 SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//选择外部时钟 HCLK SysTick->LOAD = ( SystemCoreClock / configTICK_RATE_HZ) - 1UL; //定时周期1ms SysTick->VAL = 0; //Systick定时器计数器清0 //SysTick->CTRL: bit0-定时器使能 bit1-中断使能 bit2-时钟源选择(=1系统主频,=0系统主频/8) SysTick->CTRL = 0x7; //选择外部时钟,允许中断,开启定时器 } 敲重点: 2、PendSV ①PendSV作用:任务切换时保存上下文(将一些寄存器和变量的值保存到任务堆栈,或将任务堆栈出栈,恢复过去任务的运行环境) 将任务堆栈出栈,就是将要运行的任务的堆栈的栈顶赋给sp,只有知道了sp我们才能找到对应的任务堆栈,才能找到堆栈保存任务信息;②悬起PendSV的方法是:手工往NVIC的PendSV悬起寄存器中写1。 int* NVIC_INT_CTRL= (int *)0xE000ED04; //中断控制寄存器地址 void SetPendSV(void)//挂起PendSV { *NVIC_INT_CTRL=0x10000000;//1<<28 } ③PendSV的中断优先级 void PendSVPriority_Init(void) { char* NVIC_SYSPRI14= (char *)0xE000ED22; //PendSV优先级寄存器地址 *NVIC_SYSPRI14=0xFF; //设置PendSV中断优先级最低 } 3、TCB与任务堆栈 ######task.h文件###### typedef enum { eTask_Running = 0, eTask_Ready, eTask_Suspended, eTask_Blocked, eTask_Deleted, }eTaskSta; #define OS_MAX_TASK 8 //最大任务数量 typedef struct _TaskControlBlock { stk32 *StkPtr; char name[16]; int state; //任务状态 int prio; int DlyTim; //任务阻塞时间 }TCB,*pTCB; ######task.c文件###### pTCB TASK_LIST[OS_MAX_TASK]; int created_task_num = 0;//全局变量 //任务堆栈初始化 //入栈顺序:栈顶->栈尾 xPSR,PC,LR,R12,R3-R0,R4-R11共16个(SP(R13)保存在TCB首个成员变量中)。 stk32* task_stk_init(void* func, stk32 *TopOfStack) { stk32 *stk; stk = TopOfStack; *(stk) = (u32)0x01000000L;//xPSR 程序状态寄存器,xPSR T位(第24位)必须置1,否则第一次执行任务时进入Fault中断 *(--stk) = (u32)func; //PC 初使化时指向任务函数首地址(任务切换时,可能指向任务函数中间某地址) *(--stk) = (u32)0xFFFFFFFEL;//LR 保存着各种跳转的返回连接地址,初使化为最低4位为E,是一个非法值,主要目的是不让使用R14,即任务是不能返回的 stk-=13; return stk; } void create_new_task( void *func, char name[], int prio, stk32 *TopOfStack, pTCB *tcb) { int name_len = strlen(name); irq_disable(); if(created_task_num == OS_MAX_TASK) { printf("Create Task Failrn"); } if(tcb) { *tcb = (pTCB)malloc(sizeof(TCB)); if(*tcb) { (*tcb)->StkPtr = task_stk_init(func,TopOfStack); memcpy((*tcb)->name,name,sizeof(char)*name_len); (*tcb)->state = eTask_Ready; (*tcb)->prio = prio; (*tcb)->DlyTim = 0; TASK_LIST[created_task_num] = *tcb; created_task_num++; } } irq_enable(); } //数字越大,任务优先权越高 extern pTCB pTCB_IDLE; pTCB GetHighRdyTask(void) { int i = 0; pTCB pTtmp ,pTrdy = pTCB_IDLE; for(i = 0; i < created_task_num; i++) { if(TASK_LIST->state == eTask_Ready) { pTtmp = TASK_LIST; if(pTtmp->prio > pTrdy->prio) pTrdy = pTtmp; } } return pTrdy; 敲重点: ①“stk32 *StkPtr;”必须放在任务控制块的首位; ②入栈顺序与PendSV保存和恢复现场过程有关 ③没有另外维护就绪、延时列表,只用了一个结构体指针数组,根据任务的状态来管理任务调度 4、上下文切换:PendSV_Handler函数 IMPORT pTCB_Cur IMPORT pTCB_Rdy EXPORT PendSV_Handler EXPORT SP_INIT PRESERVE8 ;//字节对齐关键词,指定当前文件八字节对齐。 AREA |.text|, CODE, READONLY ;//定义一个代码段或数据段。 THUMB ;//指定以下指令都是THUMB指令集(ARM汇编有多种指令集) SP_INIT ;初始化PSP指针 CPSID I ;//关闭全局中断 LDR R4,=0x0 ;//R4装载立即数0(不直接给PSP赋值0而是经进R寄存器作为媒介是因为PSP只能和R寄存器打交道) MSR PSP, R4 ;//PSP(process stack pointer)程序堆栈指针赋值0。PSP属用户级(特级权下为MSP),双堆栈结构。 CPSIE I ;//打开全局中断(此时若没有其他中断在响应,则立即进入PendSV中断函数) BX LR ;/******************PendSV_Handler************/ PendSV_Handler CPSID I ; OS_ENTER_CRITICAL(); MRS R0, PSP ; R0 = PSP; CBZ R0, PendSV_Handler_NoSave ; if(R0 == 0) goto PendSV_Handler_NoSave; SUB R0, R0, #0x20 ; R0 = R0 - 0x20; ; easy method STM R0, {R4-R11} LDR R1, =pTCB_Cur ; R1 = OSTCBCur; LDR R1, [R1] ; R1 = *R1;(R1 = OSTCBCur->OSTCBStkPtr) STR R0, [R1] ; *R1 = R0;(*(OSTCBCur->OSTCBStkPrt) = R0) PendSV_Handler_NoSave ;每次都会进去(因为PendSV_Handler_NoSave不是函数,而是中间的一个标签,用于跳转) ;实质就是pTCB_Cur = pTCB_Rdy ;每次运行PendSV_Handler,都会使pTCB_Cur指向pTCB_Rdy,所以调度时只需从任务数组中获取pTCB_Rdy LDR R0, =pTCB_Cur ; R0 = OSTCBCur; LDR R1, =pTCB_Rdy ; R1 = OSTCBNext; LDR R2, [R1] ; R2 = OSTCBNext->OSTCBStkPtr; STR R2, [R0] ; *R0 = R2;(OSTCBCur->OSTCBStkPtr = OSTCBNext->OSTCBStkPtr) LDR R0, [R2] ; R0 = *R2;(R0 = OSTCBNext->OSTCBStkPtr) LDM R0, {R4-R11} ADD R0, R0, #0x20 MSR PSP, R0 ; PSP = R0;(PSP = OSTCBNext->OSTCBStkPtr) ORR LR, LR, #0x04 ; LR = LR | 0x04; CPSIE I ; OS_EXIT_CRITICAL(); BX LR ; return; ; Enable interrupts at processor level align 4 ;//内存对齐指令(编译器提供的),以4个字节(32位)对齐 end ;//伪指令,放在程序行的最后,告诉编译器编译程序到此结束 kernel.asm汇编代码可以不用自己实现,但要理解它的流程与作用;这里有几个要注意的点: ①PendSV_Handler在kernel.asm中定义了,需要注释掉原来的函数(在stm32f10x_it.h) ②PendSV_Handler_NoSave不是函数,而是中间的一个标签,用于跳转 ③每次运行PendSV_Handler,都会使pTCB_Cur指向pTCB_Rdy,所以调度中只需操作pTCB_Rdy ④SP_INIT是PSP指针初始化函数,用汇编写的,在其它地方被调用 5、OS_Start与系统延时函数(阻塞,调度) void OS_Start(void) { PendSVPriority_Init(); SysTick_Init(); pTCB_Rdy = GetHighRdyTask(); pTCB_Rdy->state = eTask_Running; SP_INIT(); SetPendSV(); while(1);//等待调度 } //任务创建举例: create_new_task(Print_Task,"Print_Task",1,&STK_PRINT_TASK[STK_SIZE-1],&pT2); void Print_Task(void) { while(1) { printf("print taskrn"); OSDelayTicks(1000); } } void OSDelayTicks(int ticks) { pTCB_Cur->state = eTask_Blocked; pTCB_Cur->DlyTim = ticks-1; OS_Schedule(); while(pTCB_Cur->DlyTim != 0);//不能是while(1),否则下次任务解阻塞后,继续运行while(1); } 6、任务调度:SysTick_Handler函数 void OS_Schedule(void) { pTCB pT = GetHighRdyTask(); //检测是否需要任务切换,如果需要则挂起PendSV中断 if(pT != pTCB_Cur) { if(pTCB_Cur->state == eTask_Running) pTCB_Cur->state = eTask_Ready; pTCB_Rdy = pT; pTCB_Rdy->state = eTask_Running; SetPendSV(); } } void SysTick_Handler(void) { int i = 0; os_cpu_interrupt_disable(); if(GetTaskNum(eTask_Blocked) != 0)//延时任务列表中是否有阻塞任务 { for(i = 0; i < created_task_num; i++) { if(TASK_LIST->state == eTask_Blocked) { if(TASK_LIST->DlyTim == 0)//延时时间到了,解除阻塞 { TASK_LIST->state = eTask_Ready; } else TASK_LIST->DlyTim--; } } } if(GetTaskNum(eTask_Ready) != 0) OS_Schedule(); os_cpu_interrupt_enable(); } 结束: 这个是去年5月份做的一个小项目,后面去学Linux系统编程了,也就没有再去花时间和精力将其继续扩展完善。这里将当时的项目过程和笔记分享出来,算是抛转引玉吧。希望能帮助大家对M3内核和RTOS调度有更好的理解。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1780 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1621 浏览 1 评论
1081 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
728 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1679 浏览 2 评论
1938浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
731浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
570浏览 3评论
596浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
556浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 16:19 , Processed in 0.841558 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号