0

关于投票
做单片机开发,必须跳出芯片型号!

如题!很多人在谈单片机开发时,经常会将重点放在某一款芯片上,其实这是非常不理智的一种做法,芯片只是一种工具,方法才是根本。在做项目的时候,应该关注的是项目的功能和性能要求,然后采用反推法来分析要实现这些功能需要什么样的资源,采用什么方法或算法,这才是关键!特别是在解决各种问题时,设计思想,原理和方法是最重要的,而且是通用的,只要确定了思路和方法,用何种MCU只是实现和表达的方法不同而已。因此,我个人认为,MCU本身的资料若不做项目是完全不需要看的,毫无意义,但是,一些理论上的知识和基本知识则是必须要了解的,比如数据结构,数据库(不是指Foxbase,Orcale),操作系统(不是Windows,Linux),模拟和数字电路基础等,这些才是有可能真正提高你能力和设计水平的东西,更重要的是有助于提高你的创新的能力!

红***部分为 alanfang's Blog 内容,以下补充个人另解:

确实,搞系统设计,暂且不谈什么windows这么大的主板级系统设计,就以8位或16位单片机而言,道理一样,不能被仅仅查到的单片机作为核心然后向外扩展设计,想想现在是什么时代拉,哈哈,转眼的功夫,更高级的芯片横空出世咯~~~~

就像 alanfang's Blog 所说,从系统的功能性能要求入手,暂且抛开单片机型号,只有这样才能使设计的系统不为单片机所累,美其名曰“移植性更强,适应性更广”!

但对于小的系统,尤其是考虑成本且单片机外围电路很少的情况下,以上原则可以不理睬,因为好多单片机已经固化若干功能,具有较高的性价比。

系统分类: 单片机
用户分类: 51MCU
标签: 无标签
来源: 无分类
发表评论 阅读全文(1289) | 回复(3)

1

关于投票
构造一个51单片机的实时操作系统
构造一个51单片机的实时操作系统
长沙市希麦特电子科技有限公司 彭光红

目前,大多数的产品开发是在基于一些小容量的单片机上进行的,51系列单片机,是我国目前使用最多的单片机系列之一,有非常广大的应用环境与前景,多年来的资源积累,使51系列单片机仍是许多开发者的首选。针对这种情况,近几年涌现出许多基于51内核的扩展芯片,功能越来越齐全,速度越来越快,也从一个侧面说明了51系列单片机在国内的生命力。

多年来我们一直想找一个合适的实时操作系统,作为自己的开发基础。根据开发需求,整合一些常用的嵌入式构件,以节约开发时间,尽最大可能地减少开发工作量;另外,要求这个实时操作系统能非常容易地嵌入到小容量的芯片中。毕竟,大系统是少数的,而小应用是多数而广泛的。显而易见,μC/OS-Ⅱ是不太适合于以上要求的,而Keil C所带的RTX Tiny不带源代码,不具透明性,至于其FULL版本就更不用说了。

1 Keil C51与重入问题

说到实时操作系统,就不能不考虑重入问题。对于PC机这样的大内存处理器而言,这似乎并不是一个很麻烦的问题,借用μC/OS-Ⅱ RTOS的说法,即要求在重入的函数内,使用局部变量。但51系列单片机堆栈空间很小,仅局限在256字节之内,无法为每个函数都分配一个局部堆空间,正是由于这个原因,Keil C51使用了所谓的可覆盖技术:

(1)局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);
(2)在编译链接时,即已经完成局部变量的定位;
(3)如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。

正是由于以上的原因,在Keil C51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重入的。那么在Keil C51环境下,如何使其函数具有可重入性呢?下面分析在实时操作系统下面,任务的基本结构与模式:

void TaskA(void*ptr){
UINT8 val_a;
//其他一些变量定义
do{
//实际的用户任务处理代码
}while(1);
}
void TaskB(void*ptr){
UINT8 val_b;
//其他一些变量定义
do{
Funcl();
//其他实际的用户任务处理代码
}while(1);
}
void Funcl(){
UINT8 val_fa;
//其他变量的定义
//函数的处理代码
}

