单片机,英文Micro Controller Unit,简称MCU。其内部集成了CPU、RAM(random access memory,即随机存取存储器)、ROM(read-only memory,即只读存储器)、定时器、中断系统、通讯接口等一系列硬件功能,单片机的任务有信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机、LCD1602等)控制等。本文是对个人通过“江科大自化协”学习C51单片机过程中的一些总结,更多的偏向应用原理和编程,程序开发环境是Keil uVision5,烧录软件是STC-ISP(V6.90U)。更详细的资料可以打开以下网站:http://prechin.net/forum.php?mod=viewthread&tid=35264&extra=page%3D1
可支持Windows XP、Win7、Win8、Win10、Win11系统。也可以打开以下网盘链接:链接:https://pan.baidu.com/s/1ZvS_Bxj5T0Rg7rKsNoaArQ?pwd=0018 提取码:0018。
一、C51编程中的一些基础知识
1、sfr(special function register):特殊功能寄存器声明。
例:sfr P0 = 0x80;
声明P0口寄存器,物理地址为0x80。
2、sbit(special bit):特殊位声明。
例:sbit P0_1 = 0x81;( 或 sbit P0_1 = P0^1;)
声明P0寄存器的第1位。
- 可位寻址:位寻址指的是这个寄存器中各位值可以直接调用。如P2_0 = 0;可以点亮LCD1。
- 不可位寻址:如TMOD只可以通过TMOD &= 0xF0; TMOD |= 0x01;//设置定时器模式,来控制其中的各位。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作。
- C51引脚置“1”时输出高电平,置“0”时输出低电平;单片机在默认状态下引脚会置高电平。
- C51单片机是一个8位操作系统,用十六进制数据表示的范围是0x00~0xFF。
- VCC是电源正极的意思,GND是电源负极的意思。
- 本文涉及开发板原理图,下载相关资料便于理解。
二、LED的基本应用
在C51的开发板原理图中,8个LED的正极都接在VCC上,即高电平;那么只需要给P2的相关引脚置“0”即可导通,从而点亮LED。如P2_1 = 0;可以点亮LED1,也可以用P2 = 0xFE;点亮LED1,其它的LED点亮方法以此类推。
LED进阶的应用是LED的一亮一灭,因为IO口翻转的速度过快,导致人眼无法捕捉的LED的亮灭变化,所以要调用延时函数。STC-ISP(V6.90U)可以生成延时函数,如
void Delay(xms) //@11.0592MHz,延时1ms
{
unsigned char i, j;
i = 2;
j = 199;
while(xms) //xms不为0时都会执行此循环
do
{
while (--j);
} while (--i);
xms--; //每执行一次while(xms)循环,就自减1
}
调用一次延时函数后,P2_0口取反,即P2 _0= ~P2_0;就可以实现LED亮灭。更高阶的LED呼吸灯是通过PWM驱动实现,后面会有相关知识。
三、独立按键
轻触按键相当于一种电子开关,按下时开关闭合,松开时开关断开,实现原理是利用按键内部的金属弹片受力弹动。对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地闭合,在断开时也不会马上稳定地断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动,抖动的时间一般在5ms~10ms。
对于独立按键来说,按键没被按下时处于高电平状态,按下的时候处于低电平状态,即导通。独立按键可以通过if()语句进行使用,如if(P3_1==0){P2_0=0;}就可以实现按下独立按键1点亮LED1。在应用时可以通过调用延时函数进行消抖,如果按键按下一直不松手,可以通过while(P3_0==0)进行死循环,松手时才跳出循环执行下一条语句,这也意味着main()函数卡在这里了,下面的语句必须要按键松开才会执行,导致了CPU占用严重。可以通过定时器中断扫描解决这个问题,关于这点后面会有所介绍。
四、LED动态数码管
LED动态数码管是一种由多个发光二极管封装在一起组成“8”字型的显示器。它有共阳极和共阴极两种封装方式,可以根据开发板原理图和通过测试进行判断,带有COM(common)字样的一端是共极的,我所使用的C51中的LED数码管是共阴极封装方式,LED动态数码管会带有74HC138译码器和74HC245两个模块器件,以共阴极为例,由于数码管有8个,那么它的阴极引脚相应地也是8个,8个引脚都接到单片机核心的IO口上显然很占用线路,74HC138译码器实现通过3个引脚控制8个引脚的电平状态。
在开发板原理图中,74HC138译码器的A、B、C引脚分别连接P2_2、P2_3、P2_4引脚上,通过软件控制时要注意它们的使用方式,A、B、C引脚是使用二进制数据控制的,通过二进制转换成十进制来控制相应的Y0、Y1、Y2、Y3、Y4、Y5、Y6、Y7;它们的头上有一条横线,表明低电平有效,这也能判断LED动态数码管的封装方式;二进制有高低位之分,74HC138译码器的A、B、C引脚的最高位是C脚,最低位是A脚,如P2_4=1;P2_3=1;P2_2=0;表示Y6置“0”,对应的LED7是低电平状态,即导通。这也导致了一次只能使一个数码管显示数字,但是只要扫描的速度快,利用LED的余辉和人眼的反应速度比较慢的特性就能实现多个数码管同时显示的效果。还要注意LED7对应的是从左向右数的第二个数码管。
每个LED动态数码管有8个LED,分别编号为a、b、c、d、e、f、g、dp,具体可以看原理图;8个数码管,4个是一组,每组的a、b、c、d、e、f、g、dp引脚都只有一个,而且相同编号的引脚都接在同一个IO口上,a、b、c、d、e、f、g、dp对应的IO口分别是P0_0、P0_1、P0_2、P0_3、P0_4、P0_5、P0_6、P0_7,通过控制这些IO口输出“0”或“1”就能让数码管断开或导通,但是给一个IO口置“1”会同时控制各组相应的LED片段处于高电平状态,74HC138译码器的缺陷和这种连线方式刚好互补。
通过软件控制74HC138译码器和P0相应的IO口状态就能实现不同位置的数码管的显示不同数字,在这里给出段码表0(0x3F),1(0x06),2(0x5B),3(0x4F), 4(0x66),5(0x6D),6(0x7D),7(0x07),8(0x7F),9(0x6F),A(0x77),B(0x7C),C(0x39),D(0x5E),E(0x79),F(0x71),空(0x00)。数码管扫描,即输出扫描,通过第1个数码管显示→第2个数码管显示→第3个数码管显示→……,快速且重复进行循环,最终实现所有数码管同时显示的效果;扫描速度太快数码管的显示会出错,所以显示多个数字时需要调用延时函数,一般延时20ms。
五、矩阵键盘(4x4)
首先,我们需要明白单片机核心的P1、P2、P3的IO口都是弱上拉模式,P1、P2、P3的每个引脚都分别通过一个10K欧姆的上拉电阻连在VCC上;P0的IO口其实是开漏输出模式,但是电路的连接方式和P1、P2、P3的IO口一样,所以也可以看成是弱上拉模式,说明引脚的IO口高电平驱动能力弱,而低电平驱动能力强,所以单片机核心的引脚短接是可以检测到低电平状态的。准双向口输出配置和开漏输出配置的具体内容可以通过其它渠道进行了解。
在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式采用逐行或逐列进行扫描,读出每个按键的状态。在此开发板中矩阵按键使用P1口,编程时应该向单片机输入低电平扫描检测的方式,所以把P1口都置“1”,根据使用的是逐行扫描还是逐列扫描把P1口相关引脚置“0”。以逐列扫描为例,先配置P1_3 = 0;,然后用if()语句判断P1_7、P1_6、P1_5、P1_4哪个处于低电平状态,即哪个按键按下,从而得到相应按键的键码。这里和独立按键有相似的地方,都需要调用延时函数进行消抖,按下后不松手的处理方法一致,使用while()死循环,也会导致main()函数被卡住,用定时器中断程序可以解决,不过按键数量多导致一定程度上的困难,定时器中断扫描矩阵键盘我还没有试验过。
六、定时器
C51的定时器于单片机的内部资源,其电路的连接和运转均在单片机内部完成,可用于计时系统,实现软件计时,或实现程序每隔一段时间执行给定的语句;可替代延时函数,提高CPU的运行效率和处理速度等。像51这种的古老单片机只有T0和T1两个定时器,其它型号单片机的T0、T1与传统的51单片机是兼容的。
T0定时器有四种工作模式分别是模式0(13位定时器/计数器);模式1(16位定时器/计数器(常用));模式2(8位自动重装模式);模式3(两个8位计数器)。T1定时器工作模式比T0定时器工作模式少一个模式3。模式0由TL0(Timer Low 0)的低5位和TH0(Timer High 0)的8位构成,TL0低5位溢出后向TH0进位,TH0计数溢出后置位TCON(定时器/计数器控制寄存器)中的溢出标志位TF0,即通过硬件使TF = 1;,如果此时有中断程序,就会跳转到中断程序中执行相关语句。模式1与模式0类似。模式2是TL0溢出会置位TF0,即TF = 1;,且将TH0的数值重新装入TL0中,重装时TH0数值不变。模式3是把一个T0定时器拆成两个定时器使用。
定时器/计数器的核心部件是一个加法(也有减法)的计数器,其本质是对脉冲进行计数,只是计数脉冲来源不同:如果计数脉冲来自系统时钟(即晶振周期,晶振器件上标有振动频率),则为定时方式,此时定时器/计数器每12个时钟或者每6个时钟得到一个计数脉冲,计数值加1;如果计数脉冲来自单片机外部引脚(T0为P3.4,T1为P3.5),则为计数方式,每来一个脉冲加1。
STC90C51RC/RD+系列单片机有两种定时器计数速率:一种是12T模式,每12个时钟加1,与传统8051单片机相同;另一种是6T模式,每6个时钟加1,速度是12T模式的2倍。STC-ISP(V6.90U)可以对定时器计数器的相关参数进行选择,C51单片机是选择12T模式,一般选择模式1(16位定时器/计数器)。由于定时器的知识比较多,但是所需要的代码比较少且容易通过STC-ISP(V6.90U)获得,这里就不多赘述了,更详细的信息可以参考网盘链接里的STC90C51 52RC-RD _GUIDE-CHINESE.pdf手册,通过把里面的缩写还原成原本的英文单词更便于记忆定时器不同操作位的中文意思和所具有的功能。
void Timer0Init(void) //1毫秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式(可去掉)
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
七、中断程序
当CPU正在执行某个程序时,外界向CPU发送中断请求,要求CPU暂停当前工作,转去处理这个紧急事件,处理完之后再回到原来被中断的地方,继续执行原来的程序,此过程称为中断。中断源有优先级之分,通过PX0H,PX0(外部中断0)进行优先级设置,可以看作二进制比较大小来决定优先级,如PX0H = 0;PX0 = 1;是优先级1,PX0H = 1,PX0 = 0;是优先级2。还可以利用优先级的差别进行中断嵌套,即CPU处理中断程序时,被优先级更高的中断源打断,转而去处理优先级更高的中断程序,处理完之后再执行原中断程序。以T0定时器中断程序为例,
void Timer0Init(void) //定时器0初始化且开启溢出中断程序
{
AUXR &= 0x7F; //定时器时钟12T模式(可去掉)
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时,且不会停止
ET0=1; //T0溢出中断允许位开启
EA=1; //CPU的总中断允许控制位开启
PT0=0; //中断优先级设置,此时是最低优先级。C51单片机的TO没有PT0H,只能设置两个优先级
}
使用C语言编程的中断号:
void Int0_Routine() interrupt 0;
void Timer0_Routine() interrupt 1;
void Int1_Routine() interrupt 2;
void Timer1_Routine() interrupt 3;
void UART_Routine() interrupt 4;
void Timer2_Routine() interrupt 5;
void Int2_Routine() interrupt 6;
void Int3_Routine() interrupt 7;
中断号其实是一个中断程序的函数名,所以中断程序要执行的语句写在里面,中断程序是独立于main()函数之外的,一般的函数都需要main()函数调用之后才可以执行,中断程序则不用经过main()函数调用就可以执行。还要注意的是定时器溢出之后,会通过硬件重置TL0 = 0x00;TH0 = 0x00;,所以要通过软件设置TL0和TH0,达到相隔一段固定的时间CPU就响应T0的溢出中断请求。
外部中断有两种触发方式分别是低电平触发和下降沿触发,IT0/TCON.0 = 1;是下降沿触发;IT0/TCON.0 = 0;是低电平触发。定时器中断的溢出触发是自动的,不需要选择触发方式。
八、串口
串口是一种成本低、容易使用、通信线路简单,可实现两个设备的互相通信、应用十分广泛的通讯接口。51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。简单双向串口通信有两根通信线(发送端TXD和接收端RXD,分别与单片机核心的P3.0、P3.1复用),TXD与RXD要交叉连接,当只需单向的数据传输时,可以直接一根通信线,当电平标准不一致时,需要加电平转换芯片。
通信方式有以下概念,全双工(通信双方可以在同一时刻互相传输数据)、半双工(通信双方可以互相传输数据,但必须分时复用一根数据线)、单工(通信只能有一方发送到另一方,不能反向传输)以及异步(通信双方各自约定通信速率)、同步(通信双方靠一根时钟线来约定通信速率)。UART是全双工、异步的通信方式。
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种,TTL电平(+5V表示1,0V表示0)、RS232电平(-3~-15V表示1,+3~+15V表示0)、RS485电平(两线压差+2~+6V表示1,-2~-6V表示0(差分信号))。UART使用的是TTL电平。
STC89C52有1个UART,有四种工作模式,模式0(同步移位寄存器)、模式1(8位UART,波特率可变(常用))、模式2(9位UART,波特率固定)、模式3(9位UART,波特率可变以模式)。以模式1的发送数据为例,下面的代码可以在STC-ISP(V6.90U)中配置好相关的参数后生成,要尽量减少误差值,防止数据传输出错。
void UartInit(void) //4800bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速,SMOD = 0;SMOD0 = 1;
SCON = 0x50; //8位数据,可变波特率,其中的REN = 1;(允许/禁止串行接受位),REN可以复位,即SCON = 0x40;
AUXR &= 0xBF; //定时器时钟12T模式,8051单片机需要去掉此语句
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器,串口通信只能选择定时器1和定时器2,8051单片机需要去掉此语句
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式,即选择定时器1
TL1 = 0xFA; //设置定时初始值,用于计算波特率,0xFA=250,加到256会溢出;波特率=(2^SMOD/32) x SYSclk系统工作时钟频率
TH1 = 0xFA; //设置定时重载值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //定时器1开始计时
}
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte; //SBUF在“=”左边表示发送数据,
while(TI==0); //发送完成时,硬件会置位TI
TI=0; //软件复位TI
}
SBUF是串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址0x99。写操作使用发送寄存器;读操作使用接收寄存器;就是一个存储数据的寄存器。
模式1接收数据的相关配置,
void UartInit(void) //4800bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器时钟12T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xFA; //设置定时初始值
TH1 = 0xFA; //设置定时重载值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //定时器1开始计时
EA=1; //CPU的总中断允许控制位开启
ES=1; //串行口中断允许位开启
}
以下代码是为了直观显示电脑向单片机成功发送数据:
void UART_Routine() interrupt 4
{
if(RI==1) //接收中断请求,即数据接收完毕
{
P2=~SBUF; //读取数据用于LED点亮
RI=0; //接收标志位复位
}
}
关于串行口寄存器不同位的功能和配置,详见手册。
九、LED点阵屏
LED点阵屏由若干个独立的LED组成,以矩阵的形式排列,需要进行逐行或逐列扫描以灯珠亮灭来显示文字、图片、视频等。LED点阵屏与数码管一样,有共阴和共阳两种接法,不同的接法对应的电路结构不同,8*8LED点阵屏有16个引脚,为了较少IO口的占用,有8个引脚连接在74HC595上。
74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,常用于IO口扩展。通过SER写入数据,每次只能写入一位数据,所以可以用&运算和if()语句进行输入,如
unsigned char i;
for(i=0;i<8;i++)
{
SER=Byte&(0x80>>i);
SERCLK=1;
SERCLK=0;//SERCLK上升沿移位
}
RCLK=1;
RCLK=0;//上升沿锁存,即串行数据全部锁存为并行数据,即DPa、DPb、DPc、DPd、DPe、DPf、DPg、DPh都存储数据,表现形式是高、低电平状态。
LED点阵屏的正极接在74HC595的引脚上,负极接在核心的P0上,进行逐行或逐列扫描时,P0引脚也可以用&运算和if()语句减少代码的数量。由于P0口置“0”,LED才会导通,所以进行多行多列显示时,要调用延时函数,调用之后P0口更换时需要P0口都置“1”,即P0 = 1;。显示图形和文字需要给74HC595输入不同的串行数据,可以使用“取字模软件”得到所需输入的串行数据。
更高级一点是LED点阵屏显示动画,使用“取字模软件”得到需要显示的动画的串行数据,画字模时字长需要超过8个格子。需要用到if()语句和延时函数是字体流动起来,如
unsigned char i,offset=0;
while(1)
{
for(i=0;i<8;i++)
{
MatrixLED_ShowColumn(i,Animation[i+offset]);//取数组Animation中的数据
}
Delay(10);
offset++;
if(offset>32)//8的倍数,避免数组越界
{
offset=0;
}
}
十、DS1302实时时钟
RTC(Real Time Clock),即实时时钟,是一种集成电路,通常称为时钟芯片,且可以对年、月、日、周、时、分、秒进行计时,具有闰年补偿等多种功能DS1302。实时时钟的引脚名及作用:
引脚名 | 作用 | 引脚名 | 作用 |
VCC2 | 主电源 | CE | 芯片使能 |
VCC1 | 备用电源 | IO | 数据输入/输出 |
GND | 电源地 | SCLK | 串行时钟 |
X1、X2 | 32.768KHz晶振 |
时钟使用的是BCD码(Binary Coded Decimal),即用4位二进制数来表示1位十进制数。如1000 0101表示85,但0001 1010不合法(低四位用十进制表示>10)。用十六进制进行表示,如0x85表示85,但0x1A不合法(A>10)。两者之间的转换关系如下:
BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)
因为时钟使用两位BCD码就足够,所以这里只给出2位的转换关系。
时钟寄存器的年、月、日、周、时、分、秒读写地址是固定的,通过IO写入或读取命令和数据时需要用到if()语句,写命令是用十六进制数据,但写数据是用BCD码,所以十进制数据要转换成BCD码才能被时钟识别。实时时钟有SCLK引脚,传输数据的方式与74HC595相似,即通过SCLK的上升沿传输数据;发送完8个字节后就进入接收数据的时序,即先发送命令后接收数据;通过SCLK下降沿读取数据,CE在整个过程中都是高电平状态。READ传输和接收数据的时序就是这个方式,正常来说,写和读共有16个字节,那应该有16个波形,但是READ只有15个波形,所以写命令结束之后的下降沿就已经接收第1个字节,可以通过先给IO口写入1个字节,然后来个下降沿和上升沿,这样发完8个字节后IO还是处于高电平状态,即
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i);//通过&运算写入命令
DS1302_SCLK=0;
DS1302_SCLK=1;
}
WRITE只有写命令的时序且是16个波形。
写程序时通过BCD码先写入自定义的年、月、日、周、时、分、秒,然后再读取出来。写入和读取的是相同数据,但读取的数据是实时时钟计时功能所得到的数据,前后有所不同。高阶的可调时钟涉及的模块较多,最注意的还是年、月、日、周、时、分、秒的越界判断及处理,主要使用if()语句和逻辑||写程序。
十一、蜂鸣器
蜂鸣器是一种将电信号转换为声音信号的器件。蜂鸣器按驱动方式可分为有源蜂鸣器(内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定)和无源蜂鸣器(内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音)。8051单片机使用的是无源蜂鸣器,想要发出不同频率的声音可经过相应周期后翻转IO口实现,由于一次周期要进行两次IO口翻转,所以翻转频率是声音频率的两倍,即翻转周期是声音频率计算出的周期的1/2。用定时器0进行重装载值进行计时和溢出中断请求可以简化代码,使蜂鸣器发出不同频率声音,如
//蜂鸣器端口定义
sbit Buzzer=P1^5;
void Timer0_Routine() interrupt 1
{
/*取对应频率值的重装载值到定时器*/
TL0 = 翻转周期%256; //设置定时初值
TH0 = 翻转周期/256; //设置定时初值
Buzzer=!Buzzer; //翻转蜂鸣器IO口
}
以11.0592MHz的晶振(每(11.0592/12)us计时器加1)为例,
声音周期(us)=(11.0592/12)/(不同声音的频率(Hz))*1000000;
翻转周期(us)=(声音周期(us))/2;
重装载值(us)=65536-(翻转周期(us));
在开发板原理图中,蜂鸣器有一端已经接在VCC上,另一端的BEEP与ULN2003D的OUT5连接,ULN2003D内部连接非门电路,所以P2.5置高电平的瞬间,蜂鸣器会振动发声;P2.5置低电平,则蜂鸣器电路的BEEP端口相当于断路。
蜂鸣器的高阶用法是演奏音乐,计算出每个音符对应的频率,然后计算重装载值装在一个数组中,通过对照乐谱,用重装载值控制定时器0每次进入中断所需的时间。根据乐谱的每个音符所需要的发声时间调用延时函数,进行发音延长或者音符间的停顿。
十二、AT24C02
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。AT24C02通过EEPROM(电可擦除可编程ROM )进行存储,通过I2C总线进行通讯,容量只有256字节。
EEPROM引脚:
引脚 | 功能 |
VCC、VSS | 电源(1.8V~5.5V) |
WE | 写使能(低电平有效) |
SCL、SDA | I2C接口 |
E0、E1、E2 | I2C地址 |
I2C总线(Inter IC BUS)是一种通用数据总线,有SCL(Serial Clock)和SDA(Serial Data)两根通信线,是同步、半双工的通信方式且带数据应答。所有I2C设备的SCL和SDA分别连在一起,设备的SCL和SDA均要配置成开漏输出模式(相当于弱上拉模式去掉VCC和上拉电阻,输出“0”时,闭合开关,引脚直接与GND相连;输出“1”时,断开开关,相当于引脚悬空,此时极易受外部干扰),SCL和SDA各添加一个阻值约为4.7KΩ的上拉电阻,,开漏输出和上拉电阻的共同作用实现了“线与”的功能,能有效解决多机通信互相干扰的问题。
I2C时序结构:
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平。
b、终止条件:SCL高电平期间,SDA从低电平切换到高电平。
c、发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后SCL置高电平,从机会在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
d、接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后SCL置高电平,主机会在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节。主机在接收之前,需要SDA置高电平,即SDA = 1;。
e、发送应答(SA,从机向主机发送字节):在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
f、接收应答(RA,主机向从机发送字节):在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答。主机在接收之前,需要SDA置高电平,即SDA = 1;。
AT24C02时序结构:
SLAVE ADDRESS+R/W ̅ |
=
D6 | D5 | D4 | D3 | D2 | D1 | D0 | R/W(W低电平有效) |
- 字节写:在WORD ADDRESS处写入数据DATA。
S | S:SLAVE ADDRESS+W | RA:0 | S:WORD ADDRESS | RA:0 | S:DATAn | RA:0(1) | P |
- 读数据:读取在WORD ADDRESS处的数据DATA
S | S:SLAVE ADDRESS+W | RA:0 | S:WORD ADDRESS | RA:0 |
+
S | S:SLAVE ADDRESS+R | RA:0 | R:DATAn | SA:1(0) | P |
AT24C02(EEPROM)的高8位固定地址为1010,可配置地址(即E0、E1、E2)在开发板上都接GND,所以其地址为000。因此SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1。
十三、DS18B20
DS18B20是一种常见的数字温度传感器,内置温度报警功能,其控制命令和数据都是以数字信号的方式进行传输。DS18B20通过1-Wire(单总线)进行通信,可测-55°C~ 125°C的范围;可形成总线结构;还可寄生供电,即VCC可去掉,由IO口进行供电和传输数据、命令。
DS18B20引脚:
引脚 | 功能 |
VCC | 电源(3.0V ~ 5.5V) |
I/O | 单总线接口 |
GND | 电源地 |
单总线(1-Wire BUS)是由一根通信线DQ组成的通用数据总线,使用异步、半双工的通信方式。当单总线采用寄生供电时,供电加上通信只需DQ和GND两根线。
单总线时序结构:
- 初始化:主机将总线置低电平至少480us,然后将总线置高电平,等待15~60us后,存在的从机会将总线置低电平60~240us以响应主机,之后从机将总线置高电平。
- 发送一位:主机将总线置低电平60~120us,然后将总线置高电平,表示发送0;主机将总线置低电平1~15us,然后将总线置高电平,表示发送1。从机将在总线置低电平30us后(典型值)读取电平,整个过程所需时间应大于60us。
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
OneWire_DQ=0;
i = 4;while (--i); //Delay 10us
OneWire_DQ=Bit;
i = 24;while (--i); //Delay 50us
OneWire_DQ=1;
}
- 接收一位:主机将总线置低电平1~15us,然后将总线置高电平,并在置低电平后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us。
- 发送一个字节(低位在前):连续调用8次发送一位的时序,依次发送一个字节的8位.
- 接收一个字节(低位在前):连续调用8次接收一位的时序,依次接收一个字节的8位
DS18B20的指令
ROM指令 | 作用 | 功能指令 | 作用 |
SEARCH ROM [F0H] | 搜索ROM | CONVERT T [44H] | 转换温度 |
READ ROM [33H] | 读取ROM | WRITE SCRATCHPAD [4EH] | 写暂存器 |
MATCH ROM [55H] | 匹配ROM | READ SCRATCHPAD [BEH] | 读暂存器 |
SKIP ROM [CCH] | 跳过ROM | COPY SCRATCHPAD [48H] | 复制暂存器 |
ALARM SEARCH [ECH] | 警报搜索 | RECALL E2 [B8H] | 召回 EEPROM |
READ POWER SUPPLY [B4H] | 读电源模式 |
a、64-BIT ROM:作为器件地址,用于总线通信的寻址。
b、SCRATCHPAD(暂存器):用于总线的数据交互。
b、EEPROM:用于保存温度触发阈值和配置参数。
DS18B20时序结构:
a、温度变换:初始化→跳过ROM →开始温度变换
S | S:SKIP ROM [CCH] | S:CONVERT T [44H] |
b、温度读取:初始化→跳过ROM →读暂存器→连续的读操作
S | S:SKIP ROM [CCH] | S:READ SCRATCHPAD [BEH] |
+
R:Temperature LSB | R:Temperature MSB |
- 温度存储:
BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 |
2^3 | 2^2 | 2^1 | 2^0 | 2^(-1) | 2^(-2) | 2^(-3) | 2^(-4) |
BIT15 | BIT14 | BIT13 | BIT12 | BIT11 | BIT10 | BIT9 | BIT8 |
S | S | S | S | S | 2^6 | 2^5 | 2^4 |
其中S表示signed,即符号位。LSB:BIT0~BIT7;MSB:BIT8~BIT15。
写程序时用int型(16个字节)接收LSB和MSB的数据,如
Int T=0;
T = (MSB<<8)|LSB;
十四、LCD1602液晶显示屏
LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块。LCD1602可以显示ASCII码的标准字符和其它的一些内置特殊字符,还有8个可以自定义的字符,其总共可以显示16×2个字符,每个字符是5*7个像素点。
LCD1602的引脚:
引脚 | 功能 |
GND | 电源负极 |
VCC | 电源正极 |
VO | 对比度调节 |
RS | 数据/指令选择,1为数据,0为指令 |
RW | 读/写选择,1为读,0为写 |
EN | 使能,1为数据有效,下降沿执行命令 |
DB0~DB7 | 数据输入/输出 |
BG VCC | 背光灯电源正极 |
BG GND | 背光灯电源负极 |
DDRAM(Data Display RAM) 就是显示数据 RAM,用来寄存待显示的字符代码。共
80 个字节,其地址和屏幕的对应关系如下表:
显示位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... | 40 | |
DDRAM地址 | 第一行 | 00H | 01H | 02H | 03H | 04H | 05H | 06H | ... | 40H |
第二行 | 41H | 42H | 43H | 44H | 45H | 46H | 47H | ... | 80H |
可以在屏幕上显示的只有每行的前16个地址,多的地址可通过屏幕整体左移进行显示。
LCD1602自带显示模块指令,使用这些指令时需通过DB0~DB7进行写入。常见的初始化指令:
a、发送指令0x38 //八位数据接口,两行显示,5*7点阵
b、发送指令0x0C //显示开,光标关,闪烁关
c、发送指令0x06 //数据读写操作后,光标自动加一,画面不动
d、发送指令0x01 //清屏
发送指令和数据时,EN始终置高电平,由于LCD1602读取数据需要时间,所以需要调用延时函数,延时1ms即可。写入指令时不需要使用屏幕的字节,所以不用先进行DDRAM地址的写入,且指令的值都不超过0x40,不会影响DDRAM地址的写入。传输数据时需要先写入DDRAM地址,其中00H的地址编号是0x80,40H的地址编号是0x80+0x40,然后就可以写入数据。由于LCD1602的屏幕显示各位都是相互独立的,无法识别字符串和十位以上的数字,其中字符串可以通过数组进行拆分,字符串的最后一位是’/0’,所以可通过if()语句进行判断输入。十位以上的数字可以通过整除10和对10取余进行拆分,使用二进制、十六进制的数据表示输入的十进制数据可通过它们之间的换算法则进行。
十五、PWM驱动
PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量。
PWM重要参数:
a、频率 = 1 / TS
b、占空比 = TON(导通时间) / TS(总时间=导通时间+断开时间)
c、精度 = 占空比变化步距
PWM的具体应用是设置临界值,设置计数器定时自增,计数器得到的值和临界值进行比较。可以设置大于临界值时,端口置低电平;小于临界值时,端口置高电平,反之亦可。处于高电平和低电平时都需要调用延时函数进行一定时间的延时,使用定时器进行延时和定时器中断检测也能实现PWM驱动。
PWM驱动的具体应用有呼吸灯和电机调速。如
//LED呼吸灯
unsigned char Time,i;
i = 20;
while(1)
{
for(Time=0;Time<100;Time++) //改变亮灭时间,由暗到亮
{
while(i--) //计次延时
{
LED=0; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
i = 20;
}
for(Time=100;Time>0;Time--) //改变亮灭时间,由亮到暗
{
while(i--) //计次延时
{
LED=0; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
i = 20;
}
}
十六、AD/DA
AD(Analog to Digital):模拟-数字转换,将模拟信号转换为计算机可操作的数字信号。DA(Digital to Analog):数字-模拟转换,将计算机输出的数字信号转换为模拟信号。涉及的电路知识较多,这里难以详述。
ADC引脚:
引脚 | 功能 |
CS | 片选信号。控制转换时序和使能,串行通信寄存器,低电平时,ADC有效 |
DCLK | 外部时钟信号输入 |
DIN | 串行数据输入端。当C S为低电平时,数据在DCLK上升沿锁存进来 |
DOUT | 串行数据输出端。数据在DCLK的下降沿移出,CS低电平时有效 |
通过ADC可读取的数字信号,即:
#define XPT2046_VBAT 0xAC //光敏电阻
#define XPT2046_AUX 0xEC //辅助信号
#define XPT2046_XP 0x9C //可调电阻
#define XPT2046_YP 0xDC //热敏电阻
XPT2046时序结构
CS置低电平后,使用if()语句进行串口输入数据,输入的数据是可读取的数字信号,即输入指令。DIN进行输入所需的时间只有nm级,所以DCLK置高电平之后可以立马置低电平。输入完毕后,下一个DCLK的下降沿就是读取数字信号数据,DOUT接收的数据只有12位有效,其余的数据位会自动补0。
DAC(PWM)的应用与PWM驱动方式非常相似,不过DAC输出的是连续波形,PWM驱动输出的是方波。
十七、红外遥控
红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出,是单工、异步的通信方式,红外LED波长是940nm。
红外接收模块处于空闲状态时,红外LED不亮,接收头输出高电平。发送低电平时,红外LED以38KHz频率闪烁发光,接收头输出低电平。发送高电平时,红外LED不亮,接收头输出高电平。
红外接收模块引脚:
引脚 | 功能 |
VCC | 电源正极 |
IO | 外部中断0引脚 |
GND | 电源负极 |
关于红外NEC编码说明:
红外接收模块会与单片机以:地址码+地址反码+命令码+命令反码的数据格式(共32位,其中低位在前,高位在后)进行通信,通信开始的信号是:低电平状态9ms+高电平状态4.5ms;数据位“0”是低电平状态和高电平状态都为560us;数据位“1”是低电平状态560us+高电平状态1690us;重复信号是低电平状态9ms+高电平状态2.25ms。由于数据信号的脉冲都是通过IO口(即P3.2)向单片机核心发送的,P3.2口又是外部中断0的引脚,外部中断有两种触发方式分别是低电平触发和下降沿触发,IT0/TCON.0 = 1;是下降沿触发;IT0/TCON.0 = 0;是低电平触发。红外接收模块发出的信号都是由高电平状态变化为低电平状态作为信号的开端,所以两个触发方式都可以。
//外部中断0初始化
void Int0_Init(void)
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
由于计时的数量级比较小,用定时器进行计时需要获取每个信号的时间长度及定时器清零和初始值重装,而且判断每个信号的时间长度应该要有上下浮动,获取更准确的数据信号。这个程序用到的if()语句和&&判断较多,并且还需要进行数据的验证,即地址码=~地址反码,命令码=~命令反码。
补充(定时器0中断扫描按键):
按键按下和松开时会出现抖动,抖动的时间一般在5ms~10ms,所以定时器0可以每计时20ms就进入中断程序。按键没有被按下时IO口处于高电平状态,按下时处于低电平状态,用if()语句进行判断,如前一次进入中断程序扫描到的是高电平状态,现在进入中断程序扫描到的是低电平状态,则相应按键被按下,从而获得键码。
static unsigned char NowState,LastState;
LastState=NowState; //按键状态更新
NowState=Key_GetState(); //获取当前按键状态
if(LastState==1 && NowState==0)
{
Key_KeyNumber=1;
}