单片机/MCUwilliam hill官网
直播中

黄欢

12年用户 61经验值
擅长:可编程逻辑 控制/MCU
私信 关注
[文章]

单片机小型操作系统多任务并行的原理

本帖最后由 ruihuan 于 2013-5-25 15:32 编辑

       当你看到操作系统的多任务并行的时候,你是否觉的很神奇呢?你是否想过那些任务的时间片轮转是怎么实现的呢?这个时间片轮转的秘密是什么呢?相信很多单片机初学者都会对这个问题感兴趣。
       当你查看单片机的汇编指令的时候,你是否留意过中断返回指令的操作过程?如果你留意过这个操作过程的话,你一定会发现它通过堆栈复原PC指针的过程。也就是说:当单片机产生中断的时候,它会把当前程序正在运行的PC指针保存在堆栈当中;然后当中断程序运行完毕的时候,程序执行中断返回指令,把堆栈中保存的PC指针再返回出来。这个是单片机运行的正常情况,但是如果在单片机的中断中修改了堆栈中的PC指针值,那么是不是中断就返不回来原来的程序地址?如果还是这个地址是我故意设定的值那么他是不是执行完这个中断就跳到我的设定的这个地址上去了?如果这个地址又是另外一个任务的地址的时候不是正好实现了从一个任务的死循环中跳到了另外一个任务的死循环中去了?对了!没错!多任务并行运行、时间片轮转的秘密就是:在定时中断中修改堆栈,使保存在中断中PC指针按照预定的方式改变从而让PC指针在几个任务地址中不停的跳转。如果你有空去看UCOS的移植代码的时候,你会发现切换任务的asm函数就是在修改堆栈的内容。当然了,真正的任务的切换过程不仅仅是修改PC指针的堆栈内容,同时也修改了单片机所用到的所有可能被用到的寄存器的内容。
      分享的是我写的一个双任务并行的程序。一个任务是“main”函数的死循环,另外一个任务是函数“task1”中的死循环。任务操作很简单,就是两个io口不停在切换电平,但是由于两个任务中的时间延时不一样而是切换电平的频率不一样。单片机用的是瑞萨k0s系列的单片机(应为这个是我工作中常用的型号,我现在对这个比较熟,而51单片机很久没有用过了,所以用的是这个型号,什么时候有空了我再写个51单片机的程序)。有这个系列单片机开发板的可以单步运行看看任务切换过程中“偷换”堆栈内容的过程。(后面将分享我在这个平台下写的一个简单的时间片轮转的小操作系统)修改堆栈内容的中断函数:

__interrupt void MD_INTTM80( void )
{
/* TODO */


#asm
push ax
push bc
push de
push hl

movw ax,sp
movw bc,ax
mov a,#0ffh
sub a, c
inc a
mov c,a ;the sp_long
;/*********************;load the sp_long*****************************/
mov a,c
mov !0fec0h,a ;load the sp_long
;/**************************************************/
mov b,a ;load the data
movw de,#0feffh
movw hl,#0fec1h
?L_mov1: mov a,[de]
mov [hl],a
decw de
dec b
incw hl
mov a,b
cmp a,#0h
bnz $?L_mov1
;/************************load the data to sp**************************/
mov a,!0fea0h ;load the data to sp
mov b,a
movw de,#0fea1h
movw hl,#0feffh
?L_mov2: mov a,[de]
mov [hl],a
incw de
dec b
decw hl
mov a,b
cmp a,#0h
bnz $?L_mov2
;/*************************get the new sp_point*************************/
mov a,#0ffh ;get the new sp_point
sub a,!0fea0h
inc a
mov !0fe90h,a
mov a,#0feh
mov !0fe91h,a
;/************************save the sp_data**************************/
mov a,!0fec0h ;save the sp_data
mov !0fea0h,a
mov b,a
movw de,#0fec1h
movw hl,#0fea1h
?L_mov_sp: mov a,[de]
mov [hl],a
incw de
dec b
incw hl
mov a,b
cmp a,#0h
bnz $?L_mov_sp
#endasm
/**************************************************/

#asm
mov a,!0fe90h
mov x,a
mov a,!0fe91h
movw sp,ax
pop hl
pop de
pop bc
pop ax
#endasm


}

在上面基础上自己写的基于NEC k0s系列 PM+ 开发环境下的简易操作系统源码分享
https://bbs.elecfans.com/forum.p ... 857&pid=1600685
一步步在自己熟悉的单片机平台上写自己的简易操作系统
(1)NEC k0s系列 PM+ 开发环境下的自己写的简易 操作系统代码
https://bbs.elecfans.com/forum.ph ... 0685&fromuid=756106
一步步在自己熟悉的单片机平台上写自己的简易操作系统
(2)代码分析一
https://bbs.elecfans.com/forum.ph ... 2371&fromuid=756106
一步步在自己熟悉的单片机平台上写自己的简易操作系统
(3)代码分析二
https://bbs.elecfans.com/forum.ph ... 9110&fromuid=756106
一步步在自己熟悉的单片机平台上写自己的简易操作系统
(3)代码分析三
https://bbs.elecfans.com/forum.ph ... 8743&fromuid=756106



回帖(79)

2012-10-18 12:57:46
有木有C语言的
举报

黄欢

2012-10-18 14:36:02
本帖最后由 ruihuan 于 2012-10-18 15:04 编辑
引用: DQHWX 发表于 2012-10-18 12:57
有木有C语言的


木有啊。。。C语言不能获取堆栈SP当前的值,这样获取不了堆栈长度(当前堆栈长度=当前堆栈的值和原来设的堆栈底之差的绝对值)。
       上面汇编的内容是:先保护常用寄存器 ax bc de hl(如果是51单片那么对应的应该是r0 r1 r2 r3 ...) ;然后计算堆栈长度;再把堆栈数据全部复制到一段数据“1”区内(这个是数据去起中间缓存的作用),并且保存这段数据的长度;再把数据“2”区内的数据覆盖回原来的堆栈(数据“2”区就是上一个任务的堆栈数据);再根据数据“2”去的数据长度回复SP值;再把数据“1”区的数据覆盖数据“2”去的数据(下个中断来的时候再把这个数据“2”区的书覆盖堆栈);最后还原常用寄存器 ax bc de hl 数据,退出中断(因为我写的汇编是嵌在C语言中的,所以不用这个汇编指令)。
举报

叶楷

2012-10-18 16:04:37
举报

lxy9500

2012-10-18 16:09:30
哦,不错的思路。
举报

张贝

2012-10-18 16:29:39
…………看看
举报

陆迪

2012-10-18 16:55:52
举报

罗军云

2012-10-18 17:03:40
学习了~
举报

hbnwzhzg

2012-10-18 22:43:01
看不懂
举报

dpj19930606

2012-10-19 18:53:41
学习 徐诶
举报

865025

2012-10-21 23:48:23
呵呵,好东东
举报

马文锐

2012-10-24 17:13:45
haodongxi
举报

iwxfnl

2012-10-24 18:47:31
举报

萧斌

2012-10-24 22:21:28
懂汇编的才是真正的牛人啊
举报

jjkhkh

2012-10-25 18:45:50
支持下
举报

田兆宇

2012-11-6 17:23:01
学习一下。。
举报

活出一生精彩

2012-11-6 19:17:16
举报

葛超

2012-11-21 18:02:07
好东西
举报

804682887

2012-12-14 15:55:59
举报

梅中胜

2012-12-15 00:04:28
不错
举报

更多回帖

发帖
×
20
完善资料,
赚取积分