在上面的代码中,TaskA与TaskB并不存在直接或间接的调用关系,因而其局部变量val_a与val_b便是可以被互相覆盖的,即其可能都被定位于某一个相同的RAM空间。这样,当TaskA运行一段时间,改变了val_a后,TaskB取得CPU控制权并运行时,便可能会改变val_b。由于其指向相同的RAM空间,导致TaskA重新取得CPU控制权时,val_a的值已经改变,从而导致程序运行不正确,反过来亦然。另一方面,Funch()与TaskB有直接的调用关系,因而其局部变量val_fa与val_b不会被互相覆盖,但也不能保证其局部变量val_fa不会与TaskA或其他任务的局部变量形成可覆盖关系。

将val_a、val_b以及val_fa等局部变量定义为静态变量(加上static指示符)可以解决这一问题。但问题是,定义大量的static类型变量,将导致RAM空间的大量占用,有可能直接导致RAM空间不够用。尤其是在一些小容量的单片机内,一般只有128或256字节,大量的静态变量定义,在如此小的RAM资源状况下显然就不太合适了。由此而有了另一种的解决方法,如下代码所示:

void TaskC(void){
UINT8 x,y;
while(1){
OS_ENTER_CRITICAL();
x=GetX(); (1)
y=GetY(); (2)
//任务的其他代码
OS_EXIT_CRITICAL(); (3)
OSSleep(100); (4)
}
}

以上代码TaskC中使用了临界保护的方法来保护代码不被中断占先,确实有效地解决了RAM空间太小,不宜大量定义静态变量的问题;然而如果每个任务都采用此种结构,任务一开始,就关闭中断,将使实时性得不到保证。事实证明,这种延时是相当可观的。用一个实例来说明,如果想在系统中使用一个动态刷新的LED显示器,就难以保证显示的稳定与连续,哪怕在系统中是使用一个单独的定时器来做这一工作(进入临界区后,EA=0)。其次,这种结构事实上将占先的任务调度转化为非占先的任务调度。实际上如果在(3)与(4)之间没有碰巧发生中断并导致一个任务调度,那就可以理解为是任务主动放弃CPU的控制。如果在(3)和(4)之间碰巧产生了一个中断并导致了一个任务调度,只是执行了一次多余的任务调度而已,而且并不希望在(3)之后发生2次深圳多次的任务调度,相信读者也有这一愿望。

除此之外,还可以发现任务的一个特点:当任务从(1)重新开始时,局部变量x和y是一个什么值并不在乎,即x和y即使在(3)之后改变了,也已经不再重要,不会影响程序的正确性。其实这一特点也是大部分任务,至少是大部分任务的大部分局部变量的一个共性--如果任务在整个执行过程中,不会(被占先)放弃CPU控制权,则其局部变量大多数并不需要进行特别的保护,即其作用域只是任务的当次执行,针对上面的代码,就是临界保护区内的代码区域。
2 实时操作系统要不要占先

有上面的分析,如果要保持一个函数可重入,就得使用静态变量,系统的RAM资源将是一个严峻的考验,如果使用临界区来保护运行环境,系统的实时性又得不到保证,而且有将占先式任务调度转为非占先任务调度之虞。显然,使用静态变量简单,但是更多的不适用性,对将来功能的调整也是一个阻碍,一般不被采用。那么,就只能从环境保护上来下功夫了,但是果真只能以进入临界区牺牲系统的实时性来保证任务不被占先?下面看看临界保护这一方法的基本思路:

(1)在一个任务中,如果局部变量在其作用域内不被占先切换,则这些变量在任务被剥夺了CPU控制权后,不关心其值也不会影响任务的正确执行;
(2)使用临界区保护,可以达到上面所提到的要求;
(3)由此导致的实时性能与占先切换的减弱可以接受。

由此可知,不被占先的是任务保护局部变量的关键。既然如此,何不舍弃占先式的任务调度?这不失为一个好的出发点。针对Keil C51,非占先式任务调度,可能是一种更好的方法,更能协调51系列的单片机的既定资源,下面编写这样一个系统:

(1)使用非占先式任务调度;
(2)可以在小容量的芯片中使用,开发目标是,即使是8051这样小的芯片,也可使用这个实时操作系统;
(3)支持优先级调度,尽可能保证其实时性。

3 实时操作系统的实现

基于以上的分析与目的,今日完成了这个操作系统。在堆栈上借用RTX的管理方法,即当前任务使用全部的堆空间,如图1所示。

3.1 堆栈的初始化与任务的创建

