51单片机

89C51芯片没有自带PWM发生器,如果要用51来产生PWM波就必须要用软件编程的方法来模拟。方法大概可以分为软件延时和定时器产生两种方法。下面将逐一介绍。

1 软件延时法

利用软件延时函数,控制电平持续的时间,达到模拟pwm的效果。

程序如下:

#include<reg52.h>
sbit pwm=P1^0;
main()
{
while(1)
{
 
pwm=1;
delayus(60);//置高电平后延时60us,占空比60%
pwm=0;
delayus(40);
}
}
void delayus(uint x)
{
while(x--);
}

proteus软件仿真结果如下:

51单片机产生PWM方法

可见,用这种延时函数的方法就能简单地模拟出pwm输出。但是这种方法的缺点也相当明显。当程序除了要输出pwm波还要执行其他操作比如键盘扫描、显示等操作时,需要占用CPU一定的机器周期,这样就会影响pwm的准确度。现在很少会用到这种方法,接下来要介绍的是比较常用的方法。

2 定时器产生pwm

这种方法利用了定时器溢出中断,在中断服务程序改变电平的高低,在程序较复杂、多操作时仍能输出较准确的pwm波形。

2.1 注意事项

2.2.1中断服务程序的内容。

一般来说中断服务程序只完成改变标志位、转换高低电平的功能,如果中断服务程序中有太多的操作会影响pwm波的输出,尤其是除法、取余、浮点数运算会占用大量的机器周期,应在中断外完成运算。
2.2.2定时器装入初值的问题。

装入初值不能太接近于定时器的溢出值。如我们使用定时器方式1,最多能计65536个数,假设我们转入的初值为65534,那么定时器计两个数就会进入中断,这样会使程序紊乱而其他功能无法正常地执行,所以一般要留50-100个数的裕量。

2.2 定时器工作方式

在定时器工作方式的选择上,可以选择定时器的工作方式0、1、2都可以,本文采用的是工作方式1,即16位定时器,这样可以获得较宽的调频范围。

2.3 定时器初值的计算

设占空比为α,频率为f

产生高电平时装入定时器高8位的值应为

产生高电平时装入定时器低8位的值应为

显然,产生低电平时的公式只要把α换成(1-α)就行了。

然而在51单片机中,浮点数运算需要消耗cpu很长的时间,为了提高程序效率,通常用100倍的占空比来计算。同时,要注意数据类型,避免超出范围,影响计算结果。关于C51的乘除法问题,可以看以下这篇文章: http://blog.163.com/ssou_1985/blog/static/295320362010311102232210/

修改后的公式如下:
a为100倍占空比,fr为0.01倍频率
TH0 = (65535-a*100/fr)/256; //高位初值
TL0 = (65535-a*100/fr)%256;
同样,低电平的公式只需把a换成(100-a)即可。

2.4 例程

本例程采用定时器T0在工作方式1下产生一路PWM,用独立键盘控制频率、占空比的加减,频率可调范围100Hz-10kHz,占空比0-100%(均为理论值,实际值略低)
部分代码如下:

51单片机产生PWM方法

注:T0_H , T0_L , T1_H , T1_L 均用于暂时存储初值,进入中断服务程序后直接给寄存器TH0、TL0赋值,避免了在中断中计算。
51单片机产生PWM方法

注:flag为pwm输出标志,flag=1输出高电平,flag=0输出低电平

2.5 软件仿真结果

2.5.1 频率为100Hz

a.占空比约15%

51单片机产生PWM方法

b.占空比95%

51单片机产生PWM方法

2.5.2 频率为10KHz

a.占空比15%

51单片机产生PWM方法

b.占空比90%

51单片机产生PWM方法

转自: 玩转单片机

围观 383

一、基本知识

1.按键分类与输入原理

按键按照结构原理科分为两类,一类是触点式开关按键,如机械式开关、导电橡胶式开关灯;另一类是无触点式开关按键,如电气式按键,磁感应按键等。前者造价低,后者寿命长。目前,微机系统中最常见的是触点式开关按键。

在单片机应用系统中,除了复位按键有专门的复位电路及专一的复位功能外,其他按键都是以开关状态来设置控制功能或输入数据的。当所设置的功能键或数字键按下时,计算机应用系统应完成该按键所设定的功能,键信息输入时与软件结构密切相关的过程。

对于一组键或一个键盘,总有一个接口电路与CPU相连。CPU可以采用查询或中断方式了解有无将按键输入,并检查是哪一个按键按下,将该键号送人累加器,然后通过跳转指令转入执行该键的功能程序,执行完成后再返回主程序。

2.按键结构与特点

微机键盘通常使用机械触点式按键开关,其主要功能式把机械上的通断转换为电气上的逻辑关系。也就是说,它能提供标准的TTL逻辑电平,以便于通用数字系统的逻辑电平相容。机械式按键再按下或释放时,由于机械弹性作用的影响,通常伴随有一定的时间触点机械抖动,然后其触点才稳定下来。其抖动过程如下图1所示,抖动时间的长短与开关的机械特性有关,一般为5-10ms。在触点抖动期间检测按键的通与断,可能导致判断出错,即按键一次按下或释放错误的被认为是多次操作,这种情况是不允许出现的。为了克服你、按键触点机械抖动所致的检测误判,必须采取消抖措施。按键较少时,可采用硬件消抖;按键较多式,采用软件消抖。

51单片机之独立按键和矩阵键盘
图1 按键触点机械抖动

(1)按键编码

一组按键或键盘都要通过I/O口线查询按键的开关状态。根据键盘结构的不同,采用不同的编码。无论有无编码,以及采用什么编码,最后都要转换成为与累加器中数值相对应的键值,以实现按键功能程序的跳转。

(2)键盘程序

一个完整的键盘控制程序应具备以下功能:
a.检测有无按键按下,并采取硬件或软件措施消抖。
b.有可靠的逻辑处理办法。每次只处理一个按键,期间对任何按键的操作对系统不产生影响,且无论一次按键时间有多长,系统仅执行一次按键功能程序。
c.准确输出按键值(或键号),以满足跳转指令要求。

3.独立按键与矩阵键盘

(1)独立按键

单片机控制系统中,如果只需要几个功能键,此时,可采用独立式按键结构。

独立按键式直接用I/O口线构成的单个按键电路,其特点式每个按键单独占用一根I/O口线,每个按键的工作不会影响其他I/O口线的状态。独立按键的典型应用如图所示。独立式按键电路配置灵活,软件结构简单,但每个按键必须占用一个I/O口线,因此,在按键较多时,I/O口线浪费较大,不宜采用。独立按键如图2所示。

51单片机之独立按键和矩阵键盘
图2 独立键盘

独立按键的软件常采用查询式结构。先逐位查询没跟I/O口线的输入状态,如某一根I/O口线输入为低电平,则可确认该I/O口线所对应的按键已按下,然后,再转向该键的功能处理程序。

(2)矩阵键盘

单片机系统中,若使用按键较多时如电子密码锁、电话机键盘等一般都至少有12到16个按键,通常采用矩阵键盘。

矩阵键盘又称行列键盘,它是用四条I/O线作为行线,四条I/O线作为列线组成的键盘。在行线和列线的每个交叉点上设置一个按键。这样键盘上按键的个数就为4*4个。这种行列式键盘结构能有效地提高单片机系统中I/O口的利用率。

矩阵键盘的工作原理

最常见的键盘布局如图3所示。一般由16个按键组成,在单片机中正好可以用一个P口实现16个按键功能,这也是在单片机系统中最常用的形式,4*4矩阵键盘的内部电路如图4所示。

51单片机之独立按键和矩阵键盘
图3 矩阵键盘布局图

51单片机之独立按键和矩阵键盘
图4 矩阵键盘内部电路图

当无按键闭合时,P3.0~P3.3与P3.4~P3.7之间开路。当有键闭合时,与闭合键相连的两条I/O口线之间短路。判断有无按键按下的方法是:第一步,置列线P3.4~P3.7为输入状态,从行线P3.0~P3.3输出低电平,读入列线数据,若某一列线为低电平,则该列线上有键闭合。第二步,行线轮流输出低电平,从列线P3.4~P3.7读入数据,若有某一列为低电平,则对应行线上有键按下。综合一二两步的结果,可确定按键编号。但是键闭合一次只能进行一次键功能操作,因此须等到按键释放后,再进行键功能操作,否则按一次键,有可能会连续多次进行同样的键操作。

