单片机学习 截取自网页.docx
《单片机学习 截取自网页.docx》由会员分享,可在线阅读,更多相关《单片机学习 截取自网页.docx(84页珍藏版)》请在冰豆网上搜索。
单片机学习截取自网页
从学校毕业到工作,我已经学习单片机有六七年了,可是一直还处于入门阶段吧,没能深入进去,从51单片机到AVR单片机学的都是一些基础的东西,真正到应用到一个项目就感觉无头绪,不知如何下手,于是就想学习一下多任务操作系统UCOSII,但是毕竟是在8位机上运行,增加了代码量不说,学习起来也不是那么容易,任务的划分,单元功能的编写也很不好理,自从看了《从单片机初学者迈向单片机工程师(一份正真的让你成为工程师的贴子)》这份贴后,瞬间让我感觉舒畅,让我重新点燃了对单片机项目开发的兴趣,感谢这些无私帮助别人的工程师们,今天我把这个贴子的资料重新整理了一份,并且再加上一份在网上找的仿真实例传上来,希望能帮助像我一样的学了一段时间单片机还感到困惑,感到无助的朋友们
另外我还在希望工程师高手们能再讲得深入些,如何在Easy51RTOS下编写各种芯片的驱动,为谢!
!
!
(原文件名:
未命名.GIF)
引用图片
(原文件名:
未命名1.GIF)
引用图片
从单片机初学者迈向单片机工程师经过仔细的审核编排,修改了中间的错误原码,看不清的流程图也经过了处理,错别字也改了不少!
从单片机初学者迈向单片机工程师ourdev_629744IQEIMW.rar(文件大小:
1.90M)(原文件名:
1.rar)
第二章例程ourdev_629746M0CEMT.rar(文件大小:
17K)(原文件名:
第二章例程.rar)
第三章例程ourdev_629748SUPXQU.rar(文件大小:
23K)(原文件名:
第三章例程.rar)
第五章例程ourdev_629749FY3046.rar(文件大小:
57K)(原文件名:
第五章例程.rar)
(原文件名:
1.GIF)
引用图片
原来的基于Easy51RTOS的Mini51板万年历仿真版
原码重新编译后不能运行,我去掉了原来仿真电路中的CPLD元件,部份代码已修改完成,经编译完全可以运行
基于Easy51RTOS的Mini51板万年历仿真版ourdev_629750U8VCS2.rar(文件大小:
126K)(原文件名:
基于Easy51RTOS的Mini51板万年历仿真版.rar)
C51编程多任务程序设计的结构ourdev_629751A8HA6V.doc(文件大小:
20K)(原文件名:
C51编程多任务程序设计的结构.doc)
一种裸奔多任务模型
一个网友的总结:
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提供。
在裸奔环境下,消息队列要自己来实现。
如果对队列的概念不清楚,可参考《数据结构》教材。
这个队列机制,可做成通用模块,在不同的程序中复用。
消息队列用于缓冲事件。
事件不知道什么时候会到来,也不能保证来了就能迅速得到处理。
使用消息队列,可以保证每个事件都被处理到,以及处理顺序。
一般在两种情况下会用到消息队列:
存储外部事件:
外部事件由中断收集,然后存储到队列。
串口接收程序中的接收循环缓冲区,可理解为消息队列。
任务间通讯:
一个任务给其它任务发送消息。
timerTick,就是系统中的时钟基准。
OS中总是有一个这样的基准。
在裸奔时,我们要用一个定时器(或RTC或watchdog)来建立这个时间基准。
一个tick间隔可以设置为10ms(典型RTOS的缺省设置)。
让定时器10ms中断一次,中断发生时给tickNum++。
以前,我在定时器中断中设置1S标志、200ms标志等等。
时间相关的任务根据这些标志判断是否要执行。
近来,一般让任务直接去察看tickNum。
两次相减来判断定时是否到达。
也可以在系统中建立一个通用定时器任务,管理与不同任务相关的多个定时器;在定时到达时,由定时器任务去调用相应的callback。
系统时钟基准是所谓“零耗时裸奔”的基础。
timerTick的分辨率,决定了只适于于较大的时间延时。
在做时序时的小延时,用传统方法好了。
OS中的taskDelay()在裸奔环境下的一种实现:
OS环境:
voidxxxTask(void)
{
while
(1){
//waitEvent
//dostep_1
taskDelay(TIME_OUT_TICK_NUM);
//dostep_2
}
}
裸奔环境:
voidxxxTask(void)
{
staticunsignedinttaskStat=STAT_GENERAL;//任务状态变量
statictimer_tstartTick;
timer_tcurrTick;
if(taskStat==STAT_GENERAL)
{
//checkevent
//ifnoevent
return;
//dostep_1
startTick=sysGetTick();//sysGetTick()就是察看系统时间
taskStat=STAT_WAIT;
return;
}
elseif(taskStat==STAT_WAIT)
{
currTick=sysGetTick();//sysGetTick()就是察看系统时间
if((currTick-startTick)>=TIME_OUT_TICK_NUM)
{
//dostep_2
taskStat=STAT_GENERAL;
return;
}
else
return;
}
}
老生常谈---一种裸奔多任务模型ourdev_629752P0O6JH.txt(文件大小:
4K)(原文件名:
老生常谈---一种裸奔多任务模型.txt)
C51多任务编程思想ourdev_629753EWA0LM.pdf(文件大小:
143K)(原文件名:
C51多任务编程思想.pdf)
基于51单片机的C语言多任务操作完美版ourdev_629754PETS4B.rar(文件大小:
3K)(原文件名:
基于51单片机的C语言多任务操作完美版.rar)
Easy51RTOS的原理
//Easy51RTOS操作系统头文件
#include"os_cfg.h"
#include"functns.h"//常用一些功能函数
unsignedcharTempBuffer[6];//显示温度字符串
unsignedcharstr2[12]={'','','',0,0,0,0,0,0,0xdf,0x43,0};
//任务0:
测温度送显
voidtask0(void)
{
temp=ReadTemperature();
IntToStr(temp,TempBuffer);
str2[3]=TempBuffer[0];
str2[4]=TempBuffer[1];
str2[5]=TempBuffer[2];
str2[6]=TempBuffer[3];
str2[7]=TempBuffer[4];
str2[8]=TempBuffer[5];
GotoXY(0,1);
Print(str2);
delay_nms(300);
}
//任务1:
键盘扫描,LCD显示
voidtask1(void)
{
if(CHANGE==0)//判断change温度键是否按下
{
set_temp=key_set();//设定需要更改的温度值
if(set_temp{
fengshan();//设定的温度<实际温度,则打开电机风扇
}
elseif(set_temp>temp)
{
dianlu();//若大于,则打开电炉(这里用LED模拟一下)
}
}
}
//任务2
voidtask2()
{
}
//任务3
voidtask3()
{
}
//任务4
voidtask4(void)
{
}
//任务5
voidtask5(void)
{
}
//任务6
voidtask6()
{
}
//任务7
voidtask7()
{
}
//main主函数
voidmain(void)
{
OS_InitTimer0();
EA=1;
LCD_Init();
LCD_w_data(1,1,Temp_Str);
LCD_w_data(2,1,Key_Str);
while
(1)
{
if(OS_Delay[0]==0){task0();OS_Delay[0]=100;}//温度测量,每秒1次
if(OS_Delay[1]==0){task1();OS_Delay[1]=10;}//键盘扫描,键值存储
if(OS_Delay[2]==0){task2();OS_Delay[2]=100;}//读出存储的键值,LCD显示
if(OS_Delay[3]==0){task3();OS_Delay[3]=50;}
if(OS_Delay[4]==0){task4();OS_Delay[4]=100;}
if(OS_Delay[5]==0){task5();OS_Delay[5]=60;}
if(OS_Delay[6]==0){task6();OS_Delay[6]=70;}
if(OS_Delay[7]==0){task7();OS_Delay[7]=80;}
Delay(50);
//Taskturn;
}
}
//定时中断服务
voidOS_Timer0(void)interrupt1using2
{
uchari;
//CRY_OSC,TIME_PER_SEC在easycfg.h中配置
TH0=255-CRY_OSC/TIME_PER_SEC/12/256;
TL0=255-CRY_OSC/TIME_PER_SEC/12%256;
//每节拍对任务延时变量减1,减至0后,任务就绪。
for(i=0;i{
if(OS_Delay[i]!
=0)OS_Delay[i]--;
}
//Runing(On);
}
//和传统的前后感觉基本上是一样的…
//唯一的优点呢,是感觉OS_Delay[n]数组起到了分配各
Easy51RTOS的原理ourdev_629755MEIQGP.txt(文件大小:
3K)(原文件名:
Easy51RTOS的原理.txt)
基于51单片机的C语言多任务操作完美版
/*
1.本程序不使用任何汇编指令
2.由定时器T0产生中断,切换进程
3.由于中断或调用子程序,要把PC堆栈,故可以以SP为基址的地方找到PC
4.中断或子程序返回,要把SP出栈给PC,故可以操作SP改变程序入口
5.本程序经调试运行电路图已上传
6.程序编译是会有一个警告提示,为正常现象,因为保存R0-R7时,重新定义地址,
出现地址覆盖的警告提示。
7.用户以此模板写程序只需写用户的进程子程序和用户初始化子程序,并把各进程参数
放在规定地方,各程序放在规定地方就可以;所有的任务调度已处理好。
*/
//头文件
#include
//#include
//#include
//宏定义
#defineucharunsignedchar
#defineuintunsignedint
#defineTN65436
//进程1,2,3执行时间之比为T1:
T2:
T3(时间单位us)
#defineTN155536//1个进程循环周期内进程1执行的时间T1usTN1=(65536-T1)
#defineTN255536//1个进程循环周期内进程2执行的时间T2usTN2=(65536-T1)
#defineTN355536//1个进程循环周期内进程3执行的时间T3usTN3=(65536-T1)
//
#defineN14//进程1的延时参数
#defineN24//进程2的延时参数
#defineN34//进程3的延时参数
idatauchartemp[8]_at_0x00;//R0--R7
uchartempbf1[8];//用于保存R0--R7进程1
uchartempbf2[8];//用于保存R0--R7进程2
uchartempbf3[8];//用于保存R0--R7进程3
//定义全局变量
uintaddress1,address2,address3;
uchartest1_1=0,test2_1=0,test3_1=0,PID=1;
//各进程的标志位,是否为第一次执行,0第一次,非0非第一次;PID进程号;
uintac1,ac2,ac3;//,PC_Next;各进程的初始地址寄存器.
//test1的参数由于进程切换时没有保存普通变量,
//所以各进程的普通参数需要定义成全局变量.
uintm1,i1,j1,k1;
uchartable1[4];
//在此加入用户进程1参数
//test2的参数
intm2,i2,j2,k2;
uchartable2[4];
//在此加入用户进程2参数
//test3的参数
intm3,i3,j3,k3;
uchartable3[4];
//在此加入用户进程1参数
//声明
//unsignedintGet_Next_PC(void);//调用子程序,获取PC
voidchushihua(void);//初始化函数
voidyonghuchushihua(void);//用户初始化函数
voidtest1(void);//进程一
voidtest2(void);
voidtest3(void);
//main函数
voidmain(void)
{
//PC_Next=Get_Next_PC();
chushihua();
ac1=(unsignedint)(test1);//获取进程1的入口地址
ac2=(unsignedint)(test2);//获取进程2的入口地址
ac3=(unsignedint)(test3);//获取进程3的入口地址
yonghuchushihua();
TR0=1;
while
(1);
}
//初始化时钟
voidchushihua(void)
{
TMOD=0x01;//
EA=1;
ET0=1;
TH0=TN/256;
TL0=TN%256;
}
//中断处理,进程调度
voidtime0()interrupt1using1
{ucharib;
TR0=0;
//进程顺序分配
PID++;
if(PID==4)
{PID=1;}
//进程调度
switch(PID)
{
case1:
{
if(test3_1!
=0)//第一次否?
{//保存现场,还回地址
address3=*((unsignedchar*)(SP-4));//PC的高字节
address3<<=8;
address3+=*((unsignedchar*)(SP-5));//PC的低字节
table3[0]=*((unsignedchar*)(SP));//现场保护
table3[1]=*((unsignedchar*)(SP-1));//现场保护
table3[2]=*((unsignedchar*)(SP-2));//现场保护
table3[3]=*((unsignedchar*)(SP-3));//现场保护
for(ib=0;ib<=7;ib++)//保护R0--R7
{
tempbf3[ib]=temp[ib];
}
}
if(test1_1==0)//第一次执行
{//执行新进程,恢复现场
test1_1=1;
*((unsignedchar*)(SP-4))=ac1>>8;//PC的高字节
*((unsignedchar*)(SP-5))=ac1&0x00ff;//PC的低字节
}
else//非第一次执行
{//执行新进程,恢复现场
*((unsignedchar*)(SP-4))=address1>>8;//PC的高字节
*((unsignedchar*)(SP-5))=address1&0x00ff;//PC的低字节
*((unsignedchar*)(SP))=table1[0];//现场恢复
*((unsignedchar*)(SP-1))=table1[1];//现场恢复
*((unsignedchar*)(SP-2))=table1[2];//现场恢复
*((unsignedchar*)(SP-3))=table1[3];//现场恢复
for(ib=0;ib<=7;ib++)//恢复R0--R7
{
temp[ib]=tempbf1[ib];
}
}
TH0=TN1/256;
TL0=TN1%256;
TR0=1;
}break;
case2:
{
if(test1_1!
=0)//第一次否?
{//保存现场,还回地址,否
address1=*((unsignedchar*)(SP-4));//PC的高字节
address1<<=8;
address1+=*((unsignedchar*)(SP-5));//PC的低字节
table1[0]=*((unsignedchar*)(SP));//现场保护
table1[1]=*((unsignedchar*)(SP-1));//现场保护
table1[2]=*((unsignedchar*)(SP-2));//现场保护
table1[3]=*((unsignedchar*)(SP-3));//现场保护
for(ib=0;ib<=7;ib++)//保护R0--R7
{
tempbf1[ib]=temp[ib];
}
}
if(test2_1==0)//第一次
{//执行进程2,恢复现场
test2_1=1;
*((unsignedchar*)(SP-4))=ac2>>8;//PC的高字节
*((unsignedchar*)(SP-5))=ac2&0x00ff;//PC的低字节
}
else//非第一次
{//执行进程2,恢复现场
*((unsignedchar*)(SP-4))=address2>>8;//PC的高字节
*((unsignedchar*)(SP-5))=address2&0x00ff;//PC的低字节
*((unsignedchar*)(SP))=table2[0];//现场恢复
*((unsignedchar*)(SP-1))=table2[1];//现场恢复
*((unsignedchar*)(SP-2))=table2[2];//现场恢复
*((unsignedchar*)(SP-3))=table2[3];//现场恢复
for(ib=0;ib<=7;ib++)//恢复R0--R7
{
temp[ib]=tempbf2[ib];
}
}
TH0=TN2/256;
TL0=TN2%256;
TR0=1;
}break;
case3:
{
if(test2_1!
=0)
{//保存现场,还回地址
address2=*((unsignedchar*)(SP-4));//PC的高字节
address2<<=8;
address2+=*((unsignedchar*)(SP-5));//PC的低字节
table2[0]=*((unsignedchar*)(SP));//现场保护
table2[1]=*((unsignedchar*)(SP-1));//现场保护
table2[2]=*((unsignedchar*)(SP-2));//现场保护
table2[3]=*((unsignedchar*)(SP-3));//现场保护
for(ib=0;ib<=7;ib++)//保护R0--R7
{
tempbf2[ib]=temp[ib];
}
}
if(test3_1==0)
{//执行进程3
test3_1=1;
*((unsignedchar*)(SP-4))=ac3>>8;//PC的高字节
*((unsignedchar*)(SP-5))=ac3&0x00ff;//