堆栈的初始化实际是初始化OSTaskStackBotton数组,并将当前任务指定为空闲任务,下一个运行任务指定为最高优先级任务,即优先级为零的任务。初始化时,将SP的值存入OSTaskStackBotton[0],SP+2的值存入OSTaskStackBotton[1],依此类推。而任务是调用OSTaskCreate函数建立的,实际上只是将任务(假设为n号任务)的地址填入到对应OSTaskStackBotton[n]所指向的位置,并将SP向后移动2个字节,如图2所示。

为什么要以这样一种规律而不是其他的方式呢?这样由于在任务建立后,还未进行任务调度之前,各任务的堆栈实际上是它们自身的地址,因而其堆栈深度为2,为了程序的简便而直接填入。

void main(void){
OSInit(); /*初始化OSTaskStackBotton队列*/
TMOD=(TMOD&0XF0)|0X01;
TL0=0xBF;
TH0=0xFC;
TR0=1;
ET0=1;
TF0=0;
OSTaskCreate(TaskA,NULL,0);
OSTaskCreate(TaskB,NULL,1);
OSTaskCreate(TaskC,NULL,2);
OSStart();
}
上面这段代码中,所有任务建立后,便调用OSStart()开始任务调度。OSStart()是一个宏定义,如下所示:
#define OSStart() do{\
OSTaskCreate(TaskIdle,NULL,OS_MAX_TASKS);\
EA=1;\
return;\
}while(0)

首先,它创建了一个空闲任务并打开中断,然后便返回。返回到那里了呢?我们知道,空闲任务是优先级最低的任务,当调OSTaskCreate建立时,会将其地址填入到SP的位置,并把SP向后移动2个字节(见图2及说明),因而此时处在堆栈顶端的,一定是空闲任务TaskIdle。这就使得这里的return一定会返回到空闲任务。至此,系统进入正常运行状态。

3.2 任务的切换

任务的切换分两种情况,在当前任务优先级低于下一个取得CPU的控制权的任务时,将下一个取得CPU控制权的任务的栈顶到当前任务的栈顶之间的内容向RAM空间的高端搬移,以空出全部的RAM空间作下一个任务的堆空间,同时更新对应的OSTaskStackBotton,使其指向新的正确任务的堆栈栈底。如果当前任务的优先级高于下一个任务的优先级,则作相反的搬移,如图3与图4所示。



所有任务必须主动调用OSSleep,放弃CPU的控制权。任务调用OSSleep后,将选择优先级最高的就绪任务运行。

(编者注:实时操作系统源代码见本刊网站www.mesnet.com.cn。)

结语

系统完成后,内核的代码量在400多个字节左右,占用1个定时器中断及小量的内存空间。系统设置容量为8个任务,用户实际可用任务为7个,能够满足一般需求,也达到了在小容量芯片中应用的开发要求。由于没有采用占先式的任务调度,除开全程相关的个别任务的一些局部变量外,其他局部变量已经不存在覆盖关系,由于是任务主动放弃CPU控制权,对于个别需要保护的变量单独进行处理也变得容易。在系统中,全程不需要反复地开关中断,实时性能也很好。对个别时序要求严格的外设(如DS18B20)除外。

注:本文所说的重入不一定是严格意义上的重入,很大程度上仅指函数被打断后再次进入时,程序能正确运行,并不是说其环境变量没有改变。

 
本文摘自《单片机与嵌入式系统应用》
系统分类: 嵌入式
用户分类: 51MCU
标签: 无标签
来源: 无分类
发表评论 阅读全文(1293) | 回复(0)

1

关于投票
基于AT89C51型单片机的号音自动播放器设计
基于AT89C51型单片机的号音自动播放器设计
张玉林,镇桂勤
(西安通信学院 陕西 西安 710106)

1 引言

机关、院校日常作息需要计时和号音提示,笔者利用AT89C51型单片机和LM386型音频功率放大器构成了自动计时和号音播放器,成本低,效果好,值得推广。

2 AT89C51的主要特性和引脚功能

AT89C51是带4K字节闪烁可编程可擦除只读存储器(EPEROM)的低电压、高性能CMOS 8位微处理器(俗称单片机)。该单片机与工业标准的MCS-51型机的指令集和输出引脚兼容。AT89C51将多功能8位CPU和闪烁存储器组合在单个芯片中,为很多嵌入式控制提供了灵活性高且价格低廉的方案。

AT89C51的主要特性如下:

寿命达1000写/擦循环;

数据保留时间:10年;