识别按键的方法很多其中,最常见的方法是扫描法

按键按下时,与此键相连的行线与列线导通,行线在无按键按下时处在高电平。如果所有的列线都处在高电平,则按键按下与否不会引起行线电平的变化,因此必须使所有列线处在电平。这样,当有按键按下时,改键所在的行电平才回由高变低。才能判断相应的行有键按下。

独立按键数量少,可根据实际需要灵活编码。矩阵键盘,按键的位置由行号和列号唯一确定,因此可以分别对行号和列号进行二进制编码,然后两值合成一个字节,高4位是行号,低4位是列号。

4.键盘的工作方式

对键盘的响应取决于键盘的工作方式,键盘的工作方式应根据实际应用系统中的CPU的工作状况而定,其选取的原则是既要保证CPU能及时响应按键操作,又不要过多占用CPU的工作时间。通常键盘的工作方式有三种,编程扫描、定时扫描和中断扫描。

(1)编程扫描方式

编程扫描方式是利用CPU完成其它工作的空余时间,调用键盘扫描子程序来响应键盘输入的要求。在执行键功能程序时,CPU不再响应键输入要求,直到CPU重新扫描键盘为止。

(2)定时扫描方式

定时扫描方式就是每隔一段时间对键盘扫描一次,它利用单片机内部的定时器产生一定时间(例如10ms)的定时,当定时时间到就产生定时器溢出中断。CPU响应中断后对键盘进行扫描,并在有按键按下时识别出该键,再执行该键的功能程序。

(3)中断扫描方式

上述两种键盘扫描方式,无论是否按键,CPU都要定时扫描键盘,而单片机应用系统工作时,并非经常需要键盘输入,因此,CPU经常处于空扫描状态。

为提高CPU工作效率,可采用中断扫描工作方式。其工作过程如下:当无按键按下时,CPU处理自己的工作,当有按键按下时,产生中断请求,CPU转去执行键盘扫描子程序,并识别键号。

来源: 玩转单片机

围观 2260

在单片机的开发应用中,已逐渐开始引入高级语言,C语言就是其中的一种。用惯了汇编的人,总觉得高级语言“可控性”不好,不如汇编那样随心所欲。以下是笔者在C51编程中的几点经验,希望对初学C51者有所帮助。

一、C51热启动代码的编制

工业控制计算机,往往设有看门狗电路,看门狗动作,计算机复位,这就是热启动。热启动时,一般不允许程序从头开始,因为这将使测量或计算值复位,导致系 统工作异常。故程序必须判断是热启动还是冷启动。常用的方法是:设定某内存单位为标志位(如0x7f位和0x7e位),启动时首先读该内存单元的内容,如 果它等于一个特定的值(例如两个内存单元的都是0xaa),就认为是热启动,否则就是冷启动,程序执行初始化部分,并将0xaa赋予这两个内存单元。

