加载中
加载中
表情图片
评为精选
鼓励
加载中...
分享
加载中...
文件下载
加载中...
修改排序
加载中...
自己动手写STM32多任务调度器
张静茹2015/06/04软件综合 IP:山东
首先介绍几个寄存器,这几个寄存器只能在汇编或内联汇编时才能访问,C语言是访问不到的
R0-R12,都可作为临时变量存储,跟C语言的变量差不多,不过汇编中的存储变量是用寄存器,而且不用声明,全局可见,不分全局和局部,而且是32位的
比如想计算1+1,结果放在r0中
mov r0,0x01
add r0,0x01
寄存器.png
图片来自互联网,互联网来自<<Cortex-M3权威指南>>


R13(MSP/PSP)堆栈寄存器,汇编指令PUSH,POP会影响R13的值,
PUSH {R0} //R0的值将被压入栈内 R13-4
POP{R1}    //将SP所指向的内存赋值给R1 SP+4
栈.png


R14 :是连接寄存器(LR),用于在调用子程序时存储返回地址,应该是不能用mov mrs msr访问的,只能用pop push保存
R15 是程序计数器(PC),每条指令前都对应一个地址,把这个地址赋值给R15,程序就立即跳过去执行地址对应的程序
xRPS特殊功能寄存器组 详细内容请参考<<Cortex-M3权威指南>>

之前用过UCOS,可是他每个死循环任务下面必须有一个延时函数,才可以把调度权限交还给系统,自然这个延时函数上面也不可以有死循环,
不然将永远卡在死循环上,别的任务也就不能再被调用了


