51单片机多任务运行
试论51单片机多任务编程设计及应用
试论51单片机多任务编程设计及应用51单片机是一种基于哈弗曼编码技术的单片机,广泛应用于嵌入式系统中。
随着嵌入式系统复杂度的不断提高,多任务编程设计已经成为了一种必要的需求。
本文将试论51单片机多任务编程设计及应用。
多任务设计可以使嵌入式系统在同时运行多个任务时,具有更高的系统效率和响应速度。
在多任务环境中,各任务可同时执行,每个任务都具有自己的优先级和独立的数据空间。
对于51单片机而言,要实现多任务编程设计,需要考虑以下几个方面:1.任务划分首先需要将任务划分为多个单独的任务。
根据任务的优先级和时间限制,进行合理的划分和安排。
2.任务调度在多任务环境中,需要进行任务调度,确定任务执行的优先级和时间片。
一般采用时间片轮转方式,即每个任务分配一个时间片,时间到了就切换到下一个任务。
3.任务间通信不同任务之间需要共享数据和通信,可采用共享变量或消息机制等方式进行任务间通信。
在实际应用中,多任务编程设计可应用于多种场景,例如自动控制、机器人控制、传感器采集等。
下面以自动控制为例,介绍51单片机多任务编程设计及应用。
自动控制系统通常包括数据采集、数据处理和执行控制三个任务。
数据采集任务负责采集传感器数据,数据处理任务负责对采集数据进行处理和分析,执行控制任务负责根据处理结果执行相应的控制操作。
在这个模块中,执行控制任务的优先级最高,数据采集任务的优先级最低。
首先应进行任务划分,将数据采集、数据处理和执行控制分别划分为独立的任务。
接着需要确定任务调度的方式和时间限制,可采用时间片轮转方式,每个任务分配一个时间片,执行控制任务的时间片最短,数据采集任务的时间片最长。
最后需要进行任务间通信,数据采集任务将采集到的数据存入共享变量中,数据处理任务通过共享变量获取数据进行处理,执行控制任务通过共享变量获取处理结果进行操作。
综上所述,对于51单片机的多任务编程设计及应用,需要进行任务划分、任务调度和任务间通信。
在实际应用中,多任务设计可提高嵌入式系统的效率和响应速度,降低系统复杂度。
51单片机最简单的多任务操作系统
/*51单片机最简单的多任务操作系统其实只有个任务调度切换,把说它是OS有点牵强,但它对于一些简单的开发应用来说,简单也许就是最好的.尽情的扩展它吧.别忘了把你的成果分享给大家.这是一个最简单的OS,一切以运行效率为重,经测试,切换一次任务仅个机器周期,也就是在标准(工作于M晶振)上uS.而为速度作出的牺牲是,为了给每个任务都分配一个私有堆栈,而占用了较多的内存.作为补偿,多任务更容易安排程序逻辑,从而可以节省一些用于控制的变量.任务槽越多,占用内存越多,但任务也越好安排,以实际需求合理安排任务数目.一般来说,4个已足够.况且可以拿一个槽出来作为活动槽,换入换入一些临时任务.task_load(函数名,任务槽号)装载任务os_start(任务槽号)启动任务表.参数必须指向一个装载了的任务,否则系统会崩溃.task_switch()切换到其它任务.编写任务函数注意事项:KEIL C编译器是假定用户使用单任务环境,所以在变量的使用上都未对多任务进行处理,编写任务时应注意变量覆盖和代码重入问题.1.覆盖:编译器为了节省内存,会给两个没用调用关系的函数分配同一内存地址作为变量空间.这在单任务下是很合理的,但对于多任务来说,两个进程会互相干扰对方.解决的方法是:凡作用域内会跨越task_switch()的变量,都使用static前辍,保证其地址空间分配时的唯一性.2.重入:重入并不是多任务下独有的问题,在单任务时,函数递归同样会导致重入,即,一个函数的不同实例(或者叫作"复本")之间的变量覆盖问题.解决的方法是:使用reentrant函数后辍(例如:void function1() reentrant{...}).当然,根本的办法还是避免重入,因为重入会带来巨大的目标代码量,并极大降低运行效率.3.额外提醒一句,在本例中,任务函数必须为一个死循环.退出函数会导致系统崩溃..任务函数如果是用汇编写成或内嵌汇编,切换任务时应该注意什么问题?由于KEIL C编译器在处理函数调用时的约定规则为"子函数有可能修改任务寄存器",因此编译器在调用前已释放所有寄存器,子函数无需考虑保护任何寄存器.这对于写惯汇编的人来说有点不习惯: 汇编习惯于在子程序中保护寄存器.请注意一条原则:凡是需要跨越task_switch()的寄存器,全部需要保护(例如入栈).根本解决办法还是,不要让寄存器跨越任务切换函数task_switch()事实上这里要补充一下,正如前所说,由于编译器存在变量地址覆盖优化,因此凡是非静态变量都不得跨越task_switch().任务函数的书写:void 函数名(void){//任务函数必须定义为无参数型while(1){//任务函数不得返回,必须为死循环//....这里写任务处理代码task_switch();//每执行一段时间任务,就释放CPU一下,让别的任务有机会运行.}}任务装载:task_load(函数名,任务槽号)装载函数的动作可发生在任意时候,但通常是在main()中.要注意的是,在本例中由于没考虑任务换出,所以在执行os_start()前必须将所有任务槽装满.之后可以随意更换任务槽中的任务.启动任务调度器:os_start(任务槽号)调用该宏后,将从参数指定的任务槽开始执行任务调度.本例为每切换一次任务需额外开销个机器周期,用于迁移堆栈.*/#include<reg51.h>/*============================以下为任务管理器代码============================*/#define MAX_TASKS 3//任务槽个数.在本例中并未考虑任务换入换出,所以实际运行的任务有多少个,就定义多少个任务槽,不可多定义或少定义//任务的栈指针unsigned char idata task_sp[MAX_TASKS];#define MAX_TASK_DEP 12 //最大栈深.最低不得少于个,保守值为.//预估方法:以为基数,每增加一层函数调用,加字节.如果其间可能发生中断,则还要再加上中断需要的栈深.//减小栈深的方法:1.尽量少嵌套子程序2.调子程序前关中断.unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];//任务堆栈.unsigned char task_id;//当前活动任务号//任务切换函数(任务调度器)void task_switch(){task_sp[task_id] = SP;if(++task_id == MAX_TASKS)task_id = 0;SP = task_sp[task_id];}//任务装入函数.将指定的函数(参数)装入指定(参数)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.void task_load(unsigned int fn, unsigned char tid){task_sp[tid] = task_stack[tid] + 1;task_stack[tid][0] = (unsigned int)fn & 0xff;task_stack[tid][1] = (unsigned int)fn >> 8;}//从指定的任务开始运行任务调度.调用该宏后,将永不返回.#define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}/*============================以下为测试代码============================*/unsigned char stra[3], strb[3];//用于内存块复制测试的数组.//测试任务:复制内存块.每复制一个字节释放CPU一次void task1(){//每复制一个字节释放CPU一次,控制循环的变量必须考虑覆盖static unsigned char i;//如果将这个变量前的static去掉,会发生什么事?i = 0;while(1){//任务必须为死循环,不得退出函数,否则系统会崩溃stra[i] = strb[i];if(++i == sizeof(stra))i = 0;//变量i在这里跨越了task_switch(),因此它必须定义为静态(static),否则它将会被其它进程修改,因为在另一个进程里也会用到该变量所占用的地址.task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到}}//测试任务:复制内存块.每复制一个字节释放CPU一次.void task2(){//每复制一个字节释放CPU一次,控制循环的变量必须考虑覆盖static unsigned char i;//如果将这个变量前的static去掉,将会发生覆盖问题.task1()和task2()会被编译器分配到同一个内存地址上,当两个任务同时运行时,i的值就会被两个任务改来改去i = 0;while(1){//任务必须为死循环,不得退出函数,否则系统会崩溃stra[i] = strb[i];if(++i == sizeof(stra))i = 0;//变量i在这里跨越了task_switch(),因此它必须定义为静态(static),否则它将会被其它进程修改,因为在另一个进程里也会用到该变量所占用的地址.task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到}}//测试任务:复制内存块.复制完所有字节后释放CPU一次.void task3(){//复制全部字节后才释放CPU,控制循环的变量不须考虑覆盖unsigned char i;//这个变量前不需要加static,因为在它的作用域内并没有释放过CPUwhile(1){//任务必须为死循环,不得退出函数,否则系统会崩溃i = sizeof(stra);do{stra[i-1] = strb[i-1];}while(--i);//变量i在这里已完成它的使命,所以无需定义为静态.你甚至可以定义为寄存器型(regiter)task_switch();//释放CPU一会儿,让其它进程有机会运行.如果去掉该行,则别的进程永远不会被调用到}}void main(){//在这个示例里并没有考虑任务的换入换出,所以任务槽必须全部用完,否则系统会崩溃.//这里装载了三个任务,因此在定义MAX_TASKS时也必须定义为task_load(task1, 0);//将task1函数装入号槽task_load(task2, 1);//将task2函数装入号槽task_load(task3, 2);//将task3函数装入号槽os_start(0);//启动任务调度,并从号槽开始运行.参数改为,则首先运行号槽.//调用该宏后,程序流将永不再返回main(),也就是说,该语句行之后的所有语句都不被执行到.}。
51单片机多任务机制的实现策略研究
51单片机多任务机制的实现策略研究本文针对51单片机实现多任务处理这一问题,首先对该系列单片机多任务机制实现的原理和存在的主要问题进行了研究,然后提出了两种实现多任务机制的策略,最后对两种策略的优缺点进行了对比,对该型单片机的多任务机制研究有一定的借鉴意义。
标签:51单片机;多任务机制;实现策略1 多任务机制的实现原理和存在问题多任务机制就是在同一时间内能够执行多个任务的机制,在多个处理器的系统内,这样的机制不难实现,但在只有一个处理器的控制系统内,就势必要求多个任务在时间域内进行快速的切换,所以这种情况下的多任务并不是真正意义上的同一时间内处理多个任务,而是通过快速切换任务造成的错觉。
多任务系统任务的切换策略可分为协同式和抢占式。
协同式多任务系统,是指每个任务程序都可以释放对CPU的控制权,也即可将对CPU的使用权切换给其它程序,通常分为显式和隐式。
在抢占式多任务系统是指各程序不能自主的释放CPU的控制权,而是由操作系统统一行使CPU的控制权,操作系统能够从任何正在运行的程序上取走控制权,并将控制权赋予另一个程序[1]。
51单片机只有一个执行单元,这就意味着所有的程序只能按照执行单元的流程顺序执行,除中断外,其它的程序必须逐一完成,所以中断是进行多任务机制设计的关键切入点,只有通过中断的方式才能实现多任务的切换,应该重点考虑解决以下几个问题:一是并行任务的数量问题。
51单片机的8个工作寄存器的地址可映射到0-3区的工作区内,单片机可设置状态寄存器的数值进行工作区域的切换,所以多任务处理时的并行任务数量最大值不能超过4,这样可以保证任务切换的时效性。
二是任务切换的时间片分配方式。
在多任务处理时对时间的分配方式是需要重点考虑的问题。
可将时间的长度进行固定和量化,需要运行的各个任务含有不同时间长度的时间片段,每运行一个任务的时间片段,其状态寄存器中的时间片数相应的减少1,直至所有的时间片数减少为0,在时间片内任务不切换,在时间片段之间按照任务的优先级进行排序完成,全部任务完成后重新赋值。
51单片机多线程实现机制
51单片机多线程实现机制1.引言1.1 概述概述:随着科技的不断发展,现代社会对于嵌入式系统的要求也越来越高。
而在嵌入式系统中,如何实现多线程机制成为一个重要的研究课题。
多线程技术可以使得单片机在处理多个任务时更加高效和灵活,并能够提高系统的响应速度和吞吐量。
本文将主要介绍51单片机多线程实现的机制,旨在通过深入的研究和分析,探讨其原理、优势以及应用场景。
同时,本文将讨论多线程概念,并详细介绍在51单片机中如何实现多线程,包括线程的创建、调度以及资源共享等关键技术。
在本章中,我们将首先简要介绍多线程概念,阐述其对于嵌入式系统的意义和作用。
其次,我们将重点探讨51单片机多线程实现的机制,包括线程的创建与管理、线程的调度算法以及线程间的通信与同步等内容。
最后,我们将总结本章的内容,并对未来的研究方向进行展望。
通过本文的学习,读者将能够深入了解51单片机多线程实现的原理和技术,并能够在实际的嵌入式系统开发中灵活运用多线程技术,提升系统的性能和可靠性。
同时,本文也为进一步研究和探索多线程在嵌入式系统中的应用奠定了基础。
1.2 文章结构文章结构部分的内容可以包括以下内容:文章结构是指整篇文章的组织和安排方式,它对于读者能否清晰地理解文章的内容起着至关重要的作用。
在本文中,我将按照以下结构来组织我的文章:1. 引言:在引言部分,我将对本文的主题进行概述,并说明本文的目的和意义。
我会简要介绍51单片机和多线程的概念,并指出本文的重点是探讨如何在51单片机上实现多线程。
2. 正文:在正文部分,我将详细介绍多线程的概念,包括多线程的定义、特点、优点和应用领域。
我还会解释多线程在嵌入式系统中的重要性,并介绍一些常用的多线程实现机制。
在本文的重点部分,我将详细介绍如何在51单片机上实现多线程。
我会解释单片机的特点和限制,以及为什么需要在单片机上使用多线程。
我还会介绍一些常用的51单片机多线程实现方法,比如时间片轮转调度算法和互斥锁机制等。
单片机多任务的时间片方式实现
单片机多任务的时间片方式实现由于单片机具有价格低、运行要求低、易于开发、稳定可靠等优点,广泛应用于仪器仪表、家用电器、医用设备、航空航天、专用设备的智能化管理及过程控制等领域。
但是,单片机的位数少、频率低、内存小、I/O口少等缺点限制了其加载操作系统的可能。
因此,单片机不能像ARM等较高性能的处理器一样,利用加载的操作系统实现管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等功能。
但是,我们可以根据单片机所拥有的内存大小、CPU频率等因素,来为单片机量身定做一个小型的操作系统,以实现单片机的多任务运行。
1 微机实现多任务的方式微机实现多任务的方式一般是由加载的操作系统来实现的。
通过操作系统提供的函数来创建多进程或者多线程来实现多任务方式。
由于多进程耗费的资源多,而多线程的开销相对小的多,因此我们采用单片机模仿多线程的方式来实现。
操作系统创建多个线程后,将管理各个线程占用CPU的时间。
操作系统以轮换方式向线程提供CPU时间片,从而使多个线程看起来是同时运行的,而不是等待一个线程执行结束后再去执行下一个线程。
PC(Program Counter,程序计数器)是用于存放下一条指令地址的地方。
某个线程正在占用CPU时间,其实是PC值指向该线程所占的内存,并正在逐条取到CPU寄存器中进行运算。
该时间片结束后,PC值要指向下一个线程所占用的内存中,进行类似的运算。
其他线程都轮流一遍后,将又回到原来那个线程暂停的位置继续运算。
所以,从一个线程转换到另外一个线程去执行时,要保存此线程的现场,包括此线程下一条指令的位置(PC值)、此线程所使用的各个寄存器值等。
当此线程又拥有CPU时间时,将保存的PC值赋给PC寄存器,保存的各个寄存器值再赋给各个寄存器。
除了保存现场与恢复现场外,另外关键的一点是,操作系统能够改变PC 值;--;强制把使用CPU的权限从一个任务切换到另一个任务,这就用到了中断。
第12章 C51下的RTX-51实时多任务
• • • 清除信号标志函数os_clear_signal主要用于清除指定任务的信号 标志,主要用于选择所定义的输出状态。其函数原型如下: char os_clear_signal (unsigned char taskid); 其中,参数taskid指向所需要清除信号标志的任务号。清除信号 标志函数os_clear_signal的返回值如果为0,则表示信号标志清 除成功;如果为-1,则表示指向的任务不存在。使用清除信号标 志函数os_clear_signal的程序示例如下。 #include <RTX51TNY.h> void task_osclearsignal (void) _task_ 2 { „„ os_clear_signal (5); //清除任务5中的信号标志 „„ }
• • • • • • • •
12.2.3 启动任务函数
• • • 启动任务函数os_create_task主要用于启动指定任务号的任务。其函数原型如下: char os_create_task (unsigned char taskid); 其中,参数taskid指向所需要启动任务的任务号,taskid必须与任务定义时描述的数 字一致,其可取值的范围为0~15。启动任务函数os_create_task的返回值如果为0, 则表示启动任务成功;如果为-1,则表示所指向的任务不存在或者任务无法启动。使 用启动任务函数os_create_task的程序示例如下: #include <RTX51TNY.h> #include <stdio.h> void ntask(void) _task_ 3 { „„ } void task_oscreatetask (void) { „„ If(os_create_task (3)==-1) { Printf(“不能启动任务3”); } „„ } //任务3
51单片机多任务编程设计及应用
51单片机多任务编程设计及应用作者:李鹏来源:《科技风》2016年第16期摘要:在51单片机上实现多任务处理主要借鉴现代操作系统的分时处理方法,有几种不同的实现策略。
本文将基于此背景,讨论51单片机多任务编程的设计和应用,详细阐述其实现策略。
关键词:51单片机;多任务编程;操作系统单片机技术经历几十年的发展逐步走向成熟,被广泛应用于各行业。
51单片机指兼容Intel8031系统的单片机系统,在智能控制领域有广泛的引用[ 1 ]。
一、多任务执行原理51单片机的多任务执行机制主要借鉴现代操作系统的分时处理方法。
事实上,多任务执行并非多个任务同时运行,而是CPU在不同的任务之间不停的切换,每次执行一个任务的一小部分,之后迅速切换至下一个任务,并执行这个任务的一小部分,然后在切换至另一个任务,以此循环往复。
从宏观上来看,就好像多个任务在同时执行。
系统通过合理的调度,将CPU的运行时间合理分配给各个任务,每个任务轮流占用一小部分时间。
这就是现代操作系统分时机制的原理[ 3 ],也是51单片机多任务执行的基本方法。
二、实现策略51单片机多任务执行机制针对不同的应用场景和不同的单片机型号,在具体实现上有所区别,但从根本上来说都是以现代操作系统分时理论为基础来实现的。
下面将详细讲解具体如何使用时间片分配机制来实现51单片机的多任务执行。
基于时间片分配机制来实现51单片机多任务执行主要涉及三项内容。
一是待执行程序,以列表形式组织,二是运算资源,也就是CPU,三是调度器,用于统筹安排待执行程序的执行顺序,合理给各待执行程序分配运算资源。
具体的运行机制如下。
首先,在初始化阶段,待执行任务被组织为列表,然后调度器根据具体情况为各个任务分配不同数量的时间片。
然后在调度器的组织下,各个任务依次占用CPU,占用时间为各自对应数量的时间片。
通常来说,根据具体情况不同,各任务占用的时间片数目有所区别,但总数量都不会很多,CPU只执行任务的一小部分,然后迅速切换至下一个任务。
基于51单片机多任务嵌入式操作系统模型
这个操作系统很给力一种裸奔多任务模型一个网友的总结:stateMachine + timerTick + queue。
在RTOS环境下的多任务模型:任务通常阻塞在一个OS调用上(比如从消息队列取数据)。
外部如果想让该任务运转,就要向消息队列发送消息。
任务收到消息时,根据当前状态,决定如何处理消息。
这就是状态机。
任务将消息队列中的消息处理完毕后,重新进入阻塞状态。
任务在处理中,有时要延时一段时间,然后才继续工作:为了充分使用CPU,可以通过OS调用让其它任务去工作。
OS通常会提供一个taskDelay调用。
当任务调用taskDelay时,即进入阻塞状态,直到超时,才重新进入可工作状态(就绪状态)。
下面说说裸奔环境下的多任务模型:裸奔也可以多任务,但调度是由用户自主控制。
在RTOS环境下,一般提供抢占式调度。
在裸奔时,一般是任务在处理告一段落后,主动结束处理。
RTOS环境下的任务,一般处于一个while(1)循环中。
while(1){从消息队列接收消息。
如果没有,将阻塞。
处理消息。
}裸奔下的任务,一般采用查询方式:{查询是否有待处理的事件。
如果没有,返回。
如果有,根据任务的当前状态,进行处理。
处理完毕后,可能返回,也可能将待处理事件全部处理完毕后再返回。
}裸奔任务其实也处于一个while(1)循环中,只不过这个循环在任务外部。
main(){A_taskInit(); //任务的初始化B_taskInit();...while(1){A_taskProc(); //任务的处理B_taskProc();}}状态机既适用于OS环境,也适用于裸奔环境。
但在裸奔环境下,状态可能被切分得更细。
例如后面讲的如何在裸奔环境实现taskDelay()。
消息队列既适用于OS环境,也适用于裸奔环境。
在OS环境下,消息队列机制由OS提供。
在裸奔环境下,消息队列要自己来实现。
如果对队列的概念不清楚,可参考《数据结构》教材。
这个队列机制,可做成通用模块,在不同的程序中复用。
C51编程多任务程序设计的结构
C51编程:多任务程序设计的结构,纯属个人观点,希望大家借签一下,提出更好的意见。
[小师⊕] [156次] 01-7-31 下午 08:48:27C51的一些特征技巧可供利用:1.时间的模糊性.在大多数情况下,时间是具有模糊性的.象秒,分钟,小时..,从长的时间角度,即使你计秒的时间被退后0.5秒,在大多数情况下都是允许的,包括一些显示.还有象扫描键盘,你可在20MS去抖,也可在30,30MS时间去抖,这个时间范围是有一定弹性的.又如闪烁要求400MS,你可在410MS去刷新,下次在2*400MS,只要保证长的周期定时是准确的,个别时间是可推迟的。
这样的情形会在许多地方发生,这就给设计多任务程序提供了一个基础.2.消息的周期循环性.消息指系统函数(定时类的),模块之间有状态变化,模块内部有状态请求而相应产生的标志数据或变量数据,它的特点是它的遍历整个模块,直到有模块接收它后让它消失,没有模块接收时,循环一周被自身消失.举个例,有T0计数器0.1MS产生一个中断,让其他所有模块都知道,模块不能消灭它,它只能被自己消灭:void timer0(void) interrupt 1 /*T0中断*/{fSYS_100us=1;}bit fSYS_TimeNow;#define Timer0_MainLoop() {fSYS_TimeNow=0;if(fSYS_100us){fSYS_TimeNow=1;fSYS_100us=0;}}unsigned char uCount;main(){init();uCount=100;while(1){Time0_MainLoop();Task0();if(fSYS_TimeNow)Task1();Task2();if(fSYS_TimeNow){uCount--;if(uCount==0){uCount=100;Task3();}}}}这样消息具有自我生成消失发布的能力,而且使模块具有独立性(Time0_MainLoop();可放在WHILE中的任何地方而不影响它的作用).而象键盘之类产生的消息,常常是每个模块接收到它后,就使它消失,避免其他模块也接收.消息在多任务程序中的作用:相当与桥梁,使模块间既相互独立又相互连接。
51单片机多任务模拟调度程序
//OS_TASK[current_task_id-1].cpu_context.dpl=*((unsigned char*)SP),SP--;
//OS_TASK[current_task_id-1].cpu_context.dph=*((unsigned char*)SP),SP--;
{
while(1)
{
Delay10ms();
Delay10ms();
Delay10ms();
led3^=1;
}
}
void task4()//任务二
{
while(1)
{
Delay10ms();
Delay10ms();
Delay10ms();
Delay10ms();
led4^=1;
}
}
void main()
//下面是main.c文件内容
/*******main.c****************************************/
#include<reg52.h>
#include <intrins.h>
#include".\core\schedule.h"
sbit a5=P1^4;
sbit led0=P0^0;
//OSStartFlag=1;
//}
//
SP-=15;//平衡栈
TH0=(65536-10000)/256; //10ms
TL0=(65536-10000)%256;
OSTaskSchedule();//任务调度,计算下一个需要运行的任务的id
51单片机简单的多任务调度例子
51单⽚机简单的多任务调度例⼦看⼤家都在学操作系统,我也想学学。
所以想⽤51写⼀个来玩玩,发现⽐较郁闷。
弄了⼏下,不想再弄了,51弄这个没啥意思。
我⽤的89S52,除了速度慢,RAM资源太少之外,其它都还过得去。
弄了⼀点代码出来,放在那也没啥⽤,不如拿上来给新⼿看看,⼀个任务调度的雏形是什么样⼦的~~~~~~~~~这些代码没有经过优化,我只求实现任务切换的功能。
利⽤定时器2产⽣10mS的定时中断作为时钟节拍,任务切换时保存⼯作寄存器等操作嵌⼊了汇编指令,因此Task_Switch.C⽂件要做相应的设置才能编译通过。
受硬件资源和编译器的限制,有很多⽆奈。
程序只好这样写了,不管怎么说,到底是能调度起来了。
注:这⾥是⽼版本,后⾯⼜改动的新版本。
/*******************************************************本程序只供学习使⽤,未经作者允许,不能⽤于其它任何⽤途AT89S52 MCU 使⽤24M晶振时钟节拍设置为10mSmain.c fileCreated by Computer-lov.Date: 2005.10.27Copyright(C) Computer-lov 2005-2015All rigths reserved******************************************************/#include <at89x52.h>#include "task_switch.h"#include "MAIN.H"//灯#define LED1 P1_7#define LED2 P1_6#define LED3 P1_5#define LED4 P1_4#define LED5 P0_1#define LED6 P3_7#define ON_LED1() LED1=0#define OFF_LED1() LED1=1#define ON_LED2() LED2=0#define OFF_LED2() LED2=1#define ON_LED3() LED3=0#define OFF_LED3() LED3=1#define ON_LED4() LED4=0#define OFF_LED4() LED4=1#define ON_LED5() LED5=0#define OFF_LED5() LED5=1#define ON_LED6() LED6=0#define OFF_LED6() LED6=1//按钮#define KEY1 P1_0#define KEY2 P1_1#define KEY3 P1_2#define KEY4 P1_3//OS运⾏标志unsigned char OS_running;//堆栈申请unsigned char idata Stack[MAX_TASK][S_DEPTH];//运⾏时间unsigned int Running_Time;//程序控制块PCB pcb[MAX_TASK];//当前运⾏任务的ID号unsigned char Current_ID;/调⽤该函数使任务延时t个时钟节拍/ 输⼊参数:0<t<256 /// ⼀个时钟节拍为10mS ///void OS_Delay(unsigned char t){EA=0; //关中pcb[Current_ID].Suspend=1; //任务挂起pcb[Current_ID].Delay=t; //设置延迟节拍数EA=1; //开中task_switch(); //任务切换}///挂起任务/*void OS_Suspend(void){EA=0;pcb[Current_ID].Suspend=1; //任务挂起EA=1;task_switch(); //任务切换}*//创建⼀个任务///函数⼊⼝:Task_ID 分配给任务的唯⼀ID号 //// Task_Priority 任务优先级 //// Task_p 任务⼊⼝地址/// Stack_p 任务堆栈栈低地址 ///void Task_Create(unsigned char Task_ID,unsigned char Task_Priority,unsigned int Task_p,unsigned char Stack_p) {unsigned char i;for(i=0;i<S_DEPTH;i++){((unsigned char idata *)Stack_p)[i]=0; //初始化清空堆栈}((unsigned char idata *)Stack_p)[0]=Task_p; //将任务⼊⼝地址保存在堆栈((unsigned char idata *)Stack_p)[1]=Task_p>>8;pcb[Task_ID].Task_SP=Stack_p+Num_PUSH_bytes+1; //设置好堆栈指针pcb[Task_ID].Priority=Task_Priority; //设置任务优先级pcb[Task_ID].Suspend=0; //任务初始不挂起pcb[Task_ID].Delay=0; //任务初始不延时}//空闲任务,优先级最低///⼆个LED不停的闪烁 //void task_idle(void){static unsigned long int i; //使⽤static申明局部变量,避免临时变量使⽤相同地址while(1){ON_LED1(); //LED1亮for(i=0;i<0x2000;i++) //延迟{}OFF_LED1(); //LED1关for(i=0;i<0x2000;i++){ON_LED6(); //LED6闪烁很快,看起来是⼀直亮的OFF_LED6();}}}///任务1 检测按钮1 并控制LED2亮灭//void task_1(void){// static unsigned int j;while(1){ON_LED2();while(KEY1)OS_Delay(6); //等待KEY1按键按下while(!KEY1)OS_Delay(6); //等待KEY1释放OFF_LED2();while(KEY1)OS_Delay(6);while(!KEY1)OS_Delay(6);}}/任务2 检测按钮2 并控制LED3亮灭//void task_2(void){// static unsigned int j;while(1){ON_LED3();while(KEY2)OS_Delay(5);while(!KEY2)OS_Delay(5);OFF_LED3();while(KEY2)OS_Delay(5);while(!KEY2)OS_Delay(5);}}/任务3 检测按钮3 并控制LED4亮灭//void task_3(void){// static unsigned int j;while(1){ON_LED4();while(KEY3)OS_Delay(5);while(!KEY3)OS_Delay(5);OFF_LED4();while(KEY3)OS_Delay(5);while(!KEY3)OS_Delay(5);}}/任务4 控制LED5每秒闪⼀次//void task_4(void){// static unsigned int j;while(1){ON_LED5();OS_Delay(100); //LED5每隔1S闪⼀次OFF_LED5();OS_Delay(100);}}///主函数//void main(void){EA=0; //关中ET2=1; //定时器2开中断T2CON=0x00; //定时器⾃动重装模式T2MOD=0x00; //如果提⽰这⾥编译通不过,可将本⾏删除;或⾃⼰将定义添上 //因为keil⾃带的at89x52.h中没有T2MOD的定义RCAP2H=0xB1;RCAP2L=0xE0; //定时时间为10msTask_Create(0,5,(unsigned int)(void *)(&task_idle),(unsigned char)Stack[0]); //任务0初始化Task_Create(1,4,(unsigned int)(void *)(&task_1),(unsigned char)Stack[1]); //任务1初始化Task_Create(2,3,(unsigned int)(void *)(&task_2),(unsigned char)Stack[2]); //任务2初始化Task_Create(3,2,(unsigned int)(void *)(&task_3),(unsigned char)Stack[3]); //任务3初始化Task_Create(4,1,(unsigned int)(void *)(&task_4),(unsigned char)Stack[4]); //任务4初始化OS_running=0; //任务未开始运⾏Current_ID=MAX_TASK-1; //当前任务为最后⼀个任务pcb[Current_ID].Task_SP-=Num_PUSH_bytes; //调整任务堆栈指针,因为这时任务还未开始调度 //第⼀次进⼊中断时,会压栈。
单片机多任务编程方法介绍
单片机多任务编程方法介绍--Edan Lee(Email:*******************:284622554)前言从事单片机学习和工作已有多年,多年下来对单片机开发有一点自己的认识。
想将自己的积累和大家分享。
应该对初学者有一点帮助。
还有很多不足的地方,欢迎来函探讨。
因为文学功底不行所以直接进入主题。
主体内容本文的主要目的是介绍在没有操作系统的多任务编程方法。
所有例子都以51单片机为基础。
内容编排1、多任务简介2、任务分拆3、软件定时器4、系统架构5、例子多任务简介对于单片机的多任务就是CPU处理多个任务。
但是在同一个时间CPU只能处理一个任务。
在处理多任务的时候其实就是将CPU分时复用。
具体是在一个时间内处理一个任务,在接下来的时间里处理另一个任务。
这个时间一般很短,在操作系统里就是指时间片。
单片机因为资源少的原因很少会带操作系统来工作。
但是可以从多任务的基本原本出发模拟操作系统的多任务。
为了一个任务不会独占CPU,就将任务分成N个小任务。
划分原则是在任务需要等待的地方划分。
每次运行到这个任务的时候,CPU只执行其中的一个或者多个小任务。
任务分拆在一个LED闪烁的小程序中原理是点亮LED,延时,关闭LED,延时,循环。
#define LEDOn() LED_Port = 0;#define LEDOff() LED_Port = 1;sbit LED_Port = P1^0;/*不精确延时*/void Delay(unsigned int t){while(--t);}/*LED闪烁处理*/void LEDFlash(void){LEDOn();Delay(1000);LEDOff();Delay(1000);}/*主函数*/void main(void){LEDFlash();}上例中的LED闪烁处理函数可以用一个进程控制器(变量)将其分成四个部分。
void LEDFlash(void){static unsigned char step = 0; /*进程控制器*/switch(step){case 0:LEDOn();step++;break;case 1:Delay(1000);step++;break;case 2:LEDOff();step++;break;case 3:Delay(1000);step = 0;break;}}上例对于这个小程序来说变得复杂了点。
试论51单片机多任务编程设计及应用
数 控 技术
试论 5 1 单片机多任务编程设计及应用
夏青
( 湖北工程 学院新技术学院 湖北孝感 4 3 2 0 0 0 )
摘要: 智能控制 系统尤其是微型智能控制 系统中经常使用5 1 单片机, 随着计算机技术的发展, 人们对控制 系统不断提 出新的要求, 为了使5 1 单片机 支持 多任 务 功能, 人们 开始进行 5 1 单片机 多任 务编程 试验, 本文 对 多任 务编程 设计进 行概述, 并提 出5 1 单 片机 多任务 编程设 计及应 用 。
关键 词: 5 i 单 片机 多任务 编程 设计及 应 用 中图分类 号: T P 3 1 1 . 1 文献标 识码: A
文章编 号: 1 0 0 7 — 9 4 1 6 ( 2 0 1 5 ) 1 0 — 0 0 2 1 一 O 1
2. 1 . 2 延 时 过程 设计 单片机是计算机技术发展 的产物 , 经历了几十年的发展逐渐走 向成 熟 , 并被广泛应用到各个领域 中, 5 1 单片机是指那些所有 兼容 系统正常运行需要延 时等待过程作为基本支持 , 为了保证系统 I n t e l 8 0 3 1 的指令 系统 , 该系统能够控制电路的运行 , 原理是控制电路 的多任务执行功 能, 需要对该程序进行特殊设计 。 一般情况下系统 一种是短延 时, 设计 时可 以将其作为一个特殊过程 中特定点的 电位, 目前5 l 单片机被广泛应用到计算机系统多任务编 延 时分为两种 , 程设计 中, 并取得不错 的成果 。 来看待 , 例 如, 可 以将其设计成一个特殊P R OC 1 , 短延时的计数周期 以执行任务需要 的时 间作为标准 , 一个计数周期就是执行完成所有 1 5 1 单片机多任务编程设计概述 任务需要花费 的时间; 另一种是 长延 时, 设计 时可以将 其当做特 殊 1 . 1多任 务 设 计概 念 过程 , 也可 以将其 当做一个独立任务 。 设 计长延时程序 时可以利用 所谓的多任务设计 , 就是要求系统可 以在同一 时间支持多个任 定 时器 中断程序生成一个等待过程 , 具体 时间可 以根据编程确定 。 务运 行 , 由于每个系统 中含有一个处理器 , 无法在 同一 时间运行多 2 . 2串行 口设 计 个任务 , 因此要达到这 一要求 , 就要依赖 时间操作系统 , 各个任务在 串行 口在发送数据 时需要连续发送多个字节 , 因此需要花费较 短时 间内快速切换 , 从而实现多任务运行 。 在设计系统的多任务时 , 长时间, 导致 系统 同时执行 多个任务 的过程受 到阻碍 , 任务 在切 换 般都是将时间片切换任务作为基本依据 的, 但是执行任务时间的 时以时 间片 作为基础 , 系统在发送一 帧数据后 中间有 时间间歇 , 数 设计 方法 是有所差别的 , 有些是依据定时器平均分配时间 , 有些是 据总线与其他系统的协调性较差 , 因此数据传输的实时性会受到影 依据执行任务的完整过程来分配时间。 5 1 单片机任务设计就是依据 响。 为 了改善这种状 况, 要对串行 口进行特殊设 计, 假设 串行 口需要 时间片分 配任务 , 以往调度任务都采取两级 的方式 , 会 占据较多 系 发送缓冲 区数据 , 程序 会将所需要发送任务做初始化处理 , 经过一 统资源 , 为 了提升系统的运行效率 , 可以考虑直接调度任务过程 , 同 段时间等待 以后完成该任务 的调度 , 所有数据发送 完毕 以后再执行 时在各个 任务 中设置切换点 , 这些任务在运行时会 自 动切换 , 同时 其他任务过程 , 这种方 式可 以有效减少等待时 间, 提升 串 口发送数 不存 储现场数据 , 从而进一步提升 运行 效率[ 1 】 。 据 的效率 。 1 . 2 5 1 单 片机 多任 务设 计 原理 2 . 3硬 件 与软 件 的设 计 5 1 单片机多任务设计原理如下 : 首先 , 规划系统整体任务 , 将这 整个硬件 电路的设计要符合上述要求的所有功能 , 形成一个5 1 些任 务排 成 队列 , 处理器按照排列顺序来执行任务 , 完成所有任务 单机 片系统 , 采用 动态 显示 的数码管和 矩阵扫描键盘 , 由于 按键时 以后 回到第一个任务并重新执行 , 如此循环下去 ; 其次 , 无论是执行 可能会造成 系统 闪烁 , 影响执行任务的连续性 , 为了避免这种状况, 哪项 任务 , 只能执行 任务 中的一个过程 , 然后进行下一个任务 中的 最好使用专用芯片作为驱动 。 而软件设计 主要 就是 系统 编程 , 其 一 过程 , 其他 过程 需要等到再次执行任务时才能执行 , 也就是说执行 是 数码管动态显示 的设计 , 每位L E D的显示 过程都要设计 一个延 个任务时不能同时执行该任务中的两个过程 , 通过这种方式可以 时, 因此会浪费很多时间 , 为了实现在同一 时间内执行多个任务 , 执 保证 系统 一最快的速度从一个任务切换到另一个任务 ; 第三 , 系统 行一位就显示一位 , 就是显示 延时的同时执行其他 任务 ; 其二是 键 中要独立设计一个等待程序 , 因此系统在执行任务和切换任务 的过 盘扫描检测 , 如果没有按下按键, 系统就需要检测所有任务, 如果按 程 中不需要花费延时和等待的时间 ; 第四 , 要对 常规任务设计空执 下按键 , 就只检 测按键除的任务 其三是处理按键, 主要包括按下键 行过程 , 保 证系统在执行任务 的过程中 自动跳过该任务 , 使优先任 的时间 , 以及释放按键后的处理 。 为了避免 出现按键 抖动 的现象 , 如 务可 以优先执行 ; 第五 , 执行任务的过程 中直接调度过程, 因此系统 果进行持续按键操作 , 需要 经过一段 时间的延 时后再做处理 。 进行 中不需要再设置过程调度表 , 中断操作不用来处理任务 ; 第 六, 任务 多任务设计时 , 需要在系统 中插入一个短延时程序 , 将延 时部分 的 及过程在切换时直 接清 除现场数据, 不做保 留、 不传递参数 , 整个过 数 据 缓 存 。
51单片机多线程运行
51单片机多线程运行(1)硬件平台:51单片机学习板。
(2)实现功能:让一个LED闪烁。
(3)源代码讲解如下:*/#include "REG52.H"#define const_time_level 200void initial_myself();void initial_peripheral();void delay_long(unsigned int uiDelaylong);void led_flicker();void T0_time(); //定时中断函数sbit led_dr=P3^5;unsigned char ucLedStep=0; //步骤变量unsigned int uiTimeCnt=0; //统计定时中断次数的延时计数器void main(){initial_myself();delay_long(100);initial_peripheral();while(1){led_flicker();}}void led_flicker() ////第三区 LED闪烁应用程序{switch(ucLedStep){case 0:/* 注释一:* uiTimeCnt累加定时中断的次数,每一次定时中断它都会在中断函数里自加一。
* 只有当它的次数大于或等于设定上限const_time_level时,* 才会去改变LED灯的状态,否则CPU退出led_flicker()任务,继续快速扫描其他的任务,* 这样的程序结构就可以达到多任务并行处理的目的。
这就是鸿哥在所有开发项目中的核心框架。
*/if(uiTimeCnt>=const_time_level) //时间到{/* 注释二:* ET0=0;uiTimeCnt=0;ET0=1;----在清零uiTimeCnt之前,为什么要先禁止定时中断?* 因为uiTimeCnt是unsigned int类型,本质上是由两个字节组成。
基于嵌入式操作系统rtx-51的单片机多任务编程
RTX-51是Keil Vision自带的实时操作系 统,它具有小巧、速度快、系统开销小、使用 方便等优点。使用该操作系统可以方便的创建 多个任务函数,并使它们“同时”执行,这样 就为多任务系统的开发提供了高效、快速的解 决问题的方法。并且通过RTX-51能灵活、方 便的调度系统资源,实现各个任务指定是否使用代 码空间分页。
(6) RAMTOP指定片上栈顶部的地址。
3. 3 RTX-51Tiny库函数参考
(1 ) os_create_task():激活任务,并将任 务标记为就绪状态。
(2) os_delete_task():删除任务,停止任 务并从任务列表中删除。也可删除自己,将发 生任务轮换。
3 RTX-51 Tiny操作系统的使用与配置
3.1 RTX-51的使用
RTX-51使用的是定时器0的模式1来产 生一个周期性的中断来确定“时间片”,所以
在使用操作系统后,定时器0用户将无法再使 用,并且时间片轮换时间收到系统时钟的限制。 默认状态下为每10000个机器周期产生一次中 断,即若单片机釆用12MHZ的时钟频率的话, 则周期为O.OlSo
SCM Technology •单片机技术
基于嵌入式操作系统RTX-51的单片机多任务编程
文/刘进英董涛
L 本文首先对RTX-51进行了 摘 简单介绍,然后逐步介绍了使用
要 RTX-51编程的整个过程,并通过 一个简单的实例程序进一步阐述
了 RTX-51编程的方法。
运行 就绪 等待 删除 超时
表1: RTX-51任务的五个状态
文/徐上越
人工智能在各行各业的应用 摘 越来越为广泛,会展业也已经在 要 人工智能的推动下向智能化、数 ■字化和网络化转型.人工智能技
可以用硬件实现多任务的单片机
第一次看到用硬件实现多任务的单片机~~大家可以看一看,绝对不是广告了~~:)转自网络~~一次偶然的机会,看到一款国产的“双核”51单片机—LS2051 厂商样本上介绍其指令系统与MCS-51 兼容,内部功能、引脚功能、引脚排列以及引脚的电气特性与AT89C2051 全兼容,可直接替换AT89C2051 以及与其兼容的芯片。
LS2051 还支持独立或关联的两道程序(类拟双核)同时运行。
执行第1 道程序的性能是AT89C2051 的1.27 倍,第1 道和第2 道程序同时运行时的处理能力最高可达到AT89C2051 的2.55 倍。
经向生产厂商申请了LS2051 样片和下载器,收到后烧录程序,上机试用,性能稳定,未发现不兼容现象。
产品应用成功后,怀着好奇的心理,重点研究其特有的“双核”特点,两道程序同时运行?有点不可思异。
在网上搜索后,发现这方面的资料几乎是空白,于是自己编了点应用程序,下载后测试其性能,以下是一些使用总结。
1、好多中高挡的单片机硬件自带XXX 功能,LS2051 不带这些功能,这些功能用单片机大多都能模拟,但有些功能费时,有些功能不能分时操作,故一般单片机软件难于胜任某些功能,只能靠硬件来解决,LS2051 特有的“双核”特性,两道程序可同时运行。
各管各的,互不干涉,可用其中的一道程序来模拟某些硬件功能,有些功能只有高挡单片机才有,如正交编码信号输入,16位ADC 模数转换等等。
2、调试时用Keil C51,但厂商未提供第二道程序使用的头文件及库函数,为方便使用,我自已编了一个。
当主程序为C语言时,头文件为ls4051.h,二道程序使用的库函数文件原型为astart_c.a51(未编译),可直接调用。
当主程序为汇编语言时,头文件为ls4051_asm.inc。
3、第二道处理引擎设置独立的ACC、B、DPTR、PSW、R0~R7 和SP 特殊功能寄存器(这些特殊功能寄存器初始化值全部为0),但第1 道所属的通用寄存器为32 个(分为4个区域),而第2 道所属的通用寄存器为8 个(只有一个区域)。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
51单片机多任务运行最近发现有的幺弟在对系统的内核感兴趣,加上我也是部分内核的初学者,突然来兴,便用了两天写了一个简单的内核。
这个内核简单得不能再简单了,加上空格行、大括号和详细的注解只有246行,还带了4个点亮LED的任务。
至今为止我所见最简单的内核~~~ 就跟这个内核取个“多任务分时处理内核”吧!这个内核和ucos系统思想有很大的差异,但是能够帮助我们学习理解ucos系统,能够帮我们了解51的内部结构,以及大多数的单片机运行处理数据的原理~~~ 好废话就不说啦!希望我们能互相学习共同进步1、先来讲讲原理:首先,我们看书时会知道51单片机在执行中断的时候,会有以下几个步骤和几种情况。
根据KEIL的编译惯例(这个编译惯例你可以在编完程序后点仿真,里面有个后缀为.src 的文件,这个文件里面是一句C对应一句汇编,你就可以知道你编译的C代码它是怎么处理的,能帮助你学习汇编哦~~~),通常把进入中断后的所使用的通用寄存器组根据情况选择压栈。
也就是说,中断前后使用的寄存器组可能不一样,中断前可能使用0,中断中可能使用1。
如果使用的同一组寄存器,为了保存现场,KEIL就PUSH现场数据,然后POP就行啦。
但是keil很多时候不是你想象中那样,你叫它怎样他就怎样编译。
所以在程序中嵌入了少量的汇编。
其实,嵌入汇编是很简单的事情。
只要在C代码中加入#pragma asm 和#pragma endasm并在他俩的中间加入汇编就行。
别忘了还要在工程文件中添加C51S.LIB,这个文件在KEIL/C51/LIB中,这个文件也很重要,不然编译会出现警告,记得把文件类型选择为全部文件,不然看不见它。
接下来说说KEIL的中断汇编。
在C51中,中断到来时,CPU响应中断保存当前PC 指针地址压栈SP所指地址。
然后将PC指针指向中断向量地址,在中断向量地址中只有一句汇编程序:LJMP XX 意思是跳转到某地址。
因为中断后只有8个寄存器,但是你的代码量远远不只有8个寄存器能装下的。
这也就是说,响应中断后,先跳转到硬件规定的地址,再由那个地址跳转到中断程序入口。
然后,PC指针跳转到中断程序地址,开始从SP所指地址压栈ACC,B,DPH,DPL,PSW,按理说还需要压栈R0~R7,但KEIL一般是通过换通用寄存器来实现的(也就是改变RS1和RS0来实现的)。
也就说KEIL根本不压栈R0~R7。
这个怎么能行,当然不行!不保存我们就不能完全的返回先前压栈的任务啦!好吧,那我们就只有手动保存压栈,这样不就行了,简单吧!所以我们来帮它。
已经通过前面知道它在进入中断的时候已经把中断前的PC指针压栈到中断前SP所指的地址了,所以进入中断后,实际在SP中断前所指地址中已经按顺序压栈了PC低8位,PC高8位,ACC,B,DPH,DPL,PSW总共7个数据,SP是向上增长的,也就是说每压一次堆栈SP+1。
然后再把我们的R0~R7寄存器压入堆栈,这不就行啦,就保护现场所需的全部数据,就算有时R0~R7寄存器用不上我们也得加进去,为了为了保证正确的返回现场。
因此我们保存一次数据就需要7+8=15字节的堆栈,每个任务的起始地址保存一次,中间临时要保存一次,共需要15+15=30字节的堆栈。
所以定义程序空间为现场保存空间为0~29。
名字叫:unsigned char TASK_STACK[TASK_MAX][30];//程序现场保存数组。
TASK_MAX是程序个数,因为每一个程序都需要保存两次,每次15个变量来保存现场,并且51是8位的单片机所以用unsigned char。
然后就是程序现场保存数组的初始化使每个数据都是0。
首先,根据响应中断后的压栈顺序,知道了数组0位和1位保存的是中断前程序的地址,现在,我们需要把自己写的程序的起始地址给数组,以便第一次中断结束后程序从自己写的程序的起始地址开始。
我们都知道,程序的名字类似于一个指针,叫函数指针。
在c51中程序地址为16位,即一个unsigned int 型变量。
所以如果子程序名称为:TASK_1(),则,程序的地址为:(TASK_1)。
定义一个unsigned int address;然后让address=(unsigned int)(TASK_1);address中保存就是程序入口地址了。
然后把adress的高八位给TASK_STACK[TASK_MAX][0],低位给TASK_STACK[TASK_MAX][1]。
这样程序的初始化就完成了。
接下来就是由于进入中断时程序自己为我们保存了很多变量,所以我们只需要保存R0~R7就行。
嵌入汇编:进入中断时#pragma asmPUSH AR0省略。
PUSH AR7#pragma endasm中断结束之前#pragma asmPOP AR7省略。
POP AR0#pragma endasm这样,基本的任务切换就完成了。
前面看过别人的出栈过程,是:#pragma asmPOP AR7POP AR6POP AR5POP AR4POP AR3POP AR2POP AR1POP AR0POP PSWPOP DPLPOP DPHPOP BPOP ACCRETI#pragma endasm但是我是没有写全,我发现汇编中每次都编译这个东西,所以我就把他省略啦!希望我提供的这些能为大家起到帮助。
让我们共同进步!部分思想来自互联网!由于能力有限可能部分有错,或者写的不够好,希望大家多多海涵!如需要详细工程,请到我QQ留言,2745466252、程序:/****************************************************************************************************** * 描述:多任务运行程序* 主要类容:四个单独点亮LED的任务,间隔不同时间点亮* 平台:52单片机* 注释:多任务分时运行,一个任务运行os_times时间片,P2口接LED* 作者:苟强* 结束时间:2014/12/5***************************************************************************************************** */#include <reg52.h> //52单片机包含的头文件#define task_max 4 //任务的个数#define task_stack_max 30 //任务堆栈的大小#define os_start EA=1;ET0=1;TR0=1; //开启总中断,开启定时器中断,启动定时器#define os_stop EA=0;ET0=0;TR0=0; //关闭总中断,关闭定时器中断,关闭定时器unsigned int os_times; //每片轮回时间unsigned char os_stack[15]; //第一次压栈位置和大小unsigned int os_delays[task_max]; //每个任务的等待时间(延时函数)unsigned char os_i,os_is; //os_i循环执行任务的标号,os_is循环查询任务等待时间的标号unsigned char memory_data_size[task_max]; //记忆入栈时的偏移量unsigned char idata task_stack[task_max][task_stack_max]; //任务堆栈void os_init(unsigned int times); //初始化系统变量函数声明void os_creat_task(unsigned int address,unsigned char task_num); //创建任务函数声明void os_delay(unsigned int sleeps); //延时函数声明sbit LED0=P2^0; //任务1所使用的LEDsbit LED1=P2^1; //任务2所使用的LEDsbit LED2=P2^2; //任务3所使用的LEDsbit LED3=P2^3; //任务4所使用的LED/****************************************************************************************************** * void task_1()* 描述:任务1* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_1(){while(1){LED0=~LED0;os_delay(1000);}}/****************************************************************************************************** * void task_2()* 描述:任务2* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_2(){while(1){LED1=~LED1;os_delay(2000);}}/****************************************************************************************************** ***** void task_3()* 描述:任务3* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_3(){while(1){LED2=~LED2;os_delay(3000);}}/****************************************************************************************************** * void task_4()* 描述:任务4* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_4(){while(1){LED3=~LED3;os_delay(4000);}}/****************************************************************************************************** * void main() using 0* 描述:主函数* 主要内容:任务的创建和运行* 参数:无* 返回值:无* 被调用:无* 注释:无***************************************************************************************************** */void main() using 0 //主函数,using 0是配置R0~R7存放的位置{os_init(100); //初始化多任务系统os_creat_task(task_1,0); //创建任务1os_creat_task(task_2,1); //创建任务2os_creat_task(task_3,2); //创建任务3os_creat_task(task_4,3); //创建任务4SP=(unsigned char)(&os_stack); //给SP送一个地址,等一会堆栈就从这个地址开始压栈os_start; //打开中断开始任务while(1); //任务开始启动等待第一次任务就绪}/****************************************************************************************************** ***** void os_init(unsigned int times)* 描述:系统初始化* 主要内容:装定时器初值* 参数:定时器装入时间* 返回值:无* 被调用:void main()* 注释:创建时钟***************************************************************************************************** *****/void os_init(unsigned int times){unsigned char j,k; //声明变量在本函数使用os_times=times; //初始化系统中断时间片TMOD=0X01; //选择定时器模式TH0=0xff-(os_times>>8); //定时器高位装初值TL0=0xff-(os_times&0xff); //定时器低位装初值for(k=0;k<task_max;k++) //外层循环for(j=0;j<task_stack_max;j++) //内层循环task_stack[k][j]=0; //初始化堆栈为零os_i=0; //初始化当前任务for(os_is=0;os_is<task_max;os_is++) //循环初始化os_delays[os_is]=0; //每个任务的延时量清零}/****************************************************************************************************** ***** void os_creat_task(unsigned int address,unsigned chartask_num)* 描述:创建任务* 主要内容:保存任务的指针和优先级* 参数:任务地址和优先级* 返回值:无* 被调用:void main()* 注释:创建一个新任务***************************************************************************************************** *****/void os_creat_task(unsigned int address,unsigned char task_num){task_stack[task_num][0]=address&0xff; //每个任务初始PC指针位置高位task_stack[task_num][1]=address>>8; //每个任务初始PC指针位置低位}/****************************************************************************************************** ***** void os_delay(unsigned int sleeps)* 描述:延时函数* 主要内容:任务中延时调用* 参数:装入延时的时间* 返回值:无* 被调用:任务函数调用* 注释:当12M晶振时,sleeps=1延时***************************************************************************************************** *****/void os_delay(unsigned int sleeps){os_delays[os_i]=sleeps; //为任务中的延时变量赋值while(os_delays[os_i]!=0); //等于零时延时结束}/****************************************************************************************************** ***** void task_swith() interrupt 1 using 0* 描述:定时器中断0* 主要内容:实现任务的调度* 参数:无* 返回值:无* 被调用:无* 注释:定时器被占用,作为任务的调度***************************************************************************************************** *****/void task_swith() interrupt 1 using 0 //进入中断压栈保存{#pragma asm //开始使用汇编PUSH AR0 //压栈R0PUSH AR1 //......PUSH AR2 //......PUSH AR3 //......PUSH AR4 //......PUSH AR5 //......PUSH AR6 //......PUSH AR7 //压栈R7#pragma endasm //结束使用汇编memory_data_size[os_i]=(SP)-(unsigned char)(&task_stack[os_i]); //记住当前程序到起始位置的偏移量os_stop; //关闭中断,防止调度时被打断TH0=0xff-(os_times>>8); //装入定时器高位,提供时钟节拍TL0=0xff-(os_times&0xff); //装入定时器低位,提供时钟节拍os_i++;if(os_i>=task_max) //是否超出任务上限os_i=0;if(os_delays[os_i]==0) //当前任务是否结束memory_data_size[os_i]=15; //调到任务的起始位置SP=(unsigned char)(&task_stack[os_i]+memory_data_size[os_i]); //未结束则装入中断前地址for(os_is=0;os_is<task_max;os_is++) //循环每个任务if(os_delays[os_is]>0) //判断延时是否结束,如果没结束大于0os_delays[os_is]--; //延时减1os_start; //调度结束恢复开启中断#pragma asm //开始使用汇编POP AR7 //出栈R7POP AR6 //......POP AR5 //......POP AR4 //......POP AR3 //......POP AR2 //......POP AR1 //......POP AR0 //出栈R0#pragma endasm //结束使用汇编}。