根据以上的设计思路,编程时,设置一个指针,指向特定的内存单元如0x7f,然后在程序中根据特定内存单元的值判断冷/热启动,程序如下:

  void main()
  {  char data*HotPoint=(char*)0x7f;
  if((*HotPoint==0xaa)&&(*(--Hot
  Point)==0xaa))
  {     /*热启动的处理   */
  }
  else
  {  HotPoint=0x7e; /*冷启动的处理
  *HotPoint-0xaa;
  *(++HotPoint)=0xaa;
  }
  /*正常工作代码*/
  }

实际调试中发现,无论是热启动还是冷启动,开机后所有内存单元的值都被复位为0,实现不了热启动的要求。这是为什么呢?原来,用C语言编程时,开机时执 行的代码并非是从main()函数的第一语句开始的,在main()函数的第一语句执行前要先执行一段‘起始代码’。正是这段代码执行了内存清零的工作。 C编译程序提供了这段起始代码的源程序,名为CSTARTUP A51,打开这个文件,可以看到如下代码:

  IDATALEN EQU 8011 the length of IDATA memory m bytes
  STARTUP1:
  IF IDATALEN《》0
  MOV R0,#IDATALEN-I
  CLR A
  IDATALOOP:  MOV @R0,A
  DJNZ R0,IDATALOOP
  ENDIF

可见,在执行到判断是否热启动的代码之前,起始代码已将所有内存单元清零。如何解决这个问题呢?好在起始代码是可以更改的,方法是:修改 startup.a51源文件,然后用编译程序所附带的a51.exe程序对startup.a51编译,得到startup.obj文件,用这段代码代 替原来的起始代码。具体步骤是(设C源程序名为HOTSTART C):

  1 修改startup.a51源文件(这个文件在C51/LIB目录下)。
  2 执行如下命令:
  A51 startup.a51得到startup.obj文件。将此文件拷入HOTSTART C所在目录。
  3 将编好的C源程序用C51 EXE编译好,得到目标文件HOTSTART OBJ。
  4 用L51 HOTSTART,STARTUP OBJ命令连接,得到绝对目标文件HOTSTART。
  5 用OHS51 HOTSTART得到HOTSTART HEX文件,即可完成启动代码的修改。

对于startup.a51的修改,根据自己的需要进行,如将IDATALEN EQU 80H中的80H改为70H,就可以使6F到7F的16字节内存不被清零。

二、直接调用EPROM中已固化的程序

笔者用的仿真机,由6位数码管显示,在DE00H处存放显示子程序,只要将显示的数存入显示缓冲区,然后调用显示子程序就可以了,汇编指令为:

LCALL 0DE00H

在用C语言编程时,如何实现这一功能呢?C语言中有指向函数的指针这一概念,可以用来实现用函数指针调用函数。指向函数的指针变量的定义格式为:

类型标识符(*指针变量名)();

在定义好指针后就可以给指针变量赋值,使其指向某个函数的开始地址,然后用(*指针变量名)()即可调用这个函数。程序如下例:

  void main(void)
  {
  void (*DispBuffer)();/*定义指向函数指针*/
  DispBuffer=0xde00; /*赋值*/
  for(;;)
  { Key();
  DispBuffer();
  }
  }

三、将浮点数转化为字符数组

笔者在编制应用程序时有这样的要求:将运算的结果(浮点数)存入E2PROM中。我们知道,浮点数在C语言中是以IEEE格式存储的,一个浮点数占四个 字节。例如浮点数34 526存为160、26、10、664个数。要将该浮点数存入E2PROM,实际上就是要存这四个数。如何在程序中得到一个浮点数 的组成数呢?

浮点数在存储时,是存储在连续的字节中的,只要设法找到存储位置,就可以得到这些数了。可以定义一个void指针,将此指针指向需要存储的浮点数,然后再将此指针强制转化为char型。这样,利用指针就可以得到组成该浮点数的各个字节的值了。具体程序如下:

  #define uchar unsigned char
  #define uint unsigned int
  void FtoC(void)
  {  float a;
  uchar I,*px
  uchar x[4];/*定义字符数组,准备存储浮点数的四个字节*/
  void *pf;
  px=x; /*px指针指向数组x*/
  pf=&a;/*void型指针指向浮点数首地址*/
  a=34.526;
  for(I=0;I《4;I++)
  { *(px+I)=*((char *)pf+I);/*强制void型指针转成char型,因为void型指针不能运算*/
  }
  }

如果已将数存入E2PROM,要将其取出合并,方法也是一样,可参考下面的程序。

  #define uchar unsigned char
  #define uint unsigned int
  void CtoF(void)
  {  float a;
  uchar I,*px
  uchar x[4]-{56,180,150,73};
  void *pf;
  px=x;
  pf=&a;
  for(I=o;I《4;I++)
  { *((char *)pf+I)=*(px+I)
  }
  }

以上程序所用C语言为FRANKLIN C51 VER 3 2。

来源: 电子发烧友

围观 483

串口通信的基本认识

通信分为并行通信和串行通信,并行通信时的数据各个位同时传送,可以实现字节为单位通信,但通信线多占用资源,成本高。以前用到的的P1=0x55,一次给P1口的8个管脚分别赋值,同时进行信号输出,类似于8个车道可以过去8辆车,这样的形式是并行的,一般称P0,P1,P2,P3为51单片机的4组并行总线。

串行通信,就是一个车道,一个只能通过一辆车,如果一个0x55这样一个字节的数据要传输过去的话,假如低位在前,高位在后的话,那发送方式是:0-1-0-1-0-1-0-1,一位一位的进行传输,要发送8次才能发送完一个字节
STC89C52有两个引脚是专门用来做串口通信的,一个是P3.0(RXD),一个是P3.1(TXD),他们组成的通信接口就是串行接口,简称串口。用于两个单片机进行UART通信。两单片机通信接口连接方式:RXD——TXD,TXD——RXD。

单片机1的TXD发送通道接到单片机2的RXD接收通道,单片机的1的RXD接收通道接到单片机2的TXD发送通道,从而实现相互通信。

当单片机1想给单片机2发送数据,比如发送了0xCE,用二进制表示就是11001110,在串口通信过程中,是低位先发,高位后发的原则,那么就是让TXD首先拉低电平,持续一段时间,发送一位0,然后拉高电平,持续一段时间,发送一位1,继续拉高,在持续一段时间,发送一位1,一直把8位二进制数11001110全部发送完毕,这里涉及到一个问题,就是持续的一个时间段时间“到底是多少”。因而便引入通信中非常重要的一个概念波特率,也叫做比特率。

波特率

波特率就是发送二进制数据位的速率,习惯用baud表示,即我们发送一位二进制数据持续的时间=1/baud。在通信之前,单片机1和单片机2首先都要明确约定好他们之间的通信波特率,必须保持一致,收发双方才能正常通信。
约定好速度之后,我们还要考虑第二个问题,数据什么时候是起始,什么时候是结束?提前和延迟结束都会接收错误。在uart通信的时候,一个字节是8位,规定当没有通信信号发生时,通信线路保持高电平,当数据发送前,先发一位0表示起始位,然后发送8位数据位,数据位是先低再高,数位位发送完后才呢个后再发送一位1表示停止位,这样我们要发送的8位数据,实际上我们发送了10位,多出来两位其中一个是起始位,一个是停止位。而接受方一直保持的高电平,一旦检测到一位低电平,准备开始接受数据,接受8位数据后,然后检测停止位,再准备下一个数据接收。

串口数据发送示意图,实际上是一个时域示意图,就是信号随着时间变化的对应关系。比如在单片机的发送引脚上,左边的是先发生的,右边的是后发生的,数据位的切换时间就是波特率分之一秒,如果能够理解时域的概念,后边很多通信的时序图就很容易理解了。

RS232

在我们电脑上,一般都会有一个9针的串行接口,这个串行接口叫做RS232接口,它和UART通信有关联,但是由于现在笔记本电脑不带9针串口,所以和单片机通信越来越趋于使用USB虚拟串口。

九针串口分工头和母头

公头上5下4,上5从左到右为1.2.3.4.5;下4从左到右为6.7.8.9;
母头上5下4,上5从左到右为5.4.3.2.1;下4从左到右为9.8.7.6;

RS232接口一共有9个引脚,分别定义是:
1、载波检测DCD;
2、接收数据RXD;
3、发送数据TXD;
4、数据终端准备好DTR;
5、信号地线SG;
6、数据准备好DSR;
7、请求发送RTS;
8、清除发送CTS;
9、振铃提示RI。
我们要让这个串口和我们单片机进行通信,我们只需要关心其中的2脚RXD、3脚TXD和5脚GND即可。

虽然这三个引脚的名字和我们单片机上的串口名字一样,但是却不能直接和单片机对连通信,这是为什么呢?随着我们了解的内容越来越多,我们得慢慢知道,不是所有的电路都是5V代表高电平而0V代表低电平的。对于RS232标准来说,它是个反逻辑,也叫做负逻辑。为何叫负逻辑?它的TXD和RXD的电压,-3V~-15V电压代表是1,+3~+15V电压代表是0。低电平代表的是1,而高电平代表的是0,所以称之为负逻辑。因此电脑的9针RS232串口是不能和单片机直接连接的,需要用一个电平转换芯片MAX232来完成。

这个芯片就可以实现把标准RS232串口电平转换成我们单片机能够识别和承受的UART 0V/5V电平。从这里大家似乎慢慢有点明白了,其实RS232串口和UART串口,它们的协议类型是一样的,只是电平标准不同而已,而MAX232这个芯片起到的就是中间人的作用,它把UART电平转换成RS232电平,也把RS232电平转换成UART电平,从而实现标准RS232接口和单片机UART之间的通信连接。

USB转串口通信

随着技术的发展,工业上还有RS232串口通信的大量使用,但是商业技术的应用上,已经慢慢的使用USB转UART技术取代了RS232串口,绝大多数笔记本电脑已经没有串口这个东西了,那我们要实现单片机和电脑之间的通信该怎么办呢?

我们只需要在电路上添加一个USB转串口芯片,就可以成功实现USB通信协议和标准UART串行通信协议的转换,在我们的开发板上,我们使用的是CH340T这个芯片。

我们需要用跳线帽把中间和下边的针短接在一起。右侧的CH340T这个电路很简单,把电源、晶振接好后,6脚和7脚的DP和DM分别接USB口的2个数据引脚上去,3脚和4脚通过跳线接到了我们单片机的TXD和RXD上去。

CH340T的电路里3脚位置加了个4148的二极管,是一个小技巧。因为STC89C52这个单片机下载程序时需要冷启动,就是先点下载后上电,上电瞬间单片机会先检测需要不需要下载程序。虽然单片机的VCC是由开关来控制,但是由于CH340T的3脚是输出引脚,如果没有此二极管,开关后级单片机在断电的情况下,CH340T的3脚和单片机的P3.0(即RXD)引脚连在一起,有电流会通过这个引脚流入后级电路并且给后级的电容充电,造成后级有一定幅度的电压,这个电压值虽然只有两三伏左右,但是可能会影响到正常的冷启动。加了二极管后,一方面不影响通信,另外一个方面还可以消除这种不良影响。这个地方可以暂时作为了解,大家如果自己做这类电路,可以参考一下。

IO口模拟UART串口通信

UART串口波特率,常用的值是300、600、1200、2400、4800、9600、14400、19200、28800、38400、57600、115200等速率。IO口模拟UART串行通信程序是一个简单的演示程序,我们使用串口调试助手下发一个数据,数据加1后,再自动返回。

串口调试助手,这里我们直接使用STC-ISP软件自带的串口调试助手,先把串口调试助手的使用给大家说一下,如图11-6所示。第一步要选择串口助手菜单,第二步选择十六进制显示,第三步选择十六进制发送,第四步选择COM口,这个COM口要和自己电脑设备管理器里的那个COM口一致,波特率按我们程序设定好的选择,我们程序中让一个数据位持续时间是1/9600秒,那这个地方选择波特率就是选9600,校验位选N,数据位8,停止位1。

串口调试助手的实质就是利用电脑上的UART通信接口,发送数据给我们的单片机,也可以把我们的单片机发送的数据接收到这个调试助手界面上。

因为初次接触通信方面的技术,所以我把后面的IO模拟串口通信程序进行一下解释,大家可以边看我的解释边看程序,把底层原理先彻底弄懂。

变量定义部分就不用说了,直接看main主函数。首先是对通信的波特率的设定,在这里我们配置的波特率是9600,那么串口调试助手也得是9600。配置波特率的时候,我们用的是定时器T0的模式2。模式2中,不再是TH0代表高8位,TL0代表低8位了,而只有TL0在进行计数,当TL0溢出后,不仅仅会让TF0变1,而且还会将TH0中的内容重新自动装到TL0中。这样有一个好处,就是我们可以把想要的定时器初值提前存在TH0中,当TL0溢出后,TH0自动把初值就重新送入TL0了,全自动的,不需要程序中再给TL0重新赋值了,配置方式很简单,大家可以自己看下程序并且计算一下初值。

波特率设置好以后,打开中断,然后等待接收串口调试助手下发的数据。接收数据的时候,首先要进行低电平检测while (PIN_RXD),若没有低电平则说明没有数据,一旦检测到低电平,就进入启动接收函数StartRXD()。接收函数最开始启动半个波特率周期,初学可能这里不是很明白。大家回头看一下我们的图11-2里边的串口数据示意图,如果在数据位电平变化的时候去读取,因为时序上的误差以及信号稳定性的问题很容易读错数据,所以我们希望在信号最稳定的时候去读数据。除了信号变化的那个沿的位置外,其它位置都很稳定,那么我们现在就约定在信号中间位置去读取电平状态,这样能够保证我们读的一定是正确的。

一旦读到了起始信号,我们就把当前状态设定成接收状态,并且打开定时器中断,第一次是半个周期进入中断后,对起始位进行二次判断一下,确认一下起始位是低电平,而不是一个干扰信号。以后每经过1/9600秒进入一次中断,并且把这个引脚的状态读到RxdBuf里边。等待接收完毕之后,我们再把这个RxdBuf加1,再通过TXD引脚发送出去,同样需要先发一位起始位,然后发8个数据位,再发结束位,发送完毕后,程序运行到while (PIN_RXD),等待第二轮信号接收的开始。

串口通信基本应用

通信的三种基本类型

常见的通信传输方式可以分为单工通信、半双工通信、全双工通信。

单工通信就是只允许一个方向向另外一个方向传送信息,而另外一方不能回传消息。比如:电视遥控器、收音基等

半双工通信是指数据可以在双方之间相互传播,但是同一时刻只能呢个其中一方发给另一方,比如:对讲机

全双工通信是指发送数据同时也能接收数据,两者同步进行,就如同我们的电话一样,我们说的同时也可以听到对方的声音。

uart模块介绍

IO口模拟串口通信,让大家了解了串口通信的本质,但是我们的单片机程序却需要不停的检测扫描单片机IO口收到的数据,大量占用了单片机的运行时间。这时候就会有聪明人想了,其实我们并不是很关心通信的过程,我们只需要一个通信的结果,最终得到接收到的数据就行了。这样我们可以在单片机内部做一个硬件模块,让它自动接收数据,接收完了,通知我们一下就可以了,我们的51单片机内部就存在这样一个UART模块,要正确使用它,当然还得先把对应的特殊功能寄存器配置好。

51单片机的UART串口的结构由串行口控制寄存器SCON、发送和接收电路三部分构成,先来了解一下串口控制寄存器SCON。

SCON串行控制器的位分配(地址:0x98)

位:符号:复位值: 0:RI:0;1:TI:0;2:RB8:0;3:TB8:0;4:REN:0;5:SM2:0;6:SM1:0;7:SM0:0;
0位RI:接收中断标志位,当接收电路接收到停止位的中间位置时,RI由硬件置1,必须通过软件清零
1位TI:发送中断标志位,当发送电路发送到停止位的中间位置时,TI由硬件置1,必须通过软件清零。
2位RB8:模式2和3中接收到的第9位数据(很少用),模式1用来接收停止位。
3位TB8:模式2和3中要发送的第9位数据(很少用)。
4位REN:使能串行接收。由软件置位使能接收,软件清零则禁止接收。
5位SM2:多机通信控制位(极少用),模式1直接清零。
6位SM1和7位SM0:

这两位共同决定了串口通信的模式0~模式3共4种模式。我们最常用的就是模式1,也就是SM0=0,SM1=1,下边我们重点就讲模式1,其它模式从略。

对于串口的四种模式,模式1是最常用的,就是我们前边提到的1位起始位,8位数据位和1位停止位。下面我们就详细介绍模式1的工作细节和使用方法,至于其它3种模式与此也是大同小异,真正遇到需要使用的时候大家再去查阅相关资料就行了。

在我们使用IO口模拟串口通信的时候,串口的波特率是使用定时器T0的中断体现出来的。在硬件串口模块中,有一个专门的波特率发生器用来控制发送和接收数据的速度。对于STC89C52单片机来讲,这个波特率发生器只能由定时器T1或定时器T2产生,而不能由定时器T0产生,这和我们模拟的通信是完全不同的概念。

如果用定时器2,需要配置额外的寄存器,默认是使用定时器1的,我们本章内容主要就使用定时器T1作为波特率发生器来讲解,方式1下的波特率发生器必须使用定时器T1的模式2,也就是自动重装载模式,定时器的重载值计算公式为:
TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率
和波特率有关的还有一个寄存器,是一个电源管理寄存器PCON,他的最高位可以把波特率提高一倍,也就是如果写PCON |= 0x80以后,计算公式就成了:
TH1 = TL1 = 256 - 晶振值/12 /16 /波特率
公式中数字的含义这里解释一下,256是8位定时器的溢出值,也就是TL1的溢出值,晶振值在我们的开发板上就是11059200,12是说1个机器周期等于12个时钟周期,值得关注的是这个16,我们来重点说明。在IO口模拟串口通信接收数据的时候,采集的是这一位数据的中间位置,而实际上串口模块比我们模拟的要复杂和精确一些。他采取的方式是把一位信号采集16次,其中第7、8、9次取出来,这三次中其中两次如果是高电平,那么就认定这一位数据是1,如果两次是低电平,那么就认定这一位是0,这样一旦受到意外干扰读错一次数据,也依然可以保证最终数据的正确性。

串口通信的发送和接收电路在物理上有2个名字相同的SBUF寄存器,它们的地址也都是0x99,但是一个用来做发送缓冲,一个用来做接收缓冲。意思就是说,有2个房间,两个房间的门牌号是一样的,其中一个只出人不进人,另外一个只进人不出人,这样的话,我们就可以实现UART的全双工通信,相互之间不会产生干扰。但是在逻辑上呢,我们每次只操作SBUF,单片机会自动根据对它执行的是“读”还是“写”操作来选择是接收SBUF还是发送SBUF,后边通过程序,我们就会彻底了解这个问题。

##UART串口程序

一般情况下,我们编写串口通信程序的基本步骤如下所示:
1、配置串口为模式1。
2、配置定时器T1为模式2,即自动重装模式。
3、根据波特率计算TH1和TL1的初值,如果有需要可以使用PCON进行波特率加倍。
4、打开定时器控制寄存器TR1,让定时器跑起来。

这里还要特别注意一下,就是在使用T1做波特率发生器的时候,千万不要再使能T1的中断了。

我们先来看一下由IO口模拟串口通信直接改为使用硬件UART模块时的程序代码,看看程序是不是简单了很多,因为大部分的工作硬件模块都替我们做了。程序功能和IO口模拟的是完全一样的。

通信实例与ASCLL码

先抛开我们使用的汉字不谈,那么我们常用的字符就包含了0~9的数字、A~Z/a~z的字母、还有各种标点符号等。那么在单片机系统里面我们怎么来表示它们呢?ASCII码(American Standard Code for Information Interchange,即美国信息互换标准代码)可以完成这个使命:我们知道,在单片机中一个字节的数据可以有0~255共256个值,我们取其中的0~127共128个值赋予了它另外一层涵义

我们用字符格式发送一个小写的a,返回一个十六进制的0x61,数码管上显示的也是61,ASCII码表里字符a对应十进制是97,等于十六进制的0x61;我们再用字符格式发送一个数字1,返回一个十六进制的0x31,数码管上显示的也是31,ASCII表里字符1对应的十进制是49,等于十六进制的0x31。这下大家就该清楚了:所谓的十六进制发送和十六进制接收,都是按字节数据的真实值进行的;而字符格式发送和字符格式接收,是按ASCII码表中字符形式进行的,但它实际上最终传输的还是一个字节数据。这个表格,当然不需要大家去记住,理解它,用的时候过来查就行了。

转自: 单片机精讲吴鉴鹰

围观 613

1. IIC总线基本概念

1.1总线概述

IIC总线是PHLIPS公司推出的一种串行总线,是具备多主机系统所需的包括总线裁决和高低速器件同步功能的高性能串行总线。

1.2总线结构

IIC总线是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间可以进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这两条总线上。

IIC总线原理及其在51单片机中的简单应用

2. IIC总线协议

2.1数据传输中的主/从机

在多主机系统中,可能同时有几个主机企图启动总线传送数据。为了避免混乱, IIC总线要通过总线仲裁,以决定由哪一台主机控制总线。连在每一个总线上的电路和模块都有一个唯一地址,它们彼此之间只有简单的Master/Slaver关系。

主机:控制时钟线,产生启动信号、时钟信号、停止信号。控制线上的发送和接收节奏。

从机:按SCL线上的时钟接收SDA线上的数据或发送数据到SDA线上。

2.2协议概述

IIC总线仅占用系统的两个I/O口,一条作为串行数据线SDA,一条作为串行时钟线SCL,线上外加上拉电阻,在不传数据时SCL、 SDA总保持高电平。IIC总线通过上拉电阻接电源正极。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系。CPU发出的控制信号分为地址码和控制量两部分,地址码用来选址,即接通需要控制的电路,确定控制的种类;控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。

2.3时序图

完整的数据传输过程时序图:

IIC总线原理及其在51单片机中的简单应用

2.4信号类型

(1)开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。

(2)结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。

IIC总线原理及其在51单片机中的简单应用

(3)应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况判断是否继续传递信号。若未收到应答信号,由判断为受控单元出现故障。

IIC总线原理及其在51单片机中的简单应用

(4)总线空闲:SCL和SDA都保持高电平的状态

(5)总线忙:在数据传送开始以后,SCL为高电平的时候,SDA的数据必须保持稳定,只有当SCL为低电平的时候才允许SDA上的数据改变.

IIC总线原理及其在51单片机中的简单应用

这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。IIC总线有两根信号线,一根为SDA(数据线),一根为SCL(时钟线)。任何时候时钟信号都是由主控器件产生。

3. IIC总线数据传输格式

3.1字节传送与应答:

每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

3.2数据帧格式

IIC总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。(注:下图中有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A表示应答, A非 表示非应答(高电平)。S表示起始信号,P表示终止信号。)

A. 主机向从机发送数据,数据传送方向在整个传送过程中不变
B. 主机在第一个字节后,立即从从机读数据
C. 传送过程中,当需改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相。

4. IIC总线多主机竞争的仲裁

总线上可能挂接有多个器件,有时会发生两个或多个主器件同时想占用总线的情况,这种情况叫做总线竞争。IIC总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁,其仲裁原则是这样的:当多个主器件同时想占用总线时,如果某个主器件发送高电平,而另一个主器件发送低电平,则发送电平与此时SDA总线电平不符的那个器件将自动关闭其输出级。总线竞争的仲裁是在两个层次上进行的。首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位的比较,从而确保了竞争仲裁的可靠性。由于是利用IIC总线上的信息进行仲裁,因此不会造成信息的丢失。

为何识别到“0”将丢失仲裁呢?因为对于OD门,只能驱动到低电平,释放总线只能通过不驱动总线释放,停止驱动即产生“1”,但是发现总线还是“0”,这说明还有主机在跟自己竞争总线使用权,自己线驱动到“1”,确检测到“0”,那代表自己已经失去了仲裁。

• 主机只能在总线空闲时启动传送。两个或多个主机可能在起始条件的最小持续时间tHD;STA 内产生一个起始条件,结果在总线上产生一个规定的起始条件。

• 当SCL 线是高电平时,仲裁在SDA 线发生;这样,在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平与它自己的电平不相同。然后,进一步获得其的判定条件:

• 仲裁可以持续多位。首先是比较地址位。如果每个主机都试图寻址同一的器件,仲裁会继续比较数据位(假设主机是发送器),或者比较响应位(假设主机是接收器)。

• IIC总线的地址和数据信息由赢得仲裁的主机决定,在仲裁过程中不会丢失信息。丢失仲裁的主机可以产生时钟脉冲直到丢失仲裁的该字节末尾。

• 在串行传输过程中时,一旦有重复的起始条件或停止条件发送到IIC 总线的时侯,仲裁过程仍在进行。如果可能产生这样的情况,有关的主机必须在帧格式相同位置发送这个重复起始条件或停止条件。

• 此外,如果主机也结合了从机功能,而且在寻址阶段丢失仲裁,它很可能就是赢得仲裁的主机在寻址的器件。那么,丢失仲裁的主机必须立即切换到它的从机模式。

• IIC总线的控制只由地址或主机码以及竞争主机发送的数据决定,没有中央主机,总线也没有任何定制的优先权。

5.时钟信号的同步

在 IIC总线上传送信息时的时钟同步信号是由挂接在SCL线上的所有器件的逻辑“与”完成的。SCL线上由高电平到低电平的跳变将会影响到这些器件,一旦某个器件的时钟信号下跳为低电平,将使SCL线一直保持低电平,使SCL线上的所有器件开始低电平期。此时,低电平周期短的器件的时钟由低至高的跳变并不能影响SCL线的状态,于是这些器件将进入高电平等待的状态。当所有器件的时钟信号都上跳为高电平时,低电平期结束,SCL线被释放返回高电平,即所有的器件都同时开始它们的高电平期。其后,第一个结束高电平期的器件又将SCL线拉成低电平。这样就在SCL线上产生一个同步时钟。可见,时钟低电平时间由时钟低电平期最长的器件确定,而时钟高电平时间由时钟高电平期最短的器件确定。

6.总线功能特点及应用

6.1 IIC总线功能特点

IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。

IIC总线的另一个优点是,它支持多主控,其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

6.2总线应用

目前有很多半导体集成电路上都集成了IIC接口。带有IIC接口的单片机有:CYGNAL的 C8051F0XX系列,三星的S3C24XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外围器件如存储器、监控芯片等也提供I²C接口。

7.利用51单片机模拟IIC总线协议

7.1所需器件

AT89C51单片机、24C02存储器、6kΩ电阻两个、导线若干

7.2接线方式

利用AT89C51的IO口模拟实现IIC总线协议进行与24C02存储器的通信,其电路图如下:

IIC总线原理及其在51单片机中的简单应用

7.3程序代码

下面是使用AT89C51的IO口模拟实现IIC总线协议进行通信的程序:

#include
#include
#define uc unsigned char
sbit SDA=P1^0;
sbit SCL=P1^1;
bit NackFlag;
void delay()
{
_nop_();
_nop_();
_nop_();
_nop_();
}
void DelayX1ms(uc count)
{
uc i,j;
for(i=0;i
for(j=0;j<120;j++)
;
}
void start() //启动
{
SDA=1;
delay();
SCL=1;
delay();
SDA=0;
delay();
SCL=0;
}
void stop() //停止
{
SDA=0;
delay();
SCL=1;
delay();
SDA=1;
delay();
SCL=0;
}
void ChkAck() //应答
{
SDA=1;
SCL=1;
NackFlag=0;
if(SDA==1)
NackFlag=1;
SCL=0;
}
void send8bit(uc num)
{
uc i=8;
uc temp;
temp=num;
for(i=8;i>0;i--)
{
SDA=temp&0x80;
SCL=1;
delay();
SCL=0;
temp=temp<<1;
}
}
void sendbyte(uc num,uc addr)
{
start();
send8bit(0xa0);
ChkAck();
send8bit(num);
ChkAck();
stop();
DelayX1ms(10);
}
void main(void)
{
uc i;
for (i=0;i<0xff;i++)
{
sendbyte(i,i);
DelayX1ms(5);
}
while(1);
}

转自: 单片机精讲吴鉴鹰

围观 518

先说说什么是时钟周期?什么是机器周期?什么是指令周期?

时钟周期

时钟周期也称为振荡周期,定义为时钟脉冲的倒数(可以这样来理解,时钟周期就是单片机外接晶振的倒数,例如12M的晶振,它的时间周期就是1/12 us),是计算机中最基本的、最小的时间单位。在一个时钟周期内,CPU仅完成一个最基本的动作。对于某种单片机,若采用了1MHZ的时钟频率,则时钟周期为1us;若采用4MHZ的时钟频率,则时钟周期为250ns。由于时钟脉冲是计算机的基本工作脉冲,它控制着计算机的工作节奏(使计算机的每一步都统一到它的步调上来)。显然,对同一种机型的计算机,时钟频率越高,计算机的工作速度就越快。但是,由于不同的计算机硬件电路和器件的不完全相同,所以其所需要的时钟周频率范围也不一定相同。我们学习的8051单片机的时钟范围是1.2MHz-12MHz。 在8051单片机中把一个时钟周期定义为一个节拍(用P表示),二个节拍定义为一个状态周期(用S表示)。

机器周期

在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个S周期(状态周期)组成。8051系列单片机的一个机器周期同6个S周期(状态周期)组成。前面已说过一个时钟周期定义为一个节拍(用P表示),二个节拍定义为一个状态周期(用S表示),8051单片机的机器周期由6个状态周期组成,也就是说一个机器周期=6个状态周期=12个时钟周期。

指令周期

指令周期是执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同。对于一些简单的的单字节指令,在取指令周期中,指令取出到指令寄存器后,立即译码执行,不再需要其它的机器周期。对于一些比较复杂的指令,例如转移指令、乘法指令,则需要两个或者两个以上的机器周期。

通常含一个机器周期的指令称为单周期指令,包含两个机器周期的指令称为双周期指令。

一、10MS定时器初值的计算:

1、晶振12M
12MHz除12为1MHz,也就是说一秒=1000000次机器周期。10ms=10000次 机器周期。
65536-10000=55536(d8f0)
TH0=0xd8,TL0=0xf0

2、晶振11.0592M
11.0592MHz除12为921600Hz,就是一秒921600次机器周期,10ms=9216次机器周期。
65536-9216=56320(dc00)
TH0=0xdc,TL0=0x00

二、50MS定时器初值的计算:

1、晶振12M
12MHz除12为1MHz,也就是说一秒=1000000次机器周期。50ms=50000次 机器周期。
65536-50000=15536(3cb0)
TH0=0x3c,TL0=0xb0

2、晶振11.0592M

11.0592MHz除12为921600Hz,就是一秒921600次机器周期,50ms=46080次机器周期。
65536-46080=19456(4c00)
TH0=0x4c,TL0=0x00

三、使用说明

以12M晶振为例:每秒钟可以执行1000000次机器周期个机器周期。而T 每次溢出 最多65536 个机器周期。我们尽量应该让溢出中断的次数最少(如50ms),这样对主程序的干扰也就最小。 开发的时候可能会根据需要更换不同频率的晶振(比如c51单片机,用11.0592M的晶振,很适合产生串口时钟,而12M晶振很方便计算定时器的时间),使用插接式比较方便。

对12MHz 1个机器周期 1us 12/fosc = 1us
方式0 13位定时器最大时间间隔 = 2^13 = 8.192ms
方式1 16位定时器最大时间间隔 = 2^16 = 65.536ms
方式2 8位定时器最大时间间隔 = 2^8 = 0.256ms =256 us
定时5ms,计算计时器初值 M = 2^K-X*Fosc/12 12MHz

方式0: K=13,X=5ms,Fosc=12MHz 则 M = 2^13 - 5*10^(-3)*12*10^6/12= 3192 = 0x0C78
THx = 0CH,TLx = 78H,

方式1: K=16,X=5ms,Fosc=12MHz 则 M = 2^16 - 5*10^(-3)*12*10^6/12= 60536 = 0xEC78
THx = ECH,TLx = 78H,
50ms 12MHz THx = 3CH,TLx = B0H,
10ms THx = D8H,TLx = F0H,

方式2: 最大时间 2^8Fosc/12 = 0.256ms
十进制数是怎么来的?
6MHz 一个机器周期 12/6 = 2us
定时1ms 计数初值x
(2^16-x)*2us = 1000us
x = 2^16 - 500 ,TH,TL 可置 -500
12MHz 一个机器周期 12/12 = 1us
12MHz 一个机器周期 12/12 = 1us
定时50ms 计数初值x
(2^16-x)*1us = 50000us
x = 2^16 - 50000 ,TH,TL 可置 -500

定时器 计内部晶振频率
计数器 计外部输入CPU脚上的脉冲个数 P3.4(T0) P3.5(T1) 负跳变加一
当晶振为6MHz时,最高计数频率500KHz

转自: 玩转单片机

围观 802

P0口作为I/O口输出的时候时,输出低电平为0 输出高电平为高组态(并非5V,相当于悬空状态,也就是说P0 口不能真正的输出高电平)。给所接的负载提供电流,因此必须接(一电阻连接到VCC),由电源通过这个上拉电阻给负载提供电流。

P0作输入时不需要上拉电阻,但要先置1。因为P0口作一般I/O口时上拉场效应管一直截止,所以如果不置1,下拉场效应管会导通,永远只能读到0。因此在输入前置1,使下拉场效应管截止,端口会处于高阻浮空状态,才可以正确读入数据。

由于P0口内部没有上拉电阻,是开漏的,不管它的驱动能力多大,相当于它是没有电源的,需要外部的电路提供,绝大多数情况下P0口是必需加上拉电阻的。

1.一般的P0口在作为地址/数据复用时不接上拉电阻。
2.作为一般的I/O口时用时,由于内部没有上拉电阻,故要接上上拉电阻!!
3.当p0口用来驱动PNP管子的时候,就不需要上拉电阻,因为此时的低电平有效;
4.当P0口用来驱动NPN管子的时候,就需要上拉电阻的,因为此时只有当P0为1时候,才能够使后级端导通。 简单一点说就是它要驱动LCD显示屏显示就必须要有电源驱动,否则亮不了,而恰好P0口没有电源,所以就要外接电源,接上电阻是起到限流的作用;如果接P1、P2、P3端口就不用外接电源和电阻了。

P0口是开漏的,不管它的驱动能力多大,相当于它是没有电源的,需要外部的电路提供,绝大多数情况下P0口是必需加上拉电阻的;5、51单片机的P0口用作数据和地址总线时不必加上拉电阻。

有些IC的驱动能力并不强,如果P0口作为输入而加了不必要的上拉,有可能驱动IC无法将其拉回到低电平,从而使输入失败!

如果是驱动led,那么用1K左右的就行了。如果希望亮度大一些,电阻可减小,最小不要小于200欧姆,否则电流太大;如果希望亮度小一些,电阻可增大,增加到多少呢,主要看亮度情况,以亮度合适为准,一般来说超过3K以上时,亮度就很弱了,但是对于超高亮度的LED,有时候电阻为10K时觉得亮度还能够用。通常就用1k的。对于驱动光耦合器,如果是高电位有效,即耦合器输入端接端口和地之间,那么和LED的情况是一样的;如果是低电位有效,即耦合器输入端接端口和VCC之间,那么除了要串接一个1——4.7k之间的电阻以外,同时上拉电阻的阻值就可以用的特别大,用100k——500K之间的都行,当然用10K的也可以,但是考虑到省电问题,没有必要用那么小的。

对于驱动晶体管,又分为PNP和NPN管两种情况:对于NPN,毫无疑问NPN管是高电平有效的,因此上拉电阻的阻值用2K——20K之间的,具体的大小还要看晶体管的集电极接的是什么负载,对于LED类负载,由于发管电流很小,因此上拉电阻的阻值可以用20k的,但是对于管子的集电极为继电器负载时,由于集电极电流大,因此上拉电阻的阻值最好不要大于4.7K,有时候甚至用2K的。对于PNP管,毫无疑问PNP管是低电平有效的,因此上拉电阻的阻值用100K以上的就行了,且管子的基极必须串接一个1——10K的电阻,阻值的大小要看管子集电极的负载是什么,对于LED类负载,由于发光电流很小,因此基极串接的电阻的阻值可以用20k的,但是对于管子的集电极为继电器负载时,由于集电极电流大,因此基极电阻的阻值最好不要大于4.7K。

对于驱动TTL集成电路,上拉电阻的阻值要用1——10K之间的,有时候电阻太大的话是拉不起来的,因此用的阻值较小。但是对于CMOS集成电路,上拉电阻的阻值就可以用的很大,一般不小于20K,我通常用100K的,实际上对于CMOS电路,上拉电阻的阻值用1M的也是可以的,但是要注意上拉电阻的阻值太大的时候,容易产生干扰,尤其是线路板的线条很长的时候,这种干扰更严重,这种情况下上拉电阻不宜过大,一般要小于100K,有时候甚至小于10K。

根据以上分析,上拉电阻的阻值的选取是有很多讲究的,不能乱用。

来源: 21ic

围观 339

矩阵键盘是单片机是外部设备中所使用的排布类似于矩阵的键盘组。矩阵式结构的键盘显然比直接法要复杂一些,识别也要复杂一些,列线通过电阻接正电源,并将行线所接的单片机的I/O口作为输出端,而列线所接的I/O口则作为输入。矩阵键盘的优点是节约单片机IO口,例如普通键盘8个IO口只能用作8个按键,而矩阵键盘能作16个按键。

本文以51单片机为载体介绍一种4*4矩阵键盘实现16个按键操作的原理、电路和软件设计要点。

1、 矩阵键盘的一般电路

一种实用的单片机矩阵键盘设计

如图1,矩阵键盘电路所示,4*4矩阵键盘有4行4列按键,单片机4个I/O口接矩阵键盘的行线,另外4个I/O口接矩阵键盘的列线,通过对行线列线的操作完成按键的识别和操作。

2、 矩阵键盘的原理

矩阵键盘按键识别的流程一般是这样的:
(1)置第1行为低电平(0),其余行为高电平,读取列线数据,列线有低电平表示此行有按键按下。
(2)置第2行为低电平(0),其余行为高电平,读取列线数据,列线有低电平表示此行有按键按下。
......
(N-1)根据行线列线的电平不同可以识别是否有按键按下,哪一个按键按下,获取按键号。
(N) 根据按键号跳转至对应的按键处理程序。

3、 一个矩阵键盘程序的例子

一种实用的单片机矩阵键盘设计

图2为按键处理的主流程,思路是依次拉低按键的各个行,再读取列线的数据,假如列线有低电平,则认为此行有按键按下,标记此行有按键按下并存入行值。

读取列线数据的思路则是这样,依次读取第1-4列,假如读取此列时为低电平,则标记并存入列值。

下面是部分代码:

//(1)按键识别程序
void key() //按键扫描
{
unsigned char key_value_temp; //临时按键值,默认1111 1111(二进制)
key_value=0xff;
//键值
key_value_temp=0xff; //键值临时变量
//矩阵键盘程序流程
//依次拉低第1234行,读入列线数据
Pin_r_1=0; Pin_r_2=1;Pin_r_3=1;Pin_r_4=1; //拉低第1行。
//Pin_r_1第1行行线,Pin_r_2第2行行线,其余类似
key_value_temp=read_column();//读列数据
if (key_value_temp != 0xff)
//表示第一行有按键按下
{key_value=key_value_temp & 0x1f;
//获取按键号,例如0001
1101表示第1行第2列的按键按下
}
Pin_r_1=1; Pin_r_2=0;Pin_r_3=1;Pin_r_4=1; //拉低第2行
key_value_temp=read_column();//读列数据
if (key_value_temp != 0xff)
//表示第2行有按键按下
{key_value=key_value_temp &
0x2f;
}
Pin_r_1=1; Pin_r_2=1;Pin_r_3=0;Pin_r_4=1; //拉低第3行
key_value_temp=read_column();//读列数据
if (key_value_temp != 0xff)
//表示第3行有按键按下
{key_value=key_value_temp &
0x3f;
}
Pin_r_1=1; Pin_r_2=1;Pin_r_3=1;Pin_r_4=0; //拉低第4行
key_value_temp=read_column();//读列数据
if (key_value_temp != 0xff)
//表示第4行有按键按下
{key_value=key_value_temp &
0x4f;
}
}
//(2)读取列线数据
unsigned char read_column() //读矩阵键盘的列
{unsigned char key_column;
key_column=0xff;
if(Pin_c_1==0 ) key_column=key_column & 0xfe;
//1110
表示第1列按键按下。 key_column的低4位表示按键号,高4位常为1111
if(Pin_c_2==0 ) key_column=key_column & 0xfd;
//1101
表示第2列按键按下。
if(Pin_c_3==0 ) key_column=key_column & 0xfb;
//1011
表示第3列按键按下。
if(Pin_c_4==0 ) key_column=key_column & 0xf7;
//0111
表示第4列按键按下。
return key_column;
}

以上程序对应的键值表如下:

一种实用的单片机矩阵键盘设计

最后在程序中处理键值跳转至相应的操作即可。
有一个4*4矩阵键盘的proteus仿真例子实现了如下功能:
1、利用行扫描法读取4x4矩阵键盘键值;
2、LCD1602上显示按下的按键值。

来源: 21ic.com

围观 689

双向IO口的输出:互补推挽

在51单片机的P0口工作在普通IO口模式下,为准双向IO口。而工作在第二功能状态下时,则为标准的双向IO口。由于双向IO口的输出,要求能输出高低电平,通常会采用互补推挽电路。

在第二功能状态下,51单片机P0口采用的是互补推挽的输出方式。何为互补推挽呢?下面是它的等效电路图。

51单片机I/O口的讲解

当P0第二功能作为输出时,K1和K2两个开关轮流打开。K2闭合K1打开,就会输出高电平,并且其驱动能力很大,因为电子开关的阻值小(不像上拉电阻的值那么大)。反之K2打开,K1闭合,就会输出低电平。

两个开关交替导通,互为补充,“挽”是“拉”的意思,两个电子开关分别负责在IO口输出处“推”和“拉”电流,所以称为互补推挽。

这种IO口结构的优点很明显,驱动能力强,稳定可靠。缺点在于实现起来比较困难。在切换输出电平的过程中,例如从低电平切换到高电平,当K1断开时,要求尽可能快的输出高电平,也就是K2应该立即闭合;同时,如果K1还没断开,K2就提前闭合了,相同于两个开关同时导通,会直接短路,后果又会很严重。所以需要用电路控制好两个开关的协调工作。

双向IO口的输入:高阻态、输入电阻

双向IO口的输出,只要求能输出高低电平,因此并不是必须采用互补推挽电路。而采用互补推挽电路的好处在于,这种电路同时又可以实现高阻态的输入,从而实现标准双向IO口。

当图中的K1和K2同时断开时,IO口就可以工作在高阻态的输入状态下。高阻态到底是什么样的一个概念呢?

当IO口处于高阻态时,也将其称为浮空输入状态,其电平是悬浮不定的,既不是高电平也不是低电平。我们可以想象单片机在检测IO口的电平高低时,相当于在CPU里面有一个类似电压表的东西,并且这个电压表内阻很大,例如图中给出的100MΩ。在这里,我们可以把这个电压表的内阻称为P0.0口此时的输入电阻(也可以近似认为是输出阻抗,电阻是对直流电而言,而阻抗是对交流电来说的。这是模拟电路的知识,这里不做细说)。

51单片机I/O口的讲解

现在试想,如果不小心用手碰到了P0.0端口,而由于人体本身就是阻值很大的导体,周围有很多电磁波干扰,手上可能存在一些很微弱的电流,这个时候,电压表的读数就会发生变化,单片机读取的电平高低就会变。高阻态表现出来的结果就是外界很小的干扰,都可能导致读取的电平变化,甚至即使没有碰这个IO口,它每次读取的结果也可能不一样,因为外界的电磁波等可能会干扰到IO口。稍后我们会利用51单片机做个实验,来体验P0口的高阻态。

高阻态的意义、输出电阻

为什么双向IO口输入的时候要求是高阻态呢?

我们假设有一种装置,等效电路如下图。开关上下切换,它就会输出高低电平,通过电压表可以检测出来。但是其驱动能力很弱,连LED也驱动不了。装置里的100kΩ,可以叫做装置的输出电阻(同样也可以近似认为是输出阻抗)。

51单片机I/O口的讲解

让这个装置输出低电平,然后连接51单片机的P1.0口。这时,VCC经过10kΩ上拉电阻到达IO口,再到装置内部的100kΩ电阻,通过开关K接到GND。根据分压原理,P1.0上的电压值大概是4.55V,于是单片机读取的是高电平。而事实是,装置想输出低电平告知51单片机。这里单片机管脚作为输入功能,却干扰了外界装置的输出值,相当于单片机的这个IO口也在输出。
51单片机I/O口的讲解

当单片机的P0口工作在第二功能的输入状态,或者工作在普通IO口的输入状态,且没有外界上下拉电阻,内部的两个电子开关都是断开的,对外部呈现高阻态。从图中可以看出,装置输出的电平能被准确的读取到单片机中。之所以能准确读取,就是因为装置输出电阻比单片机IO口的输入电阻要小。
51单片机I/O口的讲解

有人可能会说,如果把装置中的电阻换成1000MΩ,这个时候这个单片机又不能准确读取电平了。但是一般情况下,我们不需要考虑这么极端。如果是理想的高阻态,其输入阻抗应该是无穷大,而这有点像超导体一样比较特殊。一般情况下认为导线电阻几乎为0,同样也认为高阻态输入电阻是无穷大。

总的来说,就是高阻态情况下,IO口输入电阻很大,而不容易干扰那些输出电阻较大、驱动能力弱的装置输出到IO口上的电平。

出处:玩转单片机

围观 360

看门狗分硬件看门狗和 软件看门狗。硬件看门狗是利用一个定时器电路,其定时输出连接到电路的复位端,程序在一定时间范围内对定时器清零(俗称“喂狗”),因此程序正常工作时, 定时器总不能溢出,也就不能产生复位信号。如果程序出现故障,不在定时周期内复位看门狗,就使得看门狗定时器溢出产生复位信号并重启系统。软件看门狗原理 上一样,只是将硬件电路上的定时器用处理器的内部定时器代替,这样可以简化硬件电路设计,但在可靠性方面不如硬件定时器,比如系统内部定时器自身发生故障 就无法检测到。当然也有通过双定时器相互监视,这不仅加大系统开销,也不能解决全部问题,比如中断系统故障导致定时器中断失效。

看门狗本身不是用来解决系统出现的问题,在调试过程中发现的故障应该要查改设计本身的错误。加入看门狗目的是对一些程序潜在错误和恶劣环 境干扰等因素导致系统死机而在无人干预情况下自动恢复系统正常工作状态。看门狗也不能完全避免故障造成的损失,毕竟从发现故障到系统复位恢复正常这段时间 内怠工。同时一些系统也需要复位前保护现场数据,重启后恢复现场数据,这可能也需要一笔软硬件的开销。

详解多任务看门狗及喂狗方法
图1:(a) 多任务系统看门狗示意图;(b) 相应的看门狗复位逻辑图

在单任务系统中看门狗工作原理如上所述,容易实现。在多任务系统中情况稍为复杂。假如每个任务都像单任务系统那么做,如图1(a)所示,只要有一个 任务正常工作并定期“喂狗”,看门狗定时器就不会溢出。除非所有的任务都故障,才能使得看门狗定时器溢出而复位,如图1(b)。

而往往我们需要的是只要有一个任务故障,系统就要求复位。或者选择几个关键的任务接受监视,只要一个任务出问题系统就要求复位,如图2(a)所示,相应的看门狗复位逻辑如图2(b)所示。

在多任务系统中通过创建一个监视任务TaskMonitor,它的优先级高于被监视的任务群Task1、Task2...Taskn。 TaskMonitor在Task1~Taskn正常工作情况下,一定时间内对硬件看门狗定时器清零。如果被监视任务群有一个Task_x出现故 障,TaskMonitor就不对看门狗定时器清零,也就达到被监视任务出现故障时系统自动重启的目的。另外任务TaskMonitor自身出故障时,也 不能及时对看门狗定时器清零,看门狗也能自动复位重启。接下来需要解决一个问题是:监视任务如何有效监视被监视的任务群。

详解多任务看门狗及喂狗方法
图2:(a) 多任务系统看门狗示意图;(b) 正确的看门狗复位逻辑图

在TaskMonitor中定义一组结构体来模拟看门狗定时器组,

typedef struct

{

UINT32 CurCnt, LastCnt;

BOOL RunState;

int taskID;

} STRUCT_WATCH_DOG;

该结构体包括被监视的任务号taskID,用来模拟“喂狗”的变量CurCnt、LastCnt(具体含义见下文),看门狗状态标志RunState用来控制当前任务是否接受监视。

被监视的任务Task1~Taskn调用自定义函数CreateWatchDog(int taskid)来创建看门狗,被监视任务一段时间内要求“喂狗”,调用ResetWatchDog(int taskid),这个“喂狗”动作实质就是对看门狗定时器结构体中的变量CurCnt加1操作。TaskMonitor大部分时间处于延时状态,假设硬件 看门狗定时是2秒,监视任务可以延时1.5秒,接着对创建的看门狗定时器组一一检验,延时前保存CurCnt的当前值到LastCnt,延时后比较 CurCnt与LastCnt是否相等,都不相等系统才是正常的。需要注意的是CurCnt和LastCnt数据字节数太小,而“喂狗”过于频繁,可能出 现CurCnt加1操作达到一个循环而与LastCnt相等。

如果有任意一组的CurCnt等于LastCnt,认为对应接受监视的任务没有“喂狗”动作,也就检测到该任务出现故障需要重启,这时候 TaskMonitor不对硬件看门狗定时器清零,或者延时很长的时间,比如10秒,足以使得系统重启。反之,系统正常,Task1~Taskn定期对 TaskMonitor“喂狗”,TaskMonitor又定期对硬件看门狗“喂狗”,系统就得不到复位。还有一点,被监视任务可以通过调用 PauseWatchDog(int taskid)来取消对应的看门狗,实际上就是对STRUCT_WATCH_DOG结构体中的RunState操作,该标志体现看门狗有效与否。

这种方式可监视的最大任务数由STRUCT_WATCH_DOG结构数据的个数决定。程序中应该有一个变量记录当前已创建的看门狗数,判断被监视任务Task1~Taskn是否“喂狗”只需比较CurCnt与LastCnt的值n次。

硬件看门狗监视TaskMonitor任务,TaskMonitor任务又监视其他的被监视任务Task1~Taskn,形成这样一种链条。这种方 式系统的故障图表示如图3所示。被监视任务Task1~Taskn及TaskMonitor都是或的关系,因此被监视的任一任务发生故障,硬件电路看门狗 就能复位。

为实现多任务系统的看门狗监视功能额外增加了TaskMonitor任务,这个任务占用执行时间多少也是一个重要问题。假设 TaskMonitor任务一个监视周期延时1.5秒,此外需要执行保存当前计数值,判断是否“喂狗”等语句,它的CPU占用时间是很小的。用一个具体的 试验证实,使用50M工作频率的CPU(S3C4510),移植vxWorks操作系统,cache不使能条件下监视10个任务,每个监视周期占用 220~240微秒。可见该任务绝大多数时间都处于任务延时状态。

被监视任务可能有获取消息、等待一个信号量等的语句,往往这个消息、信号量的等待是无限期的等待。这就需要将这类语句作一些修改。比如在vxWorks中将一次无期限的获取信号量操作

semTake(semID, WAIT_FOREVER); // WAIT_FOREVER为无限时间等待

分解为

do

{

ResetWatchDog; // “喂狗”操作

}while(semTake(semID, sysClkRateGet( )) != OK); // 1s内的等待信号量操作

多次的时间范围内的获取信号量操作,这样才能保证及时“喂狗”。

另外需要注意的是系统中是否有的任务优先级比TaskMonitor高并且长时间处于执行状态,TaskMonitor长时间得不到调度,使得看门狗错误复位。良好的任务划分,配置是不应该出现这种高优先级任务长期执行状况的。

来源: 21ic.com

围观 919

页面

订阅 RSS - 51单片机