全静态工作:0Hz-24MHz;

三级程序存储器锁定;

128×8位内部RAM;

32可编程I/O线;

2个16位定时器/计数器;

5个中断源;

可编程串行通道;

低功耗闲置和掉电模式;

片内振荡器和时钟电路。

AT89C51引脚排列如图1所示,引脚功能如下:

VCC(40):+5V。

GND(20):接地。

P0口(39-32):P0口为8位漏极开路双向I/O口,每引脚可吸收8个TTL门电流。

P1口(1-8):P1口是从内部提供上拉电阻器的8位双向I/O口,P1口缓冲器能接收和输出4个TTL门电流。

P2口(21-28):P2口为内部上拉电阻器的8位双向I/O口,P2口缓冲器可接收和输出4个TTL门电流。

P3口(10-17):P3口是8个带内部上拉电阻器的双向I/O口,可接收和输出4个TTL门电流,P3口也可作为AT89C51的特殊功能口。

RST(9):复位输入。当振荡器复位时,要保持RST引脚2个机器周期的高电平时间。

ALE/PROG(30):当访问外部存储器时,地址锁存允许的输出电平用于锁存地址的低位字节,在FLASH编程期间,此引脚用于输入编程脉冲。在平时,ALE端以不变的频率周期输出正脉冲信号,此频率为振荡器频率的1/6,它可用作对外部输出的脉冲或用于定时目的,要注意的是,每当访问外部数据存储器时,将跳过1个ALE脉冲。

PSEN(29):外部程序存储器的选通信号。在由外部程序存储器取指期间,每个机器周期2次PSEN有效,但在访问外部数据存储器时,这2次有效的PSEN信号将不出现。

EA/VPP(31):当EA保持低电平时,外部程序存储器地址为(0000H-FFFFH)不管是否有内部程序存储器。FLASH编程期间,此引脚也用于施加12V编程电源(VPP)。

XTAL1(19):反向振荡器放大器的输入及内部时钟工作电路的输入。

XTAL2(18):来自反向振荡器的输出。

3 号音自动播放系统的设计

号音自动播放系统如图2所示,AT89C51的P1.0端接音频放大模块的IN+端口,在音频放大模块的VOUT端接一个8欧姆或者16欧姆的喇叭。

3.1 电路设计及音乐编程原理

若要产生音频脉冲,只要算出某一音频的周期(1/频率),再将此周期除以2,即为半周期的时间。利用定时器计时半周期时间,每当计时终止后就将P1.0反相,然后重复计时再反相。就可在P1.0引脚上得到此频率的脉冲。

利用AT89C51的内部定时器使其工作计数器模式(MODE1)下,改变计数值TH0及TL0以产生不同频率的方法产生不同音阶,例如,频率为523Hz,其周期T=1/523=1912μs,因此只要令计数器计时956μs/1μs=956,每计数956次时将I/O反相,就可得到中音DO(523Hz)。

计数脉冲值与频率的关系式是:

N=fi÷2÷fr

式中,N是计数值;fi是机器频率(晶体振荡器为12MHz时,其频率为1MHz);fr是想要产生的频率。

其计数初值T的求法如下:

T=65536-N=65536-fi÷2÷fr

例如:设K=65536,fi=1MHz,求低音DO(261Hz)、中音DO(523Hz)、高音DO(1046Hz)的计数值。

T=65536-N=65536-fi÷2÷fr=65536-1000000÷2÷fr=65536-500000/fr

低音DO的T=65536-500000/262=63627

中音DO的T=65536-500000/523=64580

高音DO的T=65536-500000/1046=65059

C调各音符频率与计数初值T对照如表1所示。

3.2 主程序流程

本系统主要完成作息定时和号音播放功能,因此用定时器T1中断方式产生100ms基准时间,再根据作息表上各段时间的长短对基准时间用软件计时。可以用查表方式取得计数参数,计时到后将播放子程序地址送DPTR,转入播放子程序,放2遍对应号音后再继续计时。主程序流程如图3所示。

播放子程序是用T0中断方式控制P1.0不断取反以产生不同频率音符,节拍的长短靠调用200ms延时子程序次数来完成。子程序也用查表来完成。

 
本文摘自《国外电子元器件》
系统分类: 单片机
用户分类: 51MCU
标签: 无标签
来源: 无分类
发表评论 阅读全文(652) | 回复(0)
总共 , 当前 /