手把手教你写51RTOS
自己动手写一个简易操作系统(基于51单片机)
自己动手写一个简易操作系统(基于51单片机)背景介绍大一学了51单片机,对于单片机的一些常用外设有了一定的了解。
之后,大家都在说当前最流行的单片机是stm32,所以我抽出了暑假的时间的时间学习了stm32单片机,刚开始学的时候真的很痛苦,在坚持了一个星期之后,我慢慢找到了自信,stm32单片机实际上和51是一样的,只是需要配置的寄存器多了一点。
在刚开始学的时候,经常在配置的时候无法配置完全,导致无法得到预期的实验效果,但是实际上,大家没必要过分纠结于配置,完全可以直接参考别人使用该功能的配置方式。
我们应该将心思放在功能的开发上,而不是纠结于前期简单的配置。
在熟悉使用stm32之后,开始接触操作系统ucos,过程中一直觉得自己似懂非懂,所以我在想为什么我自己不利用51写一个简易操作系统,来加深自己的理解。
初期写出的操作系统不用考虑通信等高级功能,只需要写出可以调度多个任务的操作系统即可,下面给大家介绍一下我自己写的操作系统(写的不太好,仅供大家参考)。
系统实现实现简易操作系统,主要需要实现三个函数:1.创建任务函数(将定义的任务的执行入口保存起来,供调度使用)2.任务延时函数(每一个任务执行后,都需要加入延时函数,否则低优先级的任务没有机会执行)3.中断调度函数(提供时间片调度)1 创建任务函数介绍int OSTaskCreate(unsigned int Task, unsigned char* pStack, unsigned char TaskID){unsigned char i = 0, j = 0;*pStack++ = Task & 0xFF; //低八位地址(51单片机入栈向上,出栈向下)*pStack = Task >> 8; //高八位地址os_enter_critical();TaskCB[TaskID].OSTaskStackButtom = (unsigned char)pStack + 13;TaskCB[TaskID].OSWaitTick = 0;TASK_READY(TaskID); //将该优先级的任务在全局变量中标记为就绪os_exit_critical();return 0;}入口参数:unsigned int Task ---- 任务函数的入口地址unsigned char* pStack ---- 任务函数的堆栈,主要用来保存现场参数unsigned char TaskID ----- 任务优先级2 任务延时函数void OSTimeDly(unsigned int time){TaskCB[CurrentTaskID].OSWaitTick = time; //将任务的延时时间赋值给任务控制块task_sw(); //任务调度}static void task_sw(){os_enter_critical();TASK_BLOCK(CurrentTaskID); //将当前任务的就绪状态取消#pragma asm //将现场的关键参数存入堆栈PUSH ACCPUSH BPUSH DPHPUSH DPLPUSH PSWMOV PSW,#00HPUSH AR0PUSH AR1PUSH AR2PUSH AR3PUSH AR4PUSH AR5PUSH AR6PUSH AR7#pragma endasmTaskCB[CurrentTaskID].OSTaskStackButtom = SP ; //将当前任务的堆栈位置保存,用于下次恢复该任务CurrentTaskID = Task_High(); //找出处于就绪态的最高优先级的任务SP = TaskCB[CurrentTaskID].OSTaskStackButtom;#pragma asmPOP AR7POP AR6POP AR5POP AR4POP AR3POP AR2POP AR1POP AR0POP PSWPOP DPLPOP DPHPOP ACC#pragma endasmos_exit_critical(); //离开时会把SP的当前位置的值送入PC指针,所以最高优先级的任务得以运行}3 中断调度函数void TF0_isr() interrupt 1{TH0 = 56320/256;TL0 = 56320%256;TaskCB[CurrentTaskID].OSTaskStackButtom = SP; //被中断的任务的现场已经压入堆栈,所以只需保存SPCurrentTaskID = T ask_Ready_High(); //取出就绪中优先级最高的任务SP = TaskCB[CurrentTaskID].OSTaskStackButtom;#pragma asmPOP AR7POP AR6POP AR5POP AR4POP AR3POP AR2POP AR1POP AR0POP PSWPOP DPLPOP DPHPOP BPOP ACC#pragma endasm}总结:以上三个函数就是一个简易操作系统的关键函数,大家可以自己动手实现一下。
Small RTOS51学习笔记
Small RTOS51学习笔记日前,在网上下载了一个适合在8bit 51系列的MCU上允许的RTOS。
觉得还可以,编程的时候会给我工作带来很大的方便,由于以前我编程都还没有使用过RTOS,都是自己编写一个大循环函数,不断的调用各个子函数的模式。
看了他的一些源文件,觉得挺有收获,但是也有些地方不是特别清楚陈老师的思路,哎,不过按照自己的思路修改了一下,允许起来还是没有问题。
我没有编写过特别复杂的整个项目程序,都是编写一些简单的程序模块。
还没有在实时性要求特别高的程序里面使用过这个RTOS,所以,不知道改了以后会产生什么具体的影响。
还是把自己的学习笔记,保留在这吧,免得到时到处找笔记本,还是采用自问自答的方式来写笔记吧,符合我以往的习惯。
------------------------------Q1:系统时钟节拍怎么定义,怎么调用?A1:各个任务运行都需要一个周期性的信号指示,即每一个任务允许运行的时间片是多少,必须规定好。
这个时间片就是系统时钟节拍。
在Small RTOS51 中时钟节拍服务函数是OSTimeTick()。
此函数在定义在OS_core.c文件中。
注意,由于时间片是靠定时器来产生,所以必须相对应的有一个定时器时间节拍中断服务程序OSTickISR( ),这个定时器时间中断服务程序的格式是:Void OSTickISR(void) interrupt OS_TIME_ISR可以看出,这是keil c 中中断服务程序。
其中中断入口地址OS_TIME_ISR是在OS_CPU.h文件中定义的,OS_TIME_ISR取值必须是1或者3或者其他(1为Timer0,3为Timer1,其他则根据单片机类型来定)。
由于系统必须周期性的调用OSTimeTick(),以此保证时间片产生的准时。
有两个方法来保证:1.由于定时器中断服务程序调用是系统响应中断时候就进入,不需要我们人工干涉,所以在定时器时钟中断服务程序OSTickISR()来调用OSTimeTick(); 2.由于Small RTOS51具有一个发送信号函数OSIntSendsignal (Task ID),此函数能使任务ID=Task ID的任务无条件处于就绪状态,并且优先级最高,所以在定时器时钟中断服务程序中调用函OSIntSendsignal(Task ID),使任务ID=Task ID的任务无条件处于就绪状态,退出中断以后任务ID=Task ID的任务能够立即执行,我们再在此任务中调用OSTimeTick()函数即可。
C51单片机 Small_RTOS(51)_1.12.1v_使用手册 免费
用户调用 OSQPend 和 OSSemPend 时也可能使任务睡眠,可参见相应章节。 (7)信号量
在 Small RTOS 中,用一个 0 到(OS_MAX_SEMS-1)的值做索引标识一个信 号量,所有对信号量的访问都通过它访问。Small RTOS 在使用一个信号量 之前,首先要初始化该信号量,也即调用 OSSemCreate()函数,对信号量 的初始计数值赋值,该初始值为 0 到 255 之间的一个数。如果信号量是用来 表示一个或者多个事件的发生,那么该信号量的初始值应设为 0。如果信号 量是用于对共享资源的访问,那么该信号的初始值应设为 1(例如,把它当 作二值信号量使用)。最后,如果该信号量是用来表示允许任务访问 n 个 相同的资源,那么该初始值显示应该是 n,并把该信号量作为一个可计数 的信号量使用。 信号量使用的内存空间由用系统分配。 (8)消息队列 与一般的 RTOS 不同,Small RTOS 的消息队列是以字节变量(uint8 型变 量,范围为 0 到 255)作为消息,而不是以指针指向的内容作为消息。也就 是说,消息队列发送一个消息实质是将一个 0 到 255 的数值存到消息队列中, 而不是将一个指针存到消息队列中。类似的,从消息队列中获得一个消息 就是得到一个范围为 0 到 255 的值。这个 0 到 255 的值用户可以任意解释。如 果用户确实需要多个字节作为一个整体传递,可以有两个方法:一是消息
xdata
/* 消息队列存储空间选择,keil c51 有用,必须为 idata、xdata */
/* 不是 keil c51 时它必须为空格
*/
#define EN_OS_Q_PENT
mjt-51-STC-avr程序说明
1、51-STC\A VR,双程序提供。
2、程序说明:编写环境:51利用Keil2编写。
A VR利用ICC7.13编写。
实现方案:取一张SIM卡,一定是联通或者移动的卡,清空所有短信,我是指SIM卡存储的短信,您的手机如N73可以自己存的,只要清楚SIM 卡的短信就好,(不会清怎么办,找个很早的手机插上就只能看到SIM 卡的短信,清楚后,请发送阿拉伯数字1到本卡,测试是否能够正确接收短信,如果可以,请插到SIM卡上。
月末主要卡是否有费或者至少别停机。
否则发不成短信。
)连接方式:TTL电平,需要直接TXD-TXD,RXD-RXD,GND-GND.(如果发现TC35不能工作的请串接一个1K电阻。
)232连接,需要两个公头,将2-3,3-2,5-5即可。
程序实现:1初始化。
-------------------------------------------------------------------P00亮2设置发送类型为TEXT,(PDU的51需要外接存储器太大)。
---P01亮3检测网络注册状态。
----------------------------------------------------P02亮4循环扫描接收短信。
----------------------------------------------------P03亮(该灯常亮时证明进入待发送状态)【期间如不能正常接短信,请把卡插手机上,之后,利用其他手机发送短信到该卡,即可。
之后将所有中文短信删除,保留1条以数字为内容的短信。
】5删除该短信----------------------------------------------------------------P04亮6利用手机发送0-7(只能是这8个数字)控制P2口的相应管脚。
(亲测可以实现。
)7响应之后,开始设置信息中心号,设置成功后-------------------P05亮8设置目的手机地址(就是对方的手机号)-------------------------P06亮9发送短信内容成功-------------------------------------------------------P07亮10之后返回第4步继续进行扫描--------------------------------------P03亮11如果有任何一步没有成功均返回错误指示-----------------------P21亮12我所写的是51的管脚定义法,A VR的换成ABCD对应。
rtos 使用 经验 笔记
rtos 使用经验笔记
RTOS(实时操作系统)的使用经验笔记包括以下几点:
1.理解基本概念:在开始使用RTOS之前,理解RTOS的基本概念和原理非
常重要。
例如,任务、消息队列、信号量、互斥量等。
2.选择合适的RTOS:根据项目的需求和资源限制,选择合适的RTOS。
例如,
对于资源有限的嵌入式系统,可以选择小巧的RTOS,而对于需要高度可靠性的系统,可以选择具有高可靠性的RTOS。
3.学习RTOS API:学习RTOS提供的各种API,包括任务管理、消息队列管
理、信号量管理、互斥量管理等。
这些API可以帮助您更好地管理和控制RTOS。
4.编写健壮的代码:在使用RTOS时,编写健壮的代码非常重要。
这意味着
您需要确保代码能够正确处理各种异常和错误情况,并能够在发生错误时进行适当的处理。
5.进行充分的测试:在使用RTOS之前,进行充分的测试是非常重要的。
这
可以帮助您发现并解决潜在的问题和错误,并确保您的代码在各种情况下都能够正常工作。
6.考虑可扩展性和可维护性:在编写代码时,考虑可扩展性和可维护性非常
重要。
这意味着您需要将代码组织得易于理解和维护,并考虑未来的需求和变化。
7.不断学习和探索:随着技术的不断发展和进步,RTOS也在不断发展和改
进。
因此,不断学习和探索新的技术和方法是非常重要的。
总之,使用RTOS需要一定的经验和实践,但是通过不断学习和探索,您可以更好地利用RTOS的优点,提高系统的可靠性和效率。
51简单操作系统OS
当然,这里要申明一下,这玩意儿其实算不上真正的操作系统,它除了并行多任务并行外根本没有别的功能.但凡事都从简单开始,搞懂了它,就能根据应用需求,将它扩展成一个真正的操作系统.好了,代码来了.将下面的代码直接放到KEIL里编译,在每个task?()函数的"task_switch();"那里打上断点,就可以看到它们的确是"同时"在执行的.#include <reg51.h>#define MAX_TASKS 2 //任务槽个数.必须和实际任务数一至#define MAX_TASK_DEP 12 //最大栈深.最低不得少于2个,保守值为12.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];}//任务装入函数.将指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.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;}/*============================以下为测试代码============================*/void task1(){static unsigned char i;while(1){i++;task_switch();//编译后在这里打上断点}}void task2(){static unsigned char j;while(1){j+=2;task_switch();//编译后在这里打上断点}}void main(){//这里装载了两个任务,因此在定义MAX_TASKS时也必须定义为2task_load(task1, 0);//将task1函数装入0号槽task_load(task2, 1);//将task2函数装入1号槽os_start(0);}限于篇幅我已经将代码作了简化,并删掉了大部分注释,大家可以直接下载源码包,里面完整的注解,并带KEIL工程文件,断点也打好了,直接按ctrl+f5就行了.现在来看看这个多任务系统的原理:这个多任务系统准确来说,叫作"协同式多任务".所谓"协同式",指的是当一个任务持续运行而不释放资源时,其它任务是没有任何机会和方式获得运行机会,除非该任务主动释放CPU.在本例里,释放CPU是靠task_switch()来完成的.task_switch()函数是一个很特殊的函数,我们可以称它为"任务切换器".要清楚任务是如何切换的,首先要回顾一下堆栈的相关知识.有个很简单的问题,因为它太简单了,所以相信大家都没留意过:我们知道,不论是CALL还是JMP,都是将当前的程序流打断,请问CALL和JMP的区别是什么?你会说:CALL可以RET,JMP不行.没错,但原因是啥呢?为啥CALL过去的就可以用RET跳回来,JMP过去的就不能用RET来跳回呢?很显然,CALL通过某种方法保存了打断前的某些信息,而在返回断点前执行的RET指令,就是用于取回这些信息.不用多说,大家都知道,"某些信息"就是PC指针,而"某种方法"就是压栈.很幸运,在51里,堆栈及堆栈指针都是可被任意修改的,只要你不怕死.那么假如在执行RET前将堆栈修改一下会如何?往下看:当程序执行CALL后,在子程序里将堆栈刚才压入的断点地址清除掉,并将一个函数的地址压入,那么执行完RET后,程序就跳到这个函数去了.事实上,只要我们在RET前将堆栈改掉,就能将程序跳到任务地方去,而不限于CALL里压入的地址.重点来了......首先我们得为每个任务单独开一块内存,这块内存专用于作为对应的任务的堆栈,想将CPU交给哪个任务,只需将栈指针指向谁内存块就行了.接下来我们构造一个这样的函数:当任务调用该函数时,将当前的堆栈指针保存一个变量里,并换上另一个任务的堆栈指针.这就是任务调度器了.OK了,现在我们只要正确的填充好这几个堆栈的原始内容,再调用这个函数,这个任务调度就能运行起来了. 那么这几个堆栈里的原始内容是哪里来的呢?这就是"任务装载"函数要干的事了.在启动任务调度前将各个任务函数的入口地址放在上面所说的"任务专用的内存块"里就行了!对了,顺便说一下,这个"任务专用的内存块"叫作"私栈",私栈的意思就是说,每个任务的堆栈都是私有的,每个任务都有一个自已的堆栈.话都说到这份上了,相信大家也明白要怎么做了:1.分配若干个内存块,每个内存块为若干字节:这里所说的"若干个内存块"就是私栈,要想同时运行几少个任务就得分配多少块.而"每个子内存块若干字节"就是栈深.记住,每调一层子程序需要2字节.如果不考虑中断,4层调用深度,也就是8字节栈深应该差不多了.unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP]当然,还有件事不能忘,就是堆指针的保存处.不然光有堆栈怎么知道应该从哪个地址取数据啊unsigned char idata task_sp[MAX_TASKS]上面两项用于装任务信息的区域,我们给它个概念叫"任务槽".有些人叫它"任务堆",我觉得还是"槽"比较直观对了,还有任务号.不然怎么知道当前运行的是哪个任务呢?unsigned char task_id当前运行存放在1号槽的任务时,这个值就是1,运行2号槽的任务时,这个值就是2....2.构造任务调度函函数:void task_switch(){task_sp[task_id] = SP;//保存当前任务的栈指针if(++task_id == MAX_TASKS)//任务号切换到下一个任务task_id = 0;SP = task_sp[task_id];//将系统的栈指针指向下个任务的私栈.}3.装载任务:将各任务的函数地址的低字节和高字节分别入在task_stack[任务号][0]和task_stack[任务号][1]中:为了便于使用,写一个函数: task_load(函数名, 任务号)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;}4.启动任务调度器:将栈指针指向任意一个任务的私栈,执行RET指令.注意,这可很有学问的哦,没玩过堆栈的人脑子有点转不弯:这一RET,RET到哪去了?嘿嘿,别忘了在RET前已经将堆栈指针指向一个函数的入口了.你别把RET看成RET,你把它看成是另一种类型的JMP就好理解了.SP = task_sp[任务号];return;做完这4件事后,任务"并行"执行就开始了.你可以象写普通函数一个写任务函数,只需(目前可以这么说)注意在适当的时候(例如以前调延时的地方)调用一下task_switch(),以让出CPU控制权给别的任务就行了.最后说下效率问题.这个多任务系统的开销是每次切换消耗20个机器周期(CALL和RET都算在内了),贵吗?不算贵,对于很多用状态机方式实现的多任务系统来说,其实效率还没这么高--- case switch和if()可不像你想像中那么便宜.关于内存的消耗我要说的是,当然不能否认这种多任务机制的确很占内存.但建议大家不要老盯着编译器下面的那行字"DATA = XXXbyte".那个值没意义,堆栈没算进去.关于比较省内存多任务机制,我将来会说到.概括来说,这个多任务系统适用于实时性要求较高而内存需求不大的应用场合,我在运行于36M主频的STC12C4052上实测了一把,切换一个任务不到3微秒.。
SmallRtos51学习笔记1
SmallRtos51学习笔记1从函数的执行顺序来分析Small RTOS51内核,以便了解整个内核的实现过程及运行机理。
照配套光盘里面的例程分析EXT1#include "config.h"void main(void){TMOD = (TMOD & 0XF0) | 0X01; // 定时器0初始化为16位定时器TL0 = 0x0;TH0 = 0x0;TR0 = 1;ET0 = 1;OSStart();}首先是设定定时器的基本参数,并未开启总中断。
接着便进入了OSStart函数,接下来我们看看OSStart函数都做了些什么工作。
OSStart函数属于OS_cpu_c.c文件中函数将初始化small rtos51,并开始执行任务ID为0的任务void OSStart(void){uint8 idata *cp; //新建一个指针用于后面建立堆栈空间使用uint8 i;// extern idata uint8 STACK[1]; 堆栈起始位置,在OS_CPU_A定义 CP指向这段空间cp = STACK;/* uint8 idata * data OSTsakStackBotton[OS_MAX_TASKS + 2]; 任务堆栈底部位置在config.h中有做如下定义void (* const TaskFuction[OS_MAX_TASKS])(void)={TaskA,TaskB,TaskC};将栈顶指向STACK*/OSTsakStackBotton[0] = STACK;// 堆栈底部OSTsakStackBotton[OS_MAX_TASKS + 1] = (uint8 idata *)(IDATA_RAM_SIZE % 256);/* 初始化优先级最高的任务堆栈,使返回地址为任务开始地址 */ *cp++ = ((uint16)(TaskFuction[0])) % 256;SP = (uint8) cp;*cp = ((uint16)(TaskFuction[0])) / 256;/* 初始化优先级最低的任务堆栈 */cp = (uint8 idata *)(IDATA_RAM_SIZE - 1) ;*cp-- = 0;*cp-- = ((uint16)(OSIdle)) / 256;OSTsakStackBotton[OS_MAX_TASKS] = cp;*cp-- = ((uint16)(OSIdle)) % 256;/* 初始化其它优先级的任务堆栈为其他任务分配堆栈空间*/for(i = OS_MAX_TASKS - 1; i > 0; i--){*cp-- = 0;*cp-- = ((uint16)(T askFuction[i])) / 256;OSTsakStackBotton[i] = cp;*cp-- = ((uint16)(T askFuction[i])) % 256;}/* 允许中断 */Os_Enter_Sum = 1;OS_EXIT_CRITICAL();/* 函数返回优先级最高的任务 */}其实也就是为每一个任务分配一个空间。
51例程编写学习过程
51单片机系统板例程学习编写过程7426笨蛋编序言兴趣是最好的老师,它可激发人的创造热情、好奇心和求知欲。
由百折不挠的信念所支持的人的意志,比那些似乎是无敌的物质力量有更强大的威力。
————爱因斯坦对电子由衷的喜欢是一直以来学习的动力,像很多爱好者一样自己对知识掌握到一定程度就迫不及待地想着给人分享自己的学习经验,因此本文档就产生了。
在如今,单片机已经遍布在我们生活每个角落,学习单片机的人越来越多,市场上的相关系统开发板也是遍地开花,随之而来的配套例程数不胜数。
为什么编者还是要在这里自己写例程呢?现在卖开发板的商家愈来愈多,形式也是多种多样,但其中配套例程大多数都是你抄抄我我抄抄你,造成范例程序参差不齐,有的根本不配所卖的板子,有的程序则是bug百出。
所以在这里,决定资料中的每一个程序都自己编写,这也算是把所学到的知识复习一遍,并且把其中的一些学习经验记录下来,希望能够带给大家一些帮助,能够让大家更好的理解程序吧。
由于本人水平有限,难免在文档或者例程中有错误,敬请大家批评指正。
7426笨蛋2014年8月目录一、LED闪烁灯(位操作)二、LED闪烁灯(查表法)三、按键控制LED四、LED点阵简单来回显示五、LED点阵静态显示字符六、LED点阵滚动显示字符七、按键控制点阵显示点、线八、按键控制点阵显示字符九、利用定时器制作60秒表十、蜂鸣器的驱动十一、产生不同频率驱动蜂鸣器十二、利用蜂鸣器唱歌十三、电动机的驱动十四、产生PWM驱动电动机十五、OLED屏驱动显示十六、AD转换显示十七、利用AD制作简易电压表十八、利用AD制作简易示波器十九、DS1302时钟模块制作时钟二十、按键控制OLED屏分级显示二十一、制作一个简单的综合控制显示系统一、LED闪烁灯(位操作)绝大多数初学者刚接触单片机所做的都是同一件事--点亮LED灯,无论是哪个教材都差不多。
因为学单片机,最简单就是的IO口控制高低电平,而led灯可以直观显示出IO 口的电平状态。
Small RTOS51系统配置手册
描述、注意点
设置最大任
OS_MAX_TASKS
1~16 在 Small RTOS51 中,最大任务数就是实
务数
际用户 任务数 ,此 值按照 实际任 务数量
取值
声明系统嘀 OS_TICKS_PER_SEC
一般 这个常量在当前版本没有使用,此值应
嗒频率
10~100 该根据实际情况取值
中断嵌套管 理控制
EN_OS_INT_ENTER
0、1 0:禁止编译等待信号量的函数
1:允许编译等待信号量的函数
EN_OS_SEM_ACCEPT
0、1 0:禁止编译无等待请求信号量的函数
1:允许编译无等待请求信号量的函数
EN_OS_SEM_INT_POST 0、1 0:禁止编译中断中发送信号量的函数
1:允许编译中断中发送信号量的函数
EN_OS_SEM_POST
函数 OSTimeTick()处理,必须定时
调用它
声明调用延 TIMER_ISR_TASK_ID 0~15 仅当 EN_TIMER_SHARING=0 时有效,必
时/超时处理
须根据 实际情 况设 置。并 且,这 个值应
函数的任务
该比较 小,即 任务 优先级 比较高 ,否则
会影响延时/超时的精度
Keil C51 关键
Data
空格或 Small RTOS51 时无须定义,这样才能保证
字
无定义 一些内核函数可重入。其他系统一般将
它定义为空格
1
10.2 消息队列的配置
消息队列配置一览表如表 10.2 所列。
表 10.2 消息队列配置一览表
功能
常量名
取值范围
描述、注意点
总体控制 检验控制 存储控制
51OS
第二章RTOS设计概述2.1 实时多任务操作系统(RTOS)的设计思想首先要讨论的是多任务多线程机制的程序设计思想。
一些嵌入式设备需要操作系统,例如掌上电脑、PDA、网络控制器等高性能的手持设备和移动设备。
它们往往和无线通信、互联网访问和多媒体处理等复杂而强大的功能联系在一起;对CPU要求也很高,往往是以通用CPU为原型的各种高端嵌入式处理器。
作为一个完整的操作系统,RTOS有一个可靠性很高的实时内核,将CPU 时间、中断、I/O、定时器等资源都包括起来,留给用户一个标准的应用程序接口(API);根据各个任务的优先级,合理地在不同任务之间分配CPU的时间,保证程序执行的实时性、可靠性。
内核一般都能提供任务调度和中断服务等功能,部分高档商业化产品,如Windows XP Embedded,甚至支持32位地址空间、虚拟存储管理、多进程以及嵌入式操作系统中不多见的动态链接库(DLL)。
对于这些RTOS来说,多任务实时处理不是件困难的事情。
但更多的情况下,用户使用的是另一类CPU——微控制器,即单片机,往往是按照某一流程执行单一任务。
出于成本和技术上的原因,这类软件开发多数还是基于处理器直接编写,没有选配实时多任务操作系统作为开发平台,也不需要将系统软件和应用软件分开处理。
但是在实际应用中,有时也会面临同时处理多个并行任务的要求,这就需要安排一种运行机制,来模拟RTOS中的处理方法。
1 RTOS中的设计思路单处理机多道程序系统具有如下特征:①从宏观上看,几种程序“同时运行”。
即它们先后开始了各自的运行,且均未结束。
②从微机上看,几道程序“交替执行”。
对于单处理机系统而言,它们只能轮流地占用CPU。
其实质是指几道程序在处理机中“交替执行”。
我们按照现在常用的方法,把一道程序和一个任务对应,把任务中的每个分开的、独立执行的部分称之为线程。
具体到RTOS来说,一方面,实时操作中的多任务引起的并发性和实时性,要求操作系统对资源分配具有更强的控制能力。
Small RTOS(51) 1.12.1v 说明文件
1.10.2版 2002年9月9日
更正OSWait()的Bug,在极端情况下,这个Bug可能造成可能锁死任务。
修改OSQIntPost()的Keil C51特殊代码,它会造成阅读障碍。
0.52版 2002年5月9日
用户手册增加for keil c51的一些说明。
更正函c51 移植的堆栈在某种情况下初始值错误。
0.51版 2002年3月10日
1.10.1版 2002年9月4日
更正OSTimeTick的Bug,它在keil c51中不会有问题,但移植的其它系统可能出错。
1.10版 2002年9月1日
增加Small RTOS 对消息队列(简化的)和信号量的支持;改变了开关中断的方式;增加可移植的变量定义;修正一些Bug。
Small_RTOS1.12.1是周立功公司陈明计先生编写的可以在51单片机上运行实时操作系统。
Small RTOS(51) 1.12.1v 说明文件
编写动机:
就像在嵌入系统中使用C语言替代汇编一样,在嵌入系统中使用RTOS是大势所趋。原因主要是现在在大多数情况下编程效率比执行效率重要(单片机便宜嘛)。但纵观51的RTOS,keil c51 所带的RTX Full 太大(6k多),且需要外部ram,又无源代码,很多时候不实用。RTX Tiny虽然小(900多字节),但是任务没有优先级和中断管理,也无源代码,也不太实用。而ucosII虽有源代码,但是它太大,又需要外部ram,所有函数又必须是重入函数,用在51这类小片内RAM的单片机上有点勉强。于是,我借鉴ucosII和RTX Tiny编写了Small RTOS 51,虽然它为51系列编写,但是它还是比较容易移植到其它CPU上。
手把手教你学51单片机(C语言版)
10.1.2 定时时间精准性调 整
10.1.4 数码管扫描函数算 法改进
12
Part One
11 UART串口通信
11 UART串口通信
11.1 串行通信的 初步认识
11.2 RS-232通 信接口
11.3 USB转串口 通信
11.4 IO口模拟 UART串口通信
11.5 UART串口 通信的基本应用
A
C
E
13.2 1602整屏 移动
13.4 计算器实 例
13.6 练习题
13.1 通信时序 解析
13.3 多.c文件 的初步认识
B
13.5 串口通信机制 和实用的串口例程
D
F
15
Part One
14 I^2C总线与E^2PROM
14 I^2C总线与E^2PROM
14.1 I2C时 序初步 认识
14.2 I2C寻 址模式
18 RS-485通信与Modbus协议
01
18.1 RS485通信
02
18.2 Modbus 通信协议介绍
18.2.1 Modbus协议 特点 18.2.2 RTU协议帧数 据
03
18.3 Modbus 多机通信例程
04
18.4 练习题
20
Part One
19 实践项目开发——多功能电子钟
19 实践项目开发——多功能电子钟
1.6 答读者问
03
Part One
2 点亮你的LED
2 点亮你的LED
2.1 单片机的 内部资源
2.2 单片机最 小系统
2.3 LED小灯
2.6 练习题
2.5 程序下载
Small RTOS51系统及实时系统的相关概念
Small RTOS51系统笔记及实时系统的相关概念Veiko2011-4 Small RTO51简介Small RTOS51的特点1、公开的源代码:只要遵循许可协议,任何人可以免费获得源代码。
2、可移植性:作者尽量把与CPU相关部分压缩到最小,与CPU无关的代码部分用ASIC C编写。
3、可固化:Small RTOS51为嵌入式系统设计,如有固化手段,可以嵌入到产品中成为产品的一部分。
4、占先式:Small RTOS51可以管理16个用户任务,每个任务优先级不同。
Small RTOS51总是运行就绪条件下优先级最高的任务。
5、中断管理:中断可以使正在执行的任务挂起。
如果优先级更高的任务被中断唤醒,则高优先级的任务在中断嵌套全部退出后立即执行。
中断嵌套层数可达255层。
如果需要,可以禁止中断嵌套管理。
6、RAM要求小:Small RTOS51为小RAM系统设计,因而RAM需求小,相应的系统服务也少。
Small RTOS51的动行条件首先,必须有一个基于51系列单片机的C语言编译器。
如果需要直接使用这些代码,则就需要Keil C51编译器了。
当不使用消息队列时,需要Keil C51V6.14以上版本;当使用消息队列时,需要Keil C51V6.23以上版本。
特别注意:当程序复杂时不能用9级优化。
其次,必须有一个完全兼容51的单片机;当然,也可以使用软件仿真运行。
Small RTOS51的存储器需求Small RTOS51可以在没有任何外部数据存储器的单片8051系统上运行,但应用程序仍然可以访问外部存储器。
Small RTOS51可以使用C51支持的全部存储器模块,选择记忆模型仅影响应用目标的位置。
一般来说,Small RTOS51应用程序工作于小模式下。
Small RTOS51没有按照bank switching程序来设计,不能使用code banking程序。
Small RTO51的任务堆栈的计算为了节省RAM,Small RTOS51把所有自由内部RAM分配给当前任务。
Small RTOS51中的一个典型问题及其解决方法
Small RTOS51中的一个典型问题及其解决方法Small RTOS5l是一款特地为80C51系列设计的实时操作系统(事实上应当称其为实时内核),大部分代码用编写,易于移植,非常适合于资源紧急的8位机。
同时,它也是学习操作系统原理极好的入门材料。
本人就是在学习完SmallRTOS5l的基础上进一步学习了闻名的uC/0S-II,受益颇多。
1 问题描述在将Smau RTOS51应用于试验室某项目时,发觉了一个惊奇的问题。
容易说来,就是一个以无条件方式申请消息的任务居然在没有取到消息的状况下,以指示“等待超时”的代码返回了。
在这里,首先说明一下任务申请消息的两种方式:无条件方式和超时方式。
所谓五条件方式是指任务申请消息时,假如临时没有消息可取,则任务将向来等待消息,直至取到为止;而超时方式是指任务等待消息是有时光限制的,超过所设定的最大时光,即便没有取到消息,函数也可以正常返回,只是返回值不是消息,而是“超时代码”(此方式可以防止任务因取不到消息而被永远性挂起)。
可见,假如任务以无条件方式申请消息,那么函数若能够返回,则解释任务一定是取到消息了,而返回值又怎么可能是“等待超时”呢?经过认真分析SmallRTS5l的源代码,找到了问题产生的根源。
假定有任务IDX以超时方式调用OSQPend()函数申请消息。
OSQPend()函数首先会把IDX放到此消息队列的等待任务表中,然后再去推断队列中是否有消息。
最佳状况是队列中的确有消息,则OSQPend()再把IDX从今消息队列的等待任务表中删除,接着OSQPend()返回,任务取到消息。
此刻,假定消息队列中设有消息。
那么,OSQPend()就会调用OSClearSigna1(OSRunningTaskID())和OS-Sched()这两个系统函数,迫使IDX进入休眠态,同时调度器调度下一个最高优先级的就绪任务来运行。
假定任务IDY被选中,且IDY在运行中通过调用OSQIntPost()函数向此消息队列发送了一则消息。
RTOS内功修炼记(一)——任务到底应该怎么写?
RTOS内功修炼记(一)——任务到底应该怎么写?本篇文章讲述了任务的三大元素:任务控制块、任务栈、任务入口函数,并讲述了编写RTOS任务入口函数时三个重要的注意点。
1. 知识点回顾在正式开始讲解内容之前,我会先回顾一下基础知识点,请确保你已经了解并掌握。
1.1. 任务的创建方法在用户层调用API创建一个任务,通常的流程如下:① 创建一个数组作为任务栈:#define TASK1_STACK_SIZE 512k_stack_t task1_stack[TASK1_STACK_SIZE];② 创建一个任务控制块:k_task_t task1;③ 编写任务入口函数:void task1_entry(void *arg){while(1){printf('task1 is running\r\n');tos_task_delay(1000);}}④ 调用系统API创建任务:ret = tos_task_create(&task1,'task1',task1_entry,NULL,TASK1_PRO,task1_stack,TASK1_STACK_SIZE,10);创建之后任务为就绪态(处于系统就绪队列中),等待系统调度器调度执行。
1.2. STM32内存分布请先阅读文章:•STM32 内存分配解析及变量的存储位置阅读之后,你应该要知道,STM32(Cortex-M3)中Flash和SRAM的内存空间如下:其中Flash存储空间中又分为文本段、只读数据段、复制数据段:其中SRAM存储空间中又分为data数据段、bss数据段、堆空间、栈空间:并且还要知道不同的变量类型,它对应的存储位置在哪里,如果没有,一定要阅读上文之后再回来看,这是理解之后内容的基础。
1.3. Cortex-M3/4系列内核CrortexM3/4系列内核中的寄存器组都有16个寄存器,如图所示,寄存器组通常都是CPU用于数据处理和运行控制的,希望你可以大概知道每个寄存器的作用:① R0-R12:通用寄存器,用于数据操作;② R13:栈顶指针,有两个互斥的指针MSP和PSP,在任一时刻只能使用其中一个;③ R14:连接寄存器,调用子程序时存放返回地址;④ R15:程序计数器,PC指针指向哪里,CPU就执行哪里的代码;在RTOS内核中,这16个寄存器组的值称之为「上下文环境」,即当前任务运行时这16个寄存器中的值称为上文环境,下一个任务运行时这16个寄存器的值称为下文环境,「上下文切换」就是指将这16寄存器组的值修改为下一个任务的值。
Small RTOS51中断原理及应用
Small RTOS51中断原理及应用
周伟
【期刊名称】《电脑知识与技术》
【年(卷),期】2011(007)028
【摘要】介绍嵌入式实时操作系统Small RTOS51内核的中断原理、方式及用户程序编写的要点,对编程有指导意义.
【总页数】3页(P7014-7016)
【作者】周伟
【作者单位】无锡供电公司,江苏无锡214061
【正文语种】中文
【中图分类】TP316.2
【相关文献】
1.C++中断言的原理及应用 [J], 田晓春;李刚磊;李杨
2.中断处理模式:外部中断处理和内部中断处理的差异性 [J], 江继尧
3.Small RTOS51中断原理及应用 [J], 周伟
4.多中断源共用中断申请的漏中断分析与解决办法 [J], 张宏财;沈君
5.基于Small RTOS51的操作系统移植实验设计 [J], 陈小艳;张飞
因版权原因,仅展示原文概要,查看原文内容请购买。
我的RTOS经验-51
一、编译器设置
首先,要想写操作系统,必须牵涉到汇编,这里我用的是keil的编译器,所以首先要设置好keil的一些参数,才能在c中嵌入汇编代码,方法如下:
1、在C文件中要嵌入汇编的地方用#pragma asm和#pragma endasm分隔开来,
这样编译时KEIL就知道这中间的一段是汇编了。
如:
void RunFunInNewStack(void (*pfun)(),unsigned char *pStack)
{
*pStack++=(unsigned int)pfun>>8; //将函数的地址高位压入堆栈,
*pStack++=(unsigned int)pfun; //将函数的地址低位压入堆栈,
SP=pStack; //将堆栈指针指向人工堆栈的栈顶
#pragma asm
ret;
#pragma endasm //返回并开中断,开始运行fun1()
}
2、在有加入汇编的文件中,还要设置编译该文件时的选项
Generate Assembler SRC File 生成汇编SRC文件
Assemble SRC File封装汇编文件
如下图的状态为选中,选上这两项就可以在C中嵌人汇编了。
3、为了能对汇编进行封装还要在项目中加入相应的封装库文件,在笔者的项目中编译模式是小模式所以选用C51S.LIB。
这也是最常用的。
这些库文件是中KEIL安装目录下的LIB 目录中。
加好后就可以顺利编译了。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
②使用临界区保护,可以达到上面所提到的要求;
③由此导致的实时性能与占先切换的减弱可以接受。由此可知,不被占先是任务保护局部变量的关键。既然如此,何不舍弃占先式的任务调度?这不失为一个好的出发点。针对Keil C51,非占先式任务调度,可能是一种更好的方法,更能协调51系列单片机的既定资源。下面编写这样一个系统:
void TaskC(void){
UINT8 x,v;
whlk(1){
OS_ENTER_CRITICAL();
x=GetX(); (1)
y=GetY(); (2)
//任务的其他代码
OS_EXIT_CRITICAL(); (3)
0SSleep(100); (4)
}
}
以上代码TaskC中使用了临界保护的方法来保护代码不被中断占先,确实有效地解决了RAM空间太小,不宜大量定义静态变量的问题。然而如果每个任务都采用此种结构,任务一开始,就关闭中断,将使实时性得不到保证。事实证明,这种延时是相当可观的。用一个实例来说明,如果想在系统中使用一个动态刷新的LED显示器,就难以保证显示的稳定与连续,哪怕在系统中是使用一个单独的定时器来做这一工作(进入临界区后,EA=0)。其次,这种结构事实上将占先的任务调度转化为非占先的任务调度。实际上如果在(3)与(4)之间没有碰巧发生中断并导致一个任务调度,那就可以理解为是任务主动放弃CPU的控制。如果在(3)和(4)之间碰巧产生了一个中断并导致了一个任务调度,只是执行了一次多余的任务调度而已,而且并不希望在(3)之后发生2次甚至多次的任务调度,相信读者也有这一愿望。
所有任务必须主动调用OSSleep,放弃CPU的控制权。任务调用OSSleep后,将选择优先级最高的就绪任务运行。
结 语
系统完成后,内核的代码量在400多个字节左右,占用1个定时器中断及小量的内存空间。系统设置容量为8个任务,用户实际可用任务为7个,能够满足一般需求,也达到了在小容量芯片中应用的开发要求。由于没有采用占先式的任务调度,除开全程相关的个别任务的一些局部变量外,其他局部变量已经不存在覆盖关系,由于是任务主动放弃CPU控制权,对于个别需要保护的变量单独进行处理也变得容易。在系统中,全程不需要反复地开关中断,实时性能也很好。对个别时序要求严格的外设(如DSl8820)除外。
TMOD=(TMOD&0XFO)│ 0XOl;
TL0=0xBF;
TH0=0xFC;
TRO=1;
ETO=1;
TFO=O:
OSTaskCreate(TaskA,NULL,0);
OSTaskCreate(TaskB.NULL,1);
OSTaskCreate(TaskC,NULL,2);
①局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);
②在编译链接时,即已经完成局部变量的定位;
③如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
正是由于以上的原因,在Keil C51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重人的。那么在Keil C5l环境下,如何使其函数具有可重人性呢?下面分析在实时操作系统下面,任务的基本结构与模式:
OSStart();
上面这段代码中,所有任务建立后,便调用OSStart()开始任务调度。OSStart()是一个宏定义,如下所示:
#deflne OSStart() d0{\
OSTaskCreate(TaskIdle,NULL,OS_MAX_TASKS);\
EA=l:\
除此之外,还可以发现任务的一个特点:当任务从(1)重新开始时,局部变量x和y是一个什么值并不在乎,即x和y即使在(3)之后改变了,也已经不再重要,不会影响程序的正确性。其实这一特点也是大部分任务,至少是大部分任务的大部分局部变量的一个共性——如果任务在整个执行过程中,不会(被占先)放弃CPU控制权,则其局部变量大多数并不需要进行特别的保护,即其作用域只是任务的当次执行,针对上面的代码,就是临界保护区内的代码区域。
return;\
}while(O)
首先,它创建了一个空闲任务并打开中断,然后便返回。返回到哪里了呢?我们知道,空闲任务是优先级最低的任务,当调OSTaskCreate建立时,会将其地址填人到SP的位置,并把SP向后移动2个字节(见图2及说明),因而此时处在堆栈顶端的,一定是空闲任务Taslddle。这就使得这里的return一定会返回到空闲任务。至此,系统进入正常运行状态。
//其他实际的用户任务处理代码
)while(1);
void Funcl(){
UlNT8 v al_fa;
//其他变量的定义
//函数的处理代码
}
在上面的代码中,TaskA与TaskB并不存在直接或间接的调用关系,因而其局部变量val_a与val_b便是可以被互相覆盖的,即其可能都被定位于某一个相同的RAM空间。这样,当TaskA运行一段时间,改变了val_a后,TaskB取得CPU控制权并运行时,便可能会改变val_b。由于其指向相同的RAM空间,导致TaskA重新取得CPU控制权时,val—a的值已经改变,从而导致程序运行不正确,反过来亦然。另一方面,Funcl()与TaskB有直接的调用关系,因而其局部变量val_fa与val_b不会被互相覆盖,但也不能保证其局部变量val_fa不会与TaskA或其他任务的局部变量形成可覆盖关系。
将val_a、val_b以及val_fa等局部变量定义为静态变量(加上static指示符)可以解决这一问题。但问题是,定义大量的static类型变量,将导致RAM空间的大量占用,有可能直接导致RAM空间不够用。尤其是在一些小容量的单片机内,一般只有128或256字节,大量的静态变量定义,在如此小的RAM资源状况下显然就不太合适了。由此而有了另一种的解决方法,如下代码所示:
2 实时操作系பைடு நூலகம்要不要占先
由上面的分析,如果要保持一个函数可重入,就得使用静态变量,系统的RAM资源将是一个严峻的考验;如果使用临界区来保护运行环境,系统的实时性又得不到保证,而且有将占先式任务调度转为非占先任务调度之虞。显然,使用静态变量简单,但有更多的不适用性,对将来功能的调整也是一个阻碍,一般不被采用。那么,就只能从环境保护上来下功夫了,但是果真只能以进入临界区牺牲系统的实时性来保证任务不被占先?下面看看临界保护这一方法的基本思路:
3.1 堆栈的初始化与任务的创建
堆栈的初始化实际是初始化0STaskStackBotton数组,并将当前任务指定为空闲任务,下一个运行任务指定为最高优先级任务,即优先级为零的任务。初始化时,将SP的值存人OSTaslkStackBotton[O],SP+2的值存入OSTaskStacKBotton[1],依此类推。而任务是调用0STa-skCreate函数建立的。实际上只是将任务(假设为n号任务)的地址填人到对应OSTaskStackBotton[n]所指向的位置,并将SP向后移动2个字节,如图2所示。
3.2 任务的切换
任务的切换分两种情况,在当前任务优先级低于下一个取得CPU控制权的任务时,将下一个取得CPU控制权的任务的栈顶到当前任务的栈顶之间的内容向RAM空间的高端搬移,以空出全部的RAM空间作下一个任务的堆空间,同时更新对应的OSTaskStackBotton,使其指向新的正确任务的堆栈栈底。如果当前任务的优先级高于下一个任务的优先级,则作相反的搬移,如图3与图4所示。
①使用非占先式任务调度;
②可以在小容量的芯片中使用,开发目标是,即使是8051这样小的芯片,也可使用这个实时操作系统;
③支持优先级调度,尽可能保证其实时性。
3 实时操作系统的实现
基于以上的分析与目的,近日完成了这个操作系统。在堆栈上借用RTx的管理方法,即当前任务使用全部的堆空间,如图1所示。
vold TaskA(void*ptr){
UINT8 vaL_a;
//其他一些变量定义
do{
//实际的用户任务处理代码
}while(1);
}
void TaskB(void*ptr){
UINT8 vaLb;
//其他一些变量定义
do{
Funcl();
1 Keil C51与重入问题
说到实时操作系统,就不能不考虑重入问题。对于PC机这样的大内存处理器而言,这似乎并不是一个很麻烦的问题,借用μC/OS—II RTOS的说法,即要求在重入的函数内,使用局部变量。但5l系列单片机堆栈空间很小,仅局限在256字节之内,无法为每个函数都分配一个局部堆空间。正是由于这个原因,Keil C51使用了所谓的可覆盖技术:
为什么要以这样一种规律而不是其他的方式呢?这是由于在任务建立后,还未进行任务调度之前,各任务的堆栈实际上是它们自身的地址,因而其堆栈深度为2,为了程序的简便而直接填入。
void main(void){
OSInit(); /*初始化OSTaskStackBcBotton队列*/