51单片机多任务运行

合集下载

试论51单片机多任务编程设计及应用

试论51单片机多任务编程设计及应用

试论51单片机多任务编程设计及应用51单片机是一种基于哈弗曼编码技术的单片机,广泛应用于嵌入式系统中。

随着嵌入式系统复杂度的不断提高,多任务编程设计已经成为了一种必要的需求。

本文将试论51单片机多任务编程设计及应用。

多任务设计可以使嵌入式系统在同时运行多个任务时,具有更高的系统效率和响应速度。

在多任务环境中,各任务可同时执行,每个任务都具有自己的优先级和独立的数据空间。

对于51单片机而言,要实现多任务编程设计,需要考虑以下几个方面:1.任务划分首先需要将任务划分为多个单独的任务。

根据任务的优先级和时间限制,进行合理的划分和安排。

2.任务调度在多任务环境中,需要进行任务调度,确定任务执行的优先级和时间片。

一般采用时间片轮转方式,即每个任务分配一个时间片,时间到了就切换到下一个任务。

3.任务间通信不同任务之间需要共享数据和通信,可采用共享变量或消息机制等方式进行任务间通信。

在实际应用中,多任务编程设计可应用于多种场景,例如自动控制、机器人控制、传感器采集等。

下面以自动控制为例,介绍51单片机多任务编程设计及应用。

自动控制系统通常包括数据采集、数据处理和执行控制三个任务。

数据采集任务负责采集传感器数据,数据处理任务负责对采集数据进行处理和分析,执行控制任务负责根据处理结果执行相应的控制操作。

在这个模块中,执行控制任务的优先级最高,数据采集任务的优先级最低。

首先应进行任务划分,将数据采集、数据处理和执行控制分别划分为独立的任务。

接着需要确定任务调度的方式和时间限制,可采用时间片轮转方式,每个任务分配一个时间片,执行控制任务的时间片最短,数据采集任务的时间片最长。

最后需要进行任务间通信,数据采集任务将采集到的数据存入共享变量中,数据处理任务通过共享变量获取数据进行处理,执行控制任务通过共享变量获取处理结果进行操作。

综上所述,对于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单片机实现多任务处理这一问题,首先对该系列单片机多任务机制实现的原理和存在的主要问题进行了研究,然后提出了两种实现多任务机制的策略,最后对两种策略的优缺点进行了对比,对该型单片机的多任务机制研究有一定的借鉴意义。

标签:51单片机;多任务机制;实现策略1 多任务机制的实现原理和存在问题多任务机制就是在同一时间内能够执行多个任务的机制,在多个处理器的系统内,这样的机制不难实现,但在只有一个处理器的控制系统内,就势必要求多个任务在时间域内进行快速的切换,所以这种情况下的多任务并不是真正意义上的同一时间内处理多个任务,而是通过快速切换任务造成的错觉。

多任务系统任务的切换策略可分为协同式和抢占式。

协同式多任务系统,是指每个任务程序都可以释放对CPU的控制权,也即可将对CPU的使用权切换给其它程序,通常分为显式和隐式。

在抢占式多任务系统是指各程序不能自主的释放CPU的控制权,而是由操作系统统一行使CPU的控制权,操作系统能够从任何正在运行的程序上取走控制权,并将控制权赋予另一个程序[1]。

51单片机只有一个执行单元,这就意味着所有的程序只能按照执行单元的流程顺序执行,除中断外,其它的程序必须逐一完成,所以中断是进行多任务机制设计的关键切入点,只有通过中断的方式才能实现多任务的切换,应该重点考虑解决以下几个问题:一是并行任务的数量问题。

51单片机的8个工作寄存器的地址可映射到0-3区的工作区内,单片机可设置状态寄存器的数值进行工作区域的切换,所以多任务处理时的并行任务数量最大值不能超过4,这样可以保证任务切换的时效性。

二是任务切换的时间片分配方式。

在多任务处理时对时间的分配方式是需要重点考虑的问题。

可将时间的长度进行固定和量化,需要运行的各个任务含有不同时间长度的时间片段,每运行一个任务的时间片段,其状态寄存器中的时间片数相应的减少1,直至所有的时间片数减少为0,在时间片内任务不切换,在时间片段之间按照任务的优先级进行排序完成,全部任务完成后重新赋值。

51单片机多线程实现机制

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实时多任务

第12章  C51下的RTX-51实时多任务
12.2.2 清除信号标志函数
• • • 清除信号标志函数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.psw=*((unsigned char*)SP),SP--;
//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
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 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 //结束使用汇编}。

相关文档
最新文档