Other
int main(void) {           GPIO_InitTypeDef  GPIO_InitStructure;                          Stm32_Clock_Init(9); //系统时钟设置     delay_init(72);      //延时初始化     uart_init(72,9600);  //串口初始化为9600     LED_Init();          //初始化与LED连接的硬件接口       SysTick_Configuration();     OSInit();     OSTaskCreate( TaskStart,    //task pointer                     (void *)0,  //parameter                     (OS_STK *)&TASK_START_STK[START_STK_SIZE-1],    //task stack top pointer                     START_TASK_Prio );  //task priority                              OSStart();     return 0;       } //开始任务 void TaskStart(void * pdata) {     pdata = pdata;     OS_ENTER_CRITICAL();       OSTaskCreate(TaskLed, (void * )0, (OS_STK *)&TASK_LED_STK[LED_STK_SIZE-1], LED_TASK_Prio);     OSTaskCreate(TaskLed1, (void * )0, (OS_STK *)&TASK_LED1_STK[LED1_STK_SIZE-1], LED1_TASK_Prio);     OSTaskSuspend(START_TASK_Prio); //suspend but not delete     OS_EXIT_CRITICAL(); } //任务1 //控制DS0的亮灭. void TaskLed(void *pdata) {     while(1)     {         LED0 = !LED0;         OSTimeDlyHMSM(0,0,1,100);       } } //任务2 //控制DS1的亮灭. void TaskLed1(void *pdata) {     while(1)     {         LED1 = !LED1;         OSTimeDlyHMSM(0,0,1,300);       } }








正文开始:
我写的这个调度系统是用stm32 定时器中断调度任务的,任务中不需要ucos那种延时函数,定时器每隔一段时间中断当前任务,保存现场,恢复第二个任务的现场,这时PC寄存器被改为第二个任务上次中断前执行的位置然后退出中断,,从任务二上次中断前的位置继续执行
调度原理:
参考<<Cortex-M3权威指南>>中 第九章 中断的具体行为
当 C M 3开始响应一个中断时内核会自动 把 8个寄存器(R0-R3,R12,LR,PC,xPSR)的值压入栈,最关键的是PC,他直接决定了中断退出以后开始执行的位置,R0-R3,R12则保存了一些中间变量,保证了恢复现场以后程序正确执行


程序流程:                                                                       标志:↓↓↓
进入main() ----> 初始化GPIO,时钟,定时器,开中断 ---->进入任务0 ---->定时器中断时间到 ---->开始进入中断 ---->系统自动8个寄存器(R0-R3,R12,LR,PC,xPSR)的值压入栈 ---->进入中断函数TIM3_IRQHandler(此时SP堆栈指针正指向R0,R0+4后,指向R1)
Other
TIM3_IRQHandler PROC         PUSH     {r4,lr}         MRS      r4,MSP         MOV      r0,r4         ADD      r0,r0,#8         MOV      r4,r0         LDR      r0,|L0.640|         STR      r4,[r0,#0]         BL       IRQHandler         POP      {r4,pc}         ENDP
  ----->生成汇编文件后可以看到,进入TIM3_IRQHandler函数先把R4和LR压栈(这是编译器自动做的),MSP堆栈指针 - 8   ----->MRS      r4,MSP 保存栈指针   ----->指针+8(对前面R4和LR的补偿)对准 8个被自动压栈的寄存器的R0   ----->保存指针到全局变量Address_BASE   ----->bl    IRQHandler  调用任务调度程序   ----->清除中断待处理位   ----->根据之前保存的栈地址,加载8个寄存器保存当前任务现场   ----->   调用任务二的地址----->  task->Start_Next(); 更新任务标志----->   退出中断 并 恢复被修改了返回地址的8个寄存器 ----->  执行任务1----->   定时器中断时间到 ---->进入中断  -----> 保存任务1现场----->   恢复任务0  ----->退出中断  ----->goto 标志;


Other
#include "../h/main.h"                  extern Task *task; int main(void) {                      task = new Task(); //创建任务管理对象 这是一个C++类对象                          Init();//初始化 时钟 串口 GPIO     Timerx_Init(20,7199);//初始化TIM3,开中断,每2ms进一次中断     TIM3->CNT = 0x01;//意义不明 不要也行     __asm  //內联汇编     {         bl Task0 //跳转到任务1     } //  while (true); //  delete task; } int temp = 0; void Task0(void) //任务1 {     while (true)     {         LED0 = !LED0;     } }                  void Task1(void) //任务2 {     while (true)     {         LED1 = !LED1;     } }

Other
class Task //任务管理类 { public:Task(void) //构造函数,初始化时会自动调用     {         Reg_Buff[0][6]=((unsigned int)&Task0) ; //初始化任务0的指针         Reg_Buff[1][6]=((unsigned int)&Task1) ; //初始化任务1的指针         Reg_Buff[0][6]=Reg_Buff[1][7]=0x61000000 ; //初始化xRSP                          Current = 0;         Next = 1;     }     public: static const unsigned char Count = Task_Count;     public: unsigned char Current; //当前任务     public: unsigned char Next ;    //下一个任务     public: volatile unsigned int Reg_Buff[Task_Count][8];     public: void    Start_Next() //更新至下一个任务     {         (Current + 1 < Count) ? Current++ : Current = 0;         (Next + 1 < Count) ? Next++ : Next = 0;                  //      if (Next != 0 && (Next - Current) != 1) //      { //          while (true) //          {                  //          } //      }     }                  };
Other
Task *task ; unsigned int Address_BASE = 0;                  void IRQHandler(void) {                          if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源     {         TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  //清除TIMx的中断待处理位:TIM 中断源         __asm         {             ldr r5, [Address_BASE]               str r5, [&task->Reg_Buff[task->Current][0]]//R0                              ldr r5, [Address_BASE , 0x4]             str r5, [&task->Reg_Buff[task->Current][1]]//R1                              ldr r5, [Address_BASE , 0x8]             str r5, [&task->Reg_Buff[task->Current][2]]//R2                              ldr r5, [Address_BASE , 0xc]             str r5, [&task->Reg_Buff[task->Current][3]]//R3                              ldr r5, [Address_BASE , 0x10]             str r5, [&task->Reg_Buff[task->Current][4]]//R12                  //          ldr r5, [Address_BASE , 0x14] //          str r5, [&task->Reg_Buff[task->Current][5]]//R13 LR                              ldr r5, [Address_BASE , 0x18]                              str r5, [&task->Reg_Buff[task->Current][6]]//R14 PC                              ldr r5, [Address_BASE , 0x1c]             str r5, [&task->Reg_Buff[task->Current][7]]//R15 xRSP                                          /*↑↑↑保存当然运行中的任务现场↑↑↑*/                              ldr r5, [&task->Reg_Buff[task->Next][0]]//R0             str r5, [Address_BASE]                              ldr r5, [&task->Reg_Buff[task->Next][1]]//R1             str r5, [Address_BASE, 0x4]                              ldr r5, [&task->Reg_Buff[task->Next][2]]//R2             str r5, [Address_BASE, 0x8]                              ldr r5, [&task->Reg_Buff[task->Next][3]]//R3             str r5, [Address_BASE, 0xc]                              ldr r5, [&task->Reg_Buff[task->Next][4]]//R12             str r5, [Address_BASE, 0x10]                  //          ldr r5, [&task->Reg_Buff[task->Next][5]]//R13 LR //          str r5, [Address_BASE, 0x14]                              ldr r5, [&task->Reg_Buff[task->Next][6]]//R14 PC             //orr r5, 0x01             str r5, [Address_BASE, 0x18]                              ldr r5, [&task->Reg_Buff[task->Next][7]]//R15 xRSP             str r5, [Address_BASE, 0x1c]             /*↑↑↑恢复上一个任务的现场↑↑↑*/         }         task->Start_Next(); //下一个任务     } } extern "C" {     void TIM3_IRQHandler(void)   //TIM3中断  中断中不能有太多东西,否则进中断时压栈太多 MSP不容易计算     {         __ASM         {             mrs r4, msp             add r4, 0x08             str r4, [&Address_BASE]             bl    IRQHandler         }     }                  }

[修改于 10年0个月前 - 2015/06/05 14:26:47]

加载全文
来自:计算机科学 / 软件综合
62
 
1
新版本公告
~~空空如也
amo
10年0个月前 IP:江西
774065
引用 金星凌日:
我说的就是单线程的情况,与多线程根本没有关系。
这种编程算法一开始就是错的……如果目的资源是唯一性的,这种算法在ucos、windows、linux上都解决不了。
举个例子,某块跑ucos的板子上只有一个串口,任务A需要把100KB数据实时、完整的从串口发出,所以它直接直接调用putchar()一类的函数逐个字节发送;恰好发到一半时,任务B也要从串口实时、完整的发送200KB数据出去……二虎相争必有一伤。这是任何OS都解决不了的。
所以各种OS、协议栈到处都充斥着各种各样的队列
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金星凌日
10年0个月前 IP:陕西
774067
引用 amo:
这种编程算法一开始就是错的……如果目的资源是唯一性的,这种算法在ucos、windows、linux上都解决不了。
举个例子,某块跑ucos的板子上只有一个串口,任务A需要把100KB数据实时、完整的从串口发出,所以它直接直接调用putch...
我说的那种情况也与惟一性资源没有任何关系。
此外,就算这种写法完全错误,也没法阻止有人这样写,特别是ABCD由不同的人写的情况下。
我的意思实际上与《Effective C++》中的这条规则类似:使接口不易被误用。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
amo
10年0个月前 IP:广东
774241
引用 金星凌日:
我说的那种情况也与惟一性资源没有任何关系。
此外,就算这种写法完全错误,也没法阻止有人这样写,特别是ABCD由不同的人写的情况下。
我的意思实际上与《Effective C++》中的这条规则类似:使接口不易被误用。
规则不能这么随便滥用……
要分工协作,就必须先设计合理的架构,严格分层,按照规范来编程……做不到的只能让项目经理做恶人逐出团队了

比如两个人(用户层)都要发快递,直接打包好物品写好目的地址然后打电话让快递公司(应用层)上门取件即可……
要是两个人都非要层层深入,指挥某个快递员(驱动)、开什么车走什么线路(链路)……那整个体系都乱套了[s::lol]
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
amo
10年0个月前 IP:江西
774244
初学者编程,往往喜欢一个函数直接执行完所有功能;稍进阶后就是层层调用各种函数直到完成所有功能……不要说别人接手,就是自己过一段时间再看都会两眼茫然

初看成熟的开源代码,一般会很不适应,比如发送一个数据包,只是简单复制到一个数组或队列……然后,怎么就没有然后了??
但站在作者的高度,系统的各个层、模块异常清晰,数据流就像工厂的流水线:每个工人只需从面前的流水线(队列)取下半成品,根据工序的要求加工一下,再放回去就行了……外人来参观,站在工人身后看半天可能也想不明白他不停重复一个动作是为啥[s::lol]
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
金星凌日
10年0个月前 IP:陕西
774279
引用 amo:
初学者编程,往往喜欢一个函数直接执行完所有功能;稍进阶后就是层层调用各种函数直到完成所有功能……不要说别人接手,就是自己过一段时间再看都会两眼茫然

初看成熟的开源代码,一般会很不适应,比如发送一个数据包,只是简单复制到一个数组或队列……然...
这些我都知道,可能是我没有表达清楚。
我的意思是,使用了这种协程,这个函数就会变成不可复制的资源。而C语言中没有办法阻止这种错误用法,除非人工检查。或许使用断言会有一定帮助,但这并不能解决所有问题。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
amo
10年0个月前 IP:广东
774328
引用 金星凌日:
这些我都知道,可能是我没有表达清楚。
我的意思是,使用了这种协程,这个函数就会变成不可复制的资源。而C语言中没有办法阻止这种错误用法,除非人工检查。或许使用断言会有一定帮助,但这并不能解决所有问题。
这种问题目前可能没有什么好办法解决,只能强调编程规范和单元测试……
用C/C++写过大程序的人都知道,在一个地方mallco/new后如果在多个分支realloc/delete,很容易就出现内存泄露并且难以查找;在ucos里也一样,如果在某个地方你觉得很重要,加了个OS_ENTER_CRITICAL(),然后在几个条件分支里遗漏了一个OS_EXIT_CRITICAL(),结果就是不知道什么时候系统就会突然挂了(还很难重现),也是很难找出原因
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
uestc008@163.com
7年11个月前 IP:北京
835873
str r4, [&Address_BASE]这句话在我的程序里面编译不通过,提示这种指令不对,楼主看看?
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
uestc008@163.com
7年11个月前 IP:北京
835979
哦,我的汇编部分已经编译没错误了,可以保存现场和恢复现场,就是任务切换还不太清楚怎么弄的。楼主用的编译器和我的不一样,所以部分不一样。楼主两句话要完成的,我要写好多,
        __ASM volatile ("ldr r5,=Address_Base");
__ASM volatile ("ldr r6,[r5]");
__ASM volatile ("ldr r5,[r6]");
__ASM volatile ("ldr r6,=Task_Reg_Buffer");
__ASM volatile ("str r5,[r6]");
感觉好累。。。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
张静茹作者
7年11个月前 IP:江苏
835990
引用 uestc008@163.com:
哦,我的汇编部分已经编译没错误了,可以保存现场和恢复现场,就是任务切换还不太清楚怎么弄的。楼主用的编译器和我的不一样,所以部分不一样。楼主两句话要完成的,我要写好多,
        __ASM vo……
我写了两年C# 汇编已经看不懂了。。。。。
切换任务靠的是定时器
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
uestc008@163.com
7年11个月前 IP:北京
836036
引用 张静茹:
我写了两年C# 汇编已经看不懂了。。。。。
切换任务靠的是定时器
哦,没事,切换我也是靠的定时器,就是保存完现场后再回去是不是需要什么。
没事,我再研究研究,感觉又学了不少东西
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
refinder
7年10个月前 IP:江西
837670
这个离操作系统还有多远
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
上级专业
同级专业
张静茹
进士 学者 机友 笔友
文章
139
回复
1869
学术分
1
2010/12/30注册,1个月27天前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的