开发板介绍
这个板子是KST-51的开发板,芯片是STC89C52RC
新建工程
对于单片机程序来说,每个功能程序,都必须要有一个配套的工程(Project),先在文件夹创建一个工程,如图所示:
接着打开 Keil 软件,点击:Project–>New uVision Project…然后会出现一个新建工程的界面,如图所示:
给这个工程起一个名字叫做 temple,如图所示:
保存之后会弹出一个对话框,这个对话框让我们选择单片机型号。因为 Keil 软件是外国人开发的,所以我们国内的 STC89C52 并没有上榜,但是只要选择同类型号就可以了。这里我们直接选择 Intel 公司名下的 80/87C52 来代替,如图所示:
点击 OK 之后,会弹出一个对话框,如图所示,每个工程都需要一段启动代码,如果点“否”编译器会自动处理这个问题,如果点“是”,这部分代码会提供给我们用户,我们就可以按需要自己去处理这部分代码,那这部分代码在我们初学 51 的这段时间内,一般是不需要去修改的,但是随着技术的提高和知识的扩展,我们就有可能会需要了解这块内容,所以这里先点“否”
这样工程就建立好了,我们现在工程文件下先新建两个文件,一个是USER,存放main.c文件,一个是APP,存放一些外设文件,因为一个工程里面肯定会包括很多外设,这样子方便管理,如图所示:
工程有了之后,我们要建立编写代码的文件,点击 File–>New,新建一个文件,也就是我们编写程序的平台。然后点 File–>Save,可以保存文件,也可以用键盘上的快捷键Ctrl + N,新建文件,Ctrl + S保存文件,如图所示:
文件夹保存在我们刚刚新建的USER目录下,命名为main.c,如图所示:
我们每做一个功能程序,必须要新建一个工程,一个工程代表了单片机要实现的一个功能。但是一个工程,有时候可以把我们的程序分多个文件写,所以每写一个文件,我们都要添加到我们所建立的工程中去
1、点击三色正方形
2、在Groups那里同样建立USER和APP文件夹
3、接着在USER中添加我们刚才建立的main.c文件
接着我们还要来配置一些选项
点击这个魔法棒,接着点击Output,把这个HEX文件勾选上,这样子编译才可以找到这个HEX文件,如图所示:
这样子这个工程就配置好啦
如果之后还要添加一些外设也是这个步骤,外设建立一个.c文件和一个.h文件(头文件),不过外设文件可以放在我们自己建立的APP目录下,例如要新建LED外设,先在APP目录下新建LED文件,接着建立LED.c和LED.h文件,然后把LED.c文件添加到这个LED文件中
要保证编译时可以找到我们建立的.h文件,我们要把头文件的路径给包含进来,这样子才可以识别到.h文件
点击这个魔法棒,接着点击C51,在Include Paths这里点击那三个点,把头文件路径包含进来,如图所示:
头文件里面有固定的模板,跟着敲就行,后面的内容就是我们建立头文件的名字,不过要用大写,这个主要是防止重复定义
1 2 3 4 5 6 #ifndef __LED_H #define __LED_H #endif
单片机的内部资源
这里的单片机内部资源,是指作为单片机用户,单片机提供给我们可使用的东西。总结起来,主要是三大资源:
Flash——程序存储空间,早期单片机是 OTPROM
RAM——数据存储空间
SFR——特殊功能寄存器
这个STC89C52RC 的资源情况:Flash 程序空间是 8K 字节(1K=1024,1 字节= 8 位),RAM 数据空间是 512 字节,SFR 我们后边会逐一提到并且应用。
单片机最小系统
单片机最小系统,也叫做单片机最小应用系统,是指用最少的原件组成单片机可以工作的系统。单片机最小系统的三要素就是电源、晶振、复位电路,如图所示:
电源
我们所选用的 STC89C52,它需要 5V 的供电系统,我们的开发板是使用 USB 口输出的 5V 直流直接供电的。供电电路在 40 脚 和 20 脚的位置上,40 脚接的是 +5V,通常也称为 VCC 或 VDD,代表的是电源正极,20 脚接的是 GND,代表的是电源的负极。
晶振
晶振,又叫晶体振荡器,作用是为单片机系统提供基准时钟信号,单片机内部所有的工作都是以这个时钟信号为步调基准来进行工作的。STC89C52 单片机的 18 脚 和 19 脚是晶振引脚,我们接了一个 11.0592M 的晶振(它每秒钟振荡 11,059,200 次),外加 两个 20pF 的电容,电容的作用是帮助晶振起振,并维持振荡信号的稳定。
复位电路
复位电路,接到了单片机的 9 脚 RST(Reset)复位引脚上,单片机复位一般是 3 种情况:上电复位、手动复位、程序自动复位
基础知识点
三极管
三极管的初步认识
三极管是一种很常用的控制和驱动器件,主要用来 控制电流的大小,箭头方向表示 电流的流向,同时表示了三极管的极性,常用的三极管根据材料分有硅管和锗管两种, 三极管有 2 种类型,分别是 PNP 型和 NPN 型, 箭头朝外的表示为NPN型、箭头方向朝里的表示为PNP型
三极管一共有 3 个极,横向左侧的引脚叫做基极b,中间有一个箭头, 一头连接基极,另外一头连接的是发射极 e,那剩下的一个引脚就是集电极 c
三极管的原理
三极管有截止、放大、饱和三种工作状态。放大状态主要应用于模拟电路中,而数字电路主要使用的是三极管的开关特性,只用到了截止与饱和两种状态。
三极管饱和-----实现电子开关的“开”功能
三极管截止-----实现电子开关的“关”功能
口诀:导通电压顺箭头过,电压导通,电流控制
三极管用法特点:
关键点在于 b 极(基极)和 e 极(发射极)之间的电压情况, 对于PNP 而言,e 极电压只要高于 b 极 0.7V 以上,这个三极管 e 极和 c 极之间就可以顺利导通。也就是说,控制端在 b 和 e 之间,被控制端是 e 和 c 之间。 同理,NPN 型三极管的导通电压是 b 极比 e 极高 0.7V,总之是箭头的始端比末端高 0.7V 就可以导通三极管的 e 极和 c 极
b作为控制端,NPN型三极管,高电平导通,低电平关断;PNP型三极管,高电平关断,低电平导通
NPN和PNP三极管的接法有些不同, NPN型三极管当下管使用,控制灯泡的负极;PNP型三极管当上管使用,控制灯泡的正极。
逻辑运算
逻辑运算符
以下逻辑运算符都是按照变量整体值进行运算的,通常就叫做逻辑运算符
&& 逻辑与
两个为真才为真,一个为假就是假
|| 逻辑或
一个为真就是真,两个为假才是假
! 逻辑非
F = ! A,当 A 值为假时,其结果 F 为真;当 A 值为真时,结果 F 为假
位运算符
以下逻辑运算符都是按照变量内的每一个位来进行运算的,通常就叫做位运算符
& 按位与
F = A & B,将 A、B 两个字节中的每一位都进行与运算
| 按位或
将 A、B 两个字节中的每一位都进行或运算
~ 按位取反
F = ~A,将 A 字节内的每一位进行非运算(就是取反)
^ 按位异或
如果运算双方的值不同(即相异)则结果为真,双方值相同则结果为假(相同为0,不同为1)
F = A ^ B,A = 0b11001100,B = 0b11110000,F = 0b00111100
逻辑电路符号
不同数据类型间的相互转换
当不同数据类型之间混合运算的时候,不同类型的数据首先会转换为同一类型,转换的主要原则是:短字节的数据向长字节数据转换
比如:unsigned char a; unsigned int b; unsigned int c; c = a *b; 在运算的过程中,程序会自动全部按照 unsigned int 型来计算,c 的数据类型是 unsigned int 型,取值范围是 0~65535,而 70000 超过 65535 了,其结果会溢出,最终 c 的结果是 (70000 - 65536) = 4464
不同类型变量之间的相互赋值,短字节类型变量向长字节类型变量赋值时,其值保持不变。比如 unsigned char a=100; unsigned int b=700; b=a;那么最终 b 的值就是 100 了。但是如果我们的程序是 unsigned char a=100; unsigned int b=700; a=b;那么 a 的值仅仅是取了 b 的低 8 位,我们首先要把 700 变成一个 16 位的二进制数据,然后取它的低 8 位出来,也就 是 188,这就是长字节类型给短字节类型赋值的结果,会从长字节类型的低位开始截取刚好等于短字节类型长度的位,然后赋给短字节类型
C 语言不同类型运算的时候数值会转换同一类型运算,但是每一步运算都会进行识别判断,不会进行一个总的分析判断,避免这类问题 的产生可以采用强制类型转换的方法
在一个变量前边加上一个数据类型名,并且这个类型名用小括号括起来,就表示把这个变量强制转换成括号里的类型。如 c = (unsigned long)a * b ;由于强制类型转换运算符优先级高于*,所以这个地方的运算是先把 a 转换成一个 unsigned long 型的变量,而后与 b 相乘, 根据 C 语言的规则 b 会自动转换成一个 unsigned long 型的变量,而后运算完毕结果也是一个 unsigned long 型的,最终赋值给了 c
在 51 单片机里边,有一种特殊情况,就是 bit 类型的变量,这个 bit 类型的强制类型转换,是不符合上边讲的这个原则的,比如 bit a=0; unsigned char b; a=(bit)b;这个地方要特别注意,使用 bit 做强制类型转换,不是取 b 的最低位,而是它会判断 b 这个变量是 0 还是非 0 的值,如果 b 是 0,那么 a 的结果就是 0,如果 b 是任意非 0 的其它值,那么 a 的结果都是 1
51 单片机 RAM 区域的划分
51 单片机的 RAM 分为两个部分,一块是片内 RAM,一块是片外 RAM,片内 RAM 和片外 RAM 的地址不是连起来的, 片内是从 0x00 开始,片外是从 0x0000 开始的
以下是几个 Keil C51 语言中的关键字,代表了 RAM 不同区域的划分
data:片内 RAM 从 0x00~0x7F
idata:片内 RAM 从 0x00~0xFF
pdata:片外 RAM 从 0x00~0xFF
xdata:片外 RAM 从 0x0000~0xFFFF
data 是 idata 的一部分,pdata 是 xdata 的一部分,在 Keil 默认设置下,data 是可以省略的,即什么都不加的时候变量就是定义到 data 区域中的。data 区域 RAM 的访问在汇编语言中用的是直接寻址,执行速度是最快的。如果定义成 idata,不仅仅可以访问 data 区域,还可以访问 0x80H~0xFF 的范围,但加了 idata 关键字后,访问的时候 51 单片机用的是通用寄存器间接寻址,速度较 data 会慢一些,而且我们平时大多数情况下不太希望访问到 0x80H~0xFF,因为这块通常用于中断与函数调用的堆栈,所以在绝大多数情况下,我们使用内部 RAM 的时候,只用 data 就可以了
指针
变量 a 的地址就是 0x00,它的地址的表达方式可以写成:&a,这样就代表了相应变量的地址,C 语言中变量前加一个&表示取这个变量的地址,&在这里就叫做“取址符”
在 C 语言中,我们要访问一个变量,有两种方式:一种是通过变量名来访问, 另一种就是通过变量的地址来访问。在 C 语言中,地址就等同于指针,变量的地址就是变量的指针。保存指针的变量,称之为指针变量,简称为指针,而通常我们说的指针就是指针变量
指针变量的声明
在 C 语言中,变量的地址往往都是编译系统自动分配的,我们是不知道某个变量的具体地址的。所以我们定义一个指针变量 p,把普通变量 a 的地址直接送给指针变量 p
指针变量 p 的定义和初始化,有两种方式:
方法 1:定义时直接进行初始化赋值
1 2 unsigned char a;unsigned char *p = &a;
方法 2:定义后再进行赋值
1 2 3 4 unsigned char a; unsigned char *p; p = &a;
定义指针变量 *p 和取值运算 *p 的区别
1 2 3 4 5 6 7 unsigned char *p; unsigned char a = 1 ;unsigned char b = 2 ;p = &a; b = *p;
指向数组元素的指针
指向数组元素的指针,其本质还是变量的指针。因为数组中的每个元素,其实都可以直接看成是一个变量,所以指向数组元素的指针,也就是变量的指针
1 2 3 4 5 6 7 8 9 10 unsigned char number[10 ] = {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };unsigned char *p;p = &number[0 ]; p = &number[1 ]; p = p + 1 ; p = &number[0 ]; q = &number[9 ];
数组元素指针还有一种情况,就是数组名字其实就代表了数组元素的首地址
1 2 3 4 5 6 7 p = &number[0 ]; p = number;
二维数组元素的指针和一维数组类似
1 2 3 4 5 6 7 8 unsigned char *p;unsigned char number[3 ][4 ];p = &number[0 ][0 ]; number[0 ] 等价于 &number[0 ][0 ] number[1 ] 等价于 &number[1 ][0 ]
在 C 语言里边,sizeof() 可以用来获取括号内的对象所占用的内存字节数,sizeof() 括号中可以是变量名,也可以是变量类型名,其结果是等效的
复合数据类型
结构体数据类型
结构体本身不是一个基本的数据类型,而是构造的,它每个成员可以是一个基本的数据类型或者是一个构造类型。结构体既然是一种构造而成的数据类型,那么在使用之前必须先定义它
声明结构体变量的一般格式如下:
1 2 3 4 5 6 7 8 struct 结构体名{ 类型 1 变量名 1 ; 类型 2 变量名 2 ; …… 类型 n 变量名 n; }; struct 结构体名 结构体变量名 1, 结构体变量名 2, ... 结构体变量名 n ;
我们来构造一个实际的表示日期时间的结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct sTime { unsigned int year; unsigned char mon; unsigned char day; unsigned char hour; unsigned char min; unsigned char sec; unsigned char week; }; struct sTime bufTime ;bufTime.year = 0x2023 ; bufTime.mon = 0x10 ;
一个指针变量如果指向了一个结构体变量的时候,称之为结构指针变量。结构指针变量是指向的结构体变量的首地址,通过结构体指针也可以访问到这个结构变量
结构指针变量声明的一般形式如下:
1 2 3 4 5 6 7 8 struct sTime *pbufTime ;pbufTime->year = 0x2013 ; (*pbufTime).year = 0x2013 ;
共用体数据类型
共用体也称之为联合体,共用体定义和结构体十分类似
1 2 3 4 5 6 7 8 union 共用体名{ 数据类型 1 成员名 1 ; 数据类型 2 成员名 2 ; …… 数据类型 n 成员名 n; }; union 共用体名 共用体变量;
共用体表示的是几个变量共用一个内存位置,也就是成员 1、成员 2……成员 n 都用一个内存位置。共用体成员的访问方式和结构体是一样的,成员访问的方式是:共用体名.成员名,使用指针来访问的方式是:共用体名->成员名
共用体和结构体的主要区别如下:
结构体和共用体都是由多个不同的数据类型成员组成,但在任何一个时刻,共用体只能存放一个被选中的成员,而结构体所有的成员都存在。
对于共同体的不同成员的赋值,将会改变其它成员的值,而对于结构体不同成员的赋值是相互之间不影响的
枚举数据类型
在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期从周一到周日有 7 天,一年从 1 月到 12 月有 12 个月,蜂鸣器有响和不响两种状态等等。如果把这些变量定义成整型或者字符型不是很合适,因为这些变量都有自己的范围。C 语言提供了一种 称为“枚举”的类型,在枚举类型的定义中列举出所有可能的值,并可以为每一个值取一个 形象化的名字,它的这一特性可以提高程序代码的可读性
枚举的说明形式如下:
1 2 3 4 5 6 7 8 enum 枚举名{ 标识符 1 [ = 整型常数], 标识符 2 [ = 整型常数], …… 标识符 n[ = 整型常数] }; enum 枚举名 枚举变量;
枚举的说明形式中,如果没有被初始化,那么“=整型常数”是可以被省略的,如果是默认值的话,从第一个标识符顺序赋值 0、1、2……,但是当枚举中任何一个成员被赋值后, 它后边的成员按照依次加 1 的规则确定数值
枚举的使用,有几点要注意:
枚举中每个成员结束符是逗号,而不是分号,最后一个成员可以省略逗号
枚举成员的初始化值可以是负数,但是后边的成员依然依次加 1
枚举变量只能取枚举结构中的某个标识符常量,不可以在范围之外
转义字符
字符串常量在内存中按顺序逐个存储字符串中的字符的 ASCII 码值,并且特别注意,最后还有一个字符 \0,‘\0’ 字符的 ASCII 码值是 0, 它是字符串结束标志,在写字符串的时候,这个‘\0’是隐藏的,但是实际是存在的
LED模块
点亮LED用到的原理图
三极管基极通过一个 1K 的电阻接到了单片机的 74HC138芯片上的 LEDS6端口上, 发射极直接接到 5V 的电源上,集电极上分别连了8个 LED 小灯,8个LED小灯分别连了 330Ω电阻。如果 LEDS6 由我们的程序给一个 高电平 1,那么 基极 b 和发射极 e 都是 5V,也就是说 e到 b 不会产生一个 0.7V 的压降,这个时候,发射极和集电极也就不会导通,LED 小灯也就不会亮。如果程序给 LEDS6 一个低电平 0,这时 e 极还是 5V,于是 e 和 b 之间产生了压差,三极管 e 和 b 之间也就导通了,三极管 e 和 b 之间大概有 0.7V 的压降,那还有(5-0.7)V 的电压会在电阻 R47 上;这时候根据二极管特性只要负极加个 低电平0 就可以点亮LED 小灯了。
74HC245芯片
原理图
74HC245 是个 双向缓冲器(DIR 是方向引脚,当它高电平时:右侧B编号电压等于左侧A编号对应电压;当 DIR低电平:左侧A编号电压等于右侧B编号对应电压);作用是电流驱动缓冲,不起到任何逻辑控制的效果,稳定工作在 70mA 电流是没有问题的,比单片机的 8 个 IO 口大多了,所以我们可以把他接在 小灯和 IO 口之间做缓冲,这个地方 控制端是左侧接的是 P0 口,我 们要求 B 等于 A 的状态,所以 1 脚我们直接接的 5V 电源,即高电平
问:已经在电源 VCC 那地方加了一个三极管驱动了, 为何还要再加 245 驱动芯片呢?
从电源正极到负极的电流水管的粗细都要满足要求,任何一个位置的管子过细,都会出现瓶颈效应,电流在整个通路中细管处会受到限制而降低,所以在电路通路的每个位置上,都要保证通道足够畅通,这个 74HC245 的作用就是消除单片机 IO 这一环节的瓶颈
74HC138芯片
三八译码器,就是把 3 种输入状态翻译成 8 种输出状态
想让这个 74HC138 正常工作,ENLED 那个输入位置必须输入低电平,ADDR3 位置必须输入高电平
原理图
头上有一横表示在低电平时有效
真值:
L:表示低电平0
H:表示高电平1
X:表示无论是高电平还是低电平都不影响真值
从上面的管脚图及真值表可以知道该芯片使用方法很简单,给 G1(E3) 使能管脚高电平,G2(ENLED) 管脚为低电平,至于哪个管脚输出有效电平(低电平),要看 C B A 输入管脚的电平状态。如果 C B A 都为低电平,则 Y0 输出有效电平(低电平),其他管脚均输出高电平。
方法:C B A 输入就相当于 3 位 2 进制数,A 是低位,B 是次高位,C 是高位。而 Y0-Y7 具体哪一个输出有效电平,就看输入二进制对应的十进制数值。比如输入是 110(C,B,A),其对应的十进制数是 6,所以Y6输出有效电平(低电平)。
C对应A2,B对应A1,A对应A0,则A2为高位,A0为低位
前面我们知道需要给 LEDS6 一个 低电平 0才能点亮LED,所以需要设置 Y6,则需要看真值表
由真值表可知,想让 Y6为低电平,则需要设置:
ADDR3(E3):1
ADDR2(A2):1
ADDR1(A1):1
ADDR0(A0):0
ENLED(E1+E2):0
LED小灯整体电路图
我们来看下点亮 LED 小灯的过程,首先看 74HC138,我们要让 LEDS6 为低电平才能导通三极管 Q16,所以 ENLED = 0,ADDR3 = 1;保证 74HC138 使能。然后 ADDR2 = 1; ADDR1 = 1; ADDR0 = 0;这样保证了三极管 Q16 这个开关开通,5V 电源加到 LED 上。 而 74HC245 左侧是通过 P0 口控制,我们让 P0.0 引脚等于 0,就是 DB_0 等于 0,而右侧 DB0 等于 DB_0 的状态,也是 0,那么这样在这一排共 8 个 LED 小灯当中,只有最右侧的小灯和 5V 之间有压差,有压差就会有电流通过,有电流通过我们的 LED2 就会发光了。 74HC245 左侧我们可以看出来,是直接接到 P0 口上的,而 74HC138 的 ADDR0 ~ ADDR3 接在何处呢?来看下面的图
从图中可以看出,把跳线帽右侧和中间的针连到了一 起,这样实现的就是图中的 P1.0 和 ADDR0 连接到一起、P1.1 和 ADDR1 接一起、P1.2 和 ADDR2 接一起、P1.3 和 ADDR3 接一起,这样子就可以很清晰地知道为什么小灯点亮了
LED程序
点亮一个LED
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include "reg52.h" sbit LED2 = P0 ^ 0 ; sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; LED2 = 0 ; while (1 ); }
LED灯闪烁
这里我另外新建了Delay文件,里面存放延时函数,方便管理,要用的时候直接调用头文件就好
Dealy.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <reg52.h> #include "Delay.h" void Delay_ms (unsigned int xms) { unsigned char data i, j; while (xms--) { i = 11 ; j = 190 ; do { while (--j); } while (--i); } }
Dealy.h
1 2 3 4 5 6 7 8 #ifndef _DELAY_ #define _DELAY_ #include <reg52.h> void Delay_ms (unsigned int xms) ; #endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "reg52.h" #include "Delay.h" sbit LED2 = P0 ^ 0 ; sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; while (1 ) { LED2 = 0 ; Delay_ms(20 ); LED2 = 1 ; Delay_ms(20 ); } }
LED左右流水
Dealy.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <reg52.h> #include "Delay.h" void Delay_ms (unsigned int xms) { unsigned char data i, j; while (xms--) { i = 11 ; j = 190 ; do { while (--j); } while (--i); } }
Dealy.h
1 2 3 4 5 6 7 8 #ifndef _DELAY_ #define _DELAY_ #include <reg52.h> void Delay_ms (unsigned int xms) ; #endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "reg52.h" #include "Delay.h" #define LED_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { u8 i = 0 ; u8 dir = 0 ; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; while (1 ) { for (i = 0 ; i < 8 ; i++) { if (dir == 0 ) { LED_PORT = ~(0x01 << i); Delay_ms(20 ); if (LED_PORT == 0x7f ) dir = 1 ; } if (dir == 1 ) { for (i = 0 ; i < 8 ; i++) { LED_PORT = ~(0x80 >> i); Delay_ms(20 ); if (LED_PORT == 0xfe ) dir = 0 ; } } } } }
定时器
定时器的初步认识
①振荡周期:石英晶体振荡器的工作频率的倒数,为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期)。
②时钟周期:2 个振荡周期为 1 个时钟周期
③机器周期:单片机完成一个操作的最短时间。1 个机器周期含 6 个时钟周期,12 个振荡周期。
④指令周期:完成 1 条指令所占用的全部时间,它以机器周期为单位。
例如:外接晶振为 12MHz 时,51 单片机相关周期的具体值为:
振荡周期=1/12us;
时钟周期=1/6us;
机器周期=1us;
指令周期=1~4us
定时器介绍
①51 单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时器/计数器。
②定时器/计数器和单片机的 CPU 是相互独立的。定时器/计数器工作的过程是自动完成的,不需要 CPU 的参与。
③51 单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加 1。
有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加 1 的工作可以交给定时器/计数器处理。CPU 转而处理一些复杂的事情。同时可以实现精确定时作用。
定时器原理
STC89C5X 单片机内有两个可编程的定时/计数器 T0、T1 和一个特殊功能定时器 T2。定时/计数器的实质是加 1 计数器(16 位),由高 8 位和低 8 位两个寄存器 THx 和 TLx 组成。它随着计数器的输入脉冲进行自加 1,也就是每来一个脉冲,计数器就自动加 1,当加到计数器为全 1 时,再输入一个脉冲就使计数器回零,且计数器的溢出使相应的中断标志位置 1,向 CPU 发出中断请求(定时 /计数器中断允许时)。如果定时/计数器工作于定时模式,则表示定时时间已到; 如果工作于计数模式,则表示计数值已满。可见,由溢出时计数器的值减去计数初值才是加 1 计数器的计数值。
定时器就是用来进行定时的,定时器内部有一个寄存器,我们让它开始计数后,这个寄存器的值每经过一个机器周期就会自动加 1,因此,我们可以把机器周期理解为定时器的计数周期。就像我们的钟表,每经过一秒,数字自动加 1,而这个定时器就是每过一个机器周期的时间,也就是 12/11059200 秒,数字自动加 1。还有一个特别注意的地方, 就是钟表是加到 60 后,秒就自动变成 0 了,这种情况在单片机或计算机里我们称之为溢出。 假如是 16 位的定时器,也就是 2 个字节,最大值就是 65535, 那么加到 65535 后,再加 1 就算溢出,对于 51 单片机来说,溢出后,这个值会直接变成 0。从某一个初始值开始,经过确定的时间后溢出,这个过程就是定时的含义
51 单片机定时器/计数器内部结构图:
定时器的寄存器
标准的 51 单片机内部有 T0 和 T1 这两个定时器,对于单片机 的每一个功能模块,都是由它的 SFR,也就是特殊功能寄存器来控制。与定时器有关的特殊功能寄存器,有以下几个,以下表的寄存器是存储定时器的计数值的。TH0/TL0 用于 T0,TH1/TL1 用于 T1
计数器的工作由两个特殊功能寄存器控制
TCON 是控制寄存器,控制 T0、T1 的启动和停止及设置溢出标志
TMOD 是定时/计数器的工作方式寄存器,确定工作方式和功能
TCON 是“可位寻址”,TMOD 是“不可位寻址”;可位寻址就是可以直接操作其中一个位,不可位寻址只能操作整个字节
控制寄存器 TCON
TCON 的低 4 位用于控制外部中断。TCON 的高 4 位用于控制定时/计数器的启动和中断申请。
其格式如下:
TF1(TCON.7):T1(定时器1) 溢出中断请求标志位。T1 计数溢出时由硬件自动置 TF1 为 1。CPU 响应中断后 TF1 由硬件自动清 0。T1 工作时,CPU 可随时查询 TF1 的状态。所以,TF1 可用作查询测试的标志。TF1 也可以用软件置 1 或清 0,同硬件置 1 或清 0 的效果一样。
TR1(TCON.6):T1(定时器1) 运行控制位。TR1 置 1 时,T1 开始工作;TR1 置 0 时, T1 停止工作。TR1 由软件置 1 或清 0。所以,软件置位/清零来进行启动/停止定时器。
TF0(TCON.5):T0 溢出中断请求标志位,其功能与 TF1 类同。
TR0(TCON.4):T0 运行控制位,其功能与 TR1 类同。
当我们程序中写 TR1 = 1 以后,定时器值就会每经过一个机器周期自动加 1,当我们程序中写 TR1 = 0 以后,定时器就会停止加 1,其值会保持不变化。TF1是一个标志位,他的作用是告诉我们定时器溢出了。比如我们的定时器设置成 16 位的模式,那么每经过一个机器周期,TL1 加 1 一次,当 TL1 加到 255 后,再加 1,TL1 变成 0,TH1 会加 1 一次,如此一直加到 TH1 和 TL1 都是 255(即 TH1 和 TL1 组成的 16 位整型数为 65535)以后,再加 1 一次,就会溢出了,TH1 和 TL1 同时都变为 0,只要一溢出,TF1 马上自动变成 1,告诉我们定时器溢出了,仅仅是提供给我们一个信号,让我们知道定时器溢出了,它不会对定时器是否继续运行产生任何影响
工作方式寄存器 TMOD
工作方式寄存器 TMOD 用于设置定时/计数器的工作方式,低四位用于 T0,高四位用于 T1。
其格式如下:
GATE 是门控位, GATE=0 时,用于控制定时器的启动是否受外部中断源信号的影响。只要用软件使 TCON 中的 TR0 或 TR1 为 1,就可以启动定时/计数器工作;
GATE=1 时,要用软件使 TR0 或 TR1 为 1,同时外部中断引脚 INT0/1 也为高电平时,才能启动定时/计数器工作。即此时定时器的启动条件,加上了 INT0/1 引脚为高电平这一条件。
C/T :定时/计数模式选择位。C/T =0 为定时模式;C/T =1 为计数模式。
M1M0:工作方式设置位。定时/计数器有四种工作方式。
定时/计数器的工作方式
(1)方式 0
方式 0 为 13 位计数,由 TL0 的低 5 位(高 3 位未用)和 TH0 的 8 位组成。 TL0 的低 5 位溢出时向 TH0 进位,TH0 溢出时,置位 TCON 中的 TF0 标志
门控位 GATE 具有特殊的作用。当 GATE=0 时,经反相后使或门输出为 1,此时仅由 TR0 控制与门的开启,与门输出 1 时,控制开关接通,计数开始;
当GATE=1 时,由外中断引脚信号控制或门的输出,此时控制与门的开启由外部中断引脚信号和 TR0 共同控制。当 TR0=1 时,外部中断引脚信号引脚的高电平启动计数,外部中断引脚信号引脚的低电平停止计数。这种方式常用来测量外中断引脚上正脉冲的宽度。计数模式时,计数脉冲是 T0 引脚上的外部脉冲。
当 GATE = 0 时,经过 "非门"变成 1,然后再到 “或门”(只要一个为真,都为真),这时候不管 INTO 引脚是否为真,然后来到 “与门”,这时候取决于 TR0,当 TR0 为1(真) 开关才会闭合工作。计数初值与计数个数的关系为:
X = 2 13 − N X=2^{13}-N
X = 2 13 − N
(2)方式 1
方式 1 的计数位数是 16 位,由 TL0 作为低 8 位,TH0 作为高 8 位,组成了 16 位加 1 计数器
OSC框:表示时钟频率,因为 1 个机器周期等于 12 个时钟周期,所以那个 d 就等于 12
● 在 GATE 位为 1 的情况下,经过一个非门变成 0,或门电路结果要想是 1 的话,那 INT0 即 P3.2 引脚必须是 1 的情况下,这个时候定时器才会工作,而 INT0 引脚是 0 的情况下,定时器不工作,这就是 GATE 位的作用
● 当 GATE 位为 0 的时候,经过一个非门会变成 1,那么不管 INT0 引脚是什么电平,经过或门电路后都肯定是 1,定时器就会工作
● 要想让定时器工作,就是自动加 1,从图上看有两种方式,第一种方式是那个开关打到上边的箭头,就是 C/T = 0 的时候,一个机器周期 TL 就会加 1 一次,当开关打到下边的箭头,即 C/T =1 的时候,T0 引脚即 P3.4 引脚来一个脉冲,TL 就加 1 一次,这也就是计数器功能。
原理跟方式 0 差不多唯一不同的就是计数方式不同, 计数初值与计数个数的关系为:
X = 2 16 − N X=2^{16}-N
X = 2 16 − N
(3)方式 2
方式 2 为自动重装初值的 8 位计数方式。工作方式 2 特别适合于用作较精确的脉冲信号发生器
计数初值与计数个数的关系为:
X = 2 8 − N X=2^8-N
X = 2 8 − N
(4)方式 3
方式 3 只适用于定时/计数器 T0,定时器 T1 处于方式 3 时相当于 TR1=0, 停止计数。工作方式 3 将 T0 分成为两个独立的 8 位计数器 TL0 和 TH0。
这几种工作方式中应用较多的是方式 1 和方式 2。定时器中通常使用定时器方式 1,串口通信中通常使用方式 2
计算定时/计数器初值
12MHz晶振情况下,1个机器周期=1us,假如需要定时1ms则
1 m s / 1 u s = 1000 次 1ms/1us=1000\text{次}
1 m s /1 u s = 1000 次
溢出是65536,则用
65536 − 1000 = 64536 = F C 18 H 65536-1000=64536=FC18H
65536 − 1000 = 64536 = FC 18 H
所以初值为 THx=0XFC,TLx=0X18
开发板上使用的外部晶振不同,换算的初值是不一样的
我们开发板的晶振是 11.0592M,时钟周期就是 1/11059200,机器周期是 12/11059200,假如要定时 20ms,就是 0.02 秒,要经过 x 个机器周期得到 0.02 秒,我们来算一下 x*12/11059200=0.02,得到 x= 18432
先给 TH0 和 TL0 一个初始值,让它们经过 18432 个机器周期后刚好达到 65536,也就是溢出,溢出后可以通过检测 TF0 的值得知,就刚好是 0.02 秒。那么初值 y = 65536 - 18432 = 47104,转成 16 进制就是 0xB800,也就是 TH0 = 0xB8,TL0 = 0x00
小工具
使用小工具进行换算不用手动算
也可以使用STC-ISP里面的定时器计算器
如果要实现很长时间的定时,比如定时 1 秒钟。可以通过初值设置定时 1ms,每当定时 1ms 结束后又重新赋初值,并且设定一个全局变量累计定时 1ms 的次数,当累计到 1000 次,表示已经定时 1 秒了。需要其他定时时间类似操作,这样我们就可以使用定时器来实现精确延时来替代之前的 delay 函数。
定时器配置
在使用定时器时,应该如何配置使其工作?其步骤如下(各步骤顺序可任意):
对 TMOD 赋值,以确定 T0 和 T1 的工作方式,如果使用定时器 0 即对 T0 配 置,如果使用定时器 1 即对 T1 配置
根据所要定时的时间计算初值,并将其写入 TH0、TL0 或 TH1、TL1
如果使用中断,则对 EA 赋值,开放定时器中断
使 TR0 或 TR1 置位,启动定时/计数器定时或计数
判断 TCON 寄存器的 TF0 位,监测定时器溢出情况
定时器程序
定时器0控制LED 0.5 秒闪烁一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <reg52.h> sbit LED2 = P0 ^ 0 ; sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { unsigned int count = 0 ; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; TMOD |= 0x01 ; TL0 = 0x66 ; TH0 = 0xFC ; TR0 = 1 ; while (1 ) { if (TF0 == 1 ) { TF0 = 0 ; TL0 = 0x66 ; TH0 = 0xFC ; count++; } if (count == 500 ) { count = 0 ; LED2 = !LED2; } } }
使用定时器定时0.5秒控制LED流水灯流动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <reg52.h> #define LED_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { unsigned int count = 0 ; unsigned char led_state = 0x01 ; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; TMOD |= 0x01 ; TL0 = 0x66 ; TH0 = 0xFC ; TR0 = 1 ; while (1 ) { if (TF0 == 1 ) { TF0 = 0 ; TL0 = 0x66 ; TH0 = 0xFC ; count++; if (count == 500 ) { count = 0 ; LED_PORT = ~led_state; led_state <<= 1 ; if (led_state == 0 ) { led_state = 0x01 ; } } } } }
中断
标准 51 单片机中控制中断的寄存器有两个,一个是中断使能寄存器,另一个是中断优先级寄存器
中断概念
中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的, 中断功能的存在,很大程度上提高了单片机处理外部或内部事件的能力。对于单片机来讲,中断是指 CPU 在处理某一事件 A 时,发生了另一事件 B, 请求 CPU 迅速去处理(中断发生);CPU 暂时停止当前的工作(中断响应), 转去处理事件 B(中断服务);待 CPU 将事件 B 处理完毕后,再回到原来事件 A 被中断的地方继续处理事件 A(中断返回),这一过程称为中断。
单片机在执行程序时其程序流程图如下:
引起 CPU 中断的根源称为中断源。中断源向 CPU 提出中断请求,CPU 暂时中断原来的事务 A,转去处理事件 B,对事件 B 处理完毕后,再回到原来被中断的地方(即断点),称为中断返回。实现上述中断功能的部件称为中断系统(中断机构)。
CPU总是先响应优先级别最高的中断请求。如果 CPU 能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为 中断嵌套。这样的中断系统称为 多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。
中断优点:
1、分时操作
CPU 可以分时为多个I/O 设备服务,提高了计算机的利用率;
2、实时响应
CPU 能够及时处理应用系统的随机事件,系统的实时性大大增强;
3、可靠性高
CPU具有处理设备故障及掉电等突发性事件能力,从而使系统可靠性提高
中断结构
STC89C5X 系列单片机提供了 8 个中断请求源,它们分别是:外部中断 0(INT0)、外部中断 1(INT1)、外部中断 2(INT2)、外部中断 3(INT3)、定时器 0 中断、定时器 1 中断、定时器 2 中断、串口(UART)中断。
注意:51 系列单片机一定有基本的 5 个中断,但不全有 8 个中断,需要查看芯片手册,通常我们使用的都是基本的 5 个中断:INT0、INT1、定时器 0/1,串口中断。所有的中断都具有四个中断优先级(基本型只有两个)。
1 2 3 高优先级的中断请求可以打断低优先级的中断,反之,低优先级的中断请求不可以打断高优先级及同优先级的中断。 当两个相同优先级的中断同时产生时,将由查询次序来决定系统先响应哪个中断。 中断查询次序即为中断号,这个中断号在编程时非常重要,当中断来临时,只有中断号正确才能进入中断。
STC89C5X 系列单片机的各个中断查询次序表如下图所示:
下面是51单片机均有的5个基本中断:
①INT0 对应的是 P3.2 口的附加功能,可由 IT0(TCON.0)选择其为低电平有效还是下降沿有效。当 CPU 检测到 P3.2 引脚上出现有效的中断信号时,中断标志IE0(TCON.1)置 1,向 CPU 申请中断。
②INT1 对应的是 P3.3 口的附加功能,可由 IT1(TCON.2)选择其为低电平有效还是下降沿有效。当 CPU 检测到 P3.3 引脚上出现有效的中断信号时,中断标志 IE1(TCON.3)置 1,向 CPU 申请中断。
③T0 对应的是 P3.4 口的附加功能,TF0(TCON.5),片内定时/计数器 T0 溢出中断请求标志。当定时/计数器 T0 发生溢出时,置位 TF0,并向 CPU 申请中断。
④T1 对应的是 P3.5 口的附加功能,TF1(TCON.7),片内定时/计数器 T1 溢出中断请求标志。当定时/计数器 T1 发生溢出时,置位 TF1,并向 CPU 申请中断。
⑤RXD 和 TXD 对应的是 P3.0 和 P3.1 口的附加功能,RI(SCON.0)或 TI (SCON.1),串行口中断请求标志。当串行口接收完一帧串行数据时置位 RI 或当串行口发送完一帧串行数据时置位 TI,向 CPU 申请中断。
中断相关寄存器
中断允许控制
CPU 对中断系统所有中断以及某个中断源的开放和屏蔽是由中断允许寄存器 IE 控制的。
1 2 3 4 5 6 EX0(IE.0):外部中断 0 允许位; ET0(IE.1):定时/计数器 T0 中断允许位; EX1(IE.2):外部中断 1 允许位; ET1(IE.3):定时/计数器 T1 中断允许位; ES(IE.4):串行口中断允许位; EA (IE.7):CPU 中断允许(总允许)位。
中断请求标志 TCON
1 2 3 4 5 6 IT0(TCON.0),外部中断 0 触发方式控制位。 当 IT0=0 时,为电平触发方式。 当 IT0=1 时,为边沿触发方式(下降沿有效)。 IE0(TCON.1),外部中断 0 中断请求标志位。 IT1(TCON.2),外部中断 1 触发方式控制位。 IE1(TCON.3),外部中断 1 中断请求标志位。 TF0(TCON.5),定时/计数器 T0 溢出中断请求标志位。 TF1(TCON.7),定时/计数器 T1 溢出中断请求标志位。
中断优先级
同一优先级中的中断申请不止一个时,则有中断优先权排队问题。同一优先级的中断优先权排队,由中断系统硬件确定的自然优先级形成,其排列如所示:
中断号
问:单片机又怎样找到这个中断函数呢?
靠的就是中断向量地址,所以 interrupt 后面中断函数编号的数字 x 就是根据中断向 量得出的,它的计算方法是 x*8+3=向量地址
中断优先级有两种,一种是 抢占优先级,一种是 固有优先级
抢占优先级寄存器:
中断响应条件
①中断源有中断请求;
②此中断源的中断允许位为 1;
③CPU 开中断(即 EA=1)。
以上三条同时满足时,CPU 才有可能响应中断。
在使用中断时我们需要做什么呢?
①你想使用的中断是哪个?选择相应的中断号;
②你所希望的触发条件是什么?
③你希望在中断之后干什么?
外部中断配置
以外部中断 0 为例,如下:
主程序中需要有以下代码
如果要配置的是外部中断 1,只需将 EX0 改为 EX1,IT0 改为 IT1,通常使用外部中断都是配置为下降沿触发,即 IT0=1
当触发中断后即会进入中断服务函数,外部中断 0 中断服务函数如下:
在中断函数中 exti0 是函数名,可自定义,但必须符合 C 语言标识符定义规则,interrupt 是一个关键字,表示 51 单片机中断。后面的 0 是中断号,外部中断 0 中断号为 0,如果是外部中断 1,则中断号为 2
数码管
数码管简介
数码管是一种半导体发光器件,其基本单元是发光二极管。按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管
共阳数码管 是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极 COM 接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮
共阴数码管 是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴数码管在应用时应将公共极 COM 接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮
数码表
共阴数码管码表
共阳数码管码表
从上述共阳和共阴码表中不难发现,它们的数据正好是相互取反的值。比如共阴数码管数字 0 段码:0x3f,其二进制是:0011 1111,取反后为:1100 0000, 转换成 16 进制即为 0XC0。其他段码依此类推。该段码数据由来,是将 a 段作为最低位,b 段作为次低位,其他按顺序类推,dp 段为最高位,共 8 位,正好和 51 单片机的一组端口数一样,因此可以直接使用某一组端口控制数码管的段选数据口,比如 P0 口
数码管原理图
我们开发板的所用的数码管都是共阳数码管,一共有 6 个
6 个数码管的 com 都是接到了正极上,和 LED 小灯电路一样,也是由 74HC138 控制三极管的导通来控制整个数码管的使能。先来看最右边的 DS1 这个数码管,原理图上可 以看出,控制 DS1 的三极管是 Q17,控制 Q17 的引脚是 LEDS0,对应到 74HC138 上边就是 U3 的 Y0 输出,如图所示:
问:为什么数码管上边有 2 个 com 呢?
一方面是 2 个可以起到对称的效果,刚好是 10 个引脚,另外一个方面,公共端通过的电流较大,并联电路电流之和等于总电流,用2 个 com 可以把公共电流平均到 2 个引脚上去,降低单条线路承受的电流
数码管的 8 个段,我们直接当成 8 个 LED 小灯来控制,那就是 a、b、c、d、e、f、g、dp
数码管显示
静态显示 :多位数码管依然可以静态显示,但是显示时要么只显示一位数码管,要么多位同时显示相同内容。当多位数码管应用于某一系统时,它们的“位选”是可独立控制的,而“段选”是连接在一起的,我们可以通过位选信号控制哪几个数码管亮,而在同一时刻,位选选通的所有数码管上显示的数字始终都是一样的,因为它们的段选是连接在一起的,送入所有数码管的段选信号都是相同的,所以它们显示的数字必定一样,数码管的这种显示方法叫做静态显示。
动态显示 :就是利用减少段选线,分开位选线,利用位选线不同时选择通断,改变段选数据来实现的。比如在第一次选中第一位数码管时,给段选数据 0, 下一次位选中第二位数码管时显示 1。为了在显示 1 的时候,0 不会消失(当然实际上是消失了),必须在人肉眼观察不到的时间里再次点亮第一次点亮的 0。 而这时就需要记住,人的肉眼正常情况下只能分辨变化超过 24ms 间隔的运动。 也就是说,在下一次点亮 0 这个数字的时间差不得大于 24ms。这时就会发现, 数码管点亮是在向右或者向左一位一位点亮,形成了动态效果。如果把间隔时间改长就能直接展现这一现象。
静态数码管程序
最右边的数码管显示0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <reg52.h> #define LED_SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; while (1 ) { LED_SMG_PORT = 0xc0 ; } }
最右边的数码管显示0~F
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16; #define LED_SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 code gsmg[] = { 0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; void main () { u16 count = 0 ; u8 sec = 0 ; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; TMOD |= 0x01 ; TL0 = 0x66 ; TH0 = 0xFC ; TR0 = 1 ; while (1 ) { if (TF0 == 1 ) { TF0 = 0 ; TL0 = 0x66 ; TH0 = 0xFC ; count++; } if (count == 1000 ) { count = 0 ; LED_SMG_PORT = gsmg[sec]; sec++; if (sec >= 16 ) { sec = 0 ; } } } }
另外一种写法
将中断写成一个函数,这样子就不需要清除溢出标志位(TF0 = 0),因为定时器中断服务函数会自动清除标志位,得加上ET0或者ET1 = 1,使能定时器中断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16; #define LED_PORT P0 u8 code gsmg[] = { 0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; EA = 1 ; TMOD |= 0x01 ; TH0 = 0XFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; while (1 ); } void time0 () interrupt 1 { static u16 count = 0 ; static u8 sec = 0 ; TH0 = 0XFC ; TL0 = 0x66 ; count++; if (count == 1000 ) { count = 0 ; LED_PORT = gsmg[sec]; sec++; if (sec > 15 ) { sec = 0 ; } } }
动态数码管程序
数码管计数,从0到999999
解决数码管抖动的方法就是用中断
中断函数写好后, 每当满足中断条件而触发中断后,系统就会自动来调用中断函数。比如平时一直在主程序 while(1)的循环中执行,假如程序有 100 行,当执 行到 50 行时,定时器溢出了,那么单片机就会立刻跑到中断函数中执行中断程序,中断程序执行完毕后再自动返回到刚才的第 50 行处继续执行下面的程序,这样 就保证了动态显示间隔是固定的 1ms,不会因为程序执行时间不一致的原因导致数码管显示的抖动了
还有一点需要注意:程序应该尽量减少全局变量的使用,能用局部变量代替尽量代替
全局变量在其定义后所有函数都能用,但是静态局部变量只能在一个函数里面用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#define LED_SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 code gsmg[] = { 0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; u8 Led_Buff[6 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; u32 sec = 0 ; u8 flag = 0 ; void main () { EA = 1 ; ENLED = 0 ; ADDR3 = 1 ; TMOD |= 0x01 ; TL0 = 0x66 ; TH0 = 0xFC ; ET0 = 1 ; TR0 = 1 ; while (1 ) { if (flag == 1 ) { flag = 0 ; sec++; if (sec <= 0 ) { sec = 0 ; } Led_Buff[0 ] = gsmg[sec % 10 ]; Led_Buff[1 ] = gsmg[sec / 10 % 10 ]; Led_Buff[2 ] = gsmg[sec / 100 % 10 ]; Led_Buff[3 ] = gsmg[sec / 1000 % 10 ]; Led_Buff[4 ] = gsmg[sec / 10000 % 10 ]; Led_Buff[5 ] = gsmg[sec / 100000 % 10 ]; } } } void Time0 () interrupt 1 { static u16 count = 0 ; static u8 i = 0 ; TL0 = 0x66 ; TH0 = 0xFC ; count++; if (count == 1000 ) { count = 0 ; flag = 1 ; } if (sec <= 9 ) { Led_Buff[1 ] = 0xFF ; Led_Buff[2 ] = 0xFF ; Led_Buff[3 ] = 0xFF ; Led_Buff[4 ] = 0xFF ; Led_Buff[5 ] = 0xFF ; } if (sec <= 99 ) { Led_Buff[2 ] = 0xFF ; Led_Buff[3 ] = 0xFF ; Led_Buff[4 ] = 0xFF ; Led_Buff[5 ] = 0xFF ; } if (sec <= 999 ) { Led_Buff[3 ] = 0xFF ; Led_Buff[4 ] = 0xFF ; Led_Buff[5 ] = 0xFF ; } if (sec <= 9999 ) { Led_Buff[4 ] = 0xFF ; Led_Buff[5 ] = 0xFF ; } if (sec <= 99999 ) { Led_Buff[5 ] = 0xFF ; } LED_SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i = 0 ; LED_SMG_PORT = Led_Buff[5 ]; break ; default : break ; } }
1 2 3 4 5 6 7 8 9 10 11 12 SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = LedBuff[i]; if (i < 5 ) i++; else i = 0 ;
使用定时器做一个时钟
smg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <reg52.h> #include "smg.h" #define LED_SMG_PORT P0 u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 Led_Buff[6 ] = {0XFF , 0XFF , 0XFF , 0XFF , 0XFF , 0XFF }; void smg_display () { static u8 i = 0 ; SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i = 0 ; LED_SMG_PORT = Led_Buff[5 ]; break ; default : break ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _SMG_H #define _SMG_H typedef unsigned char u8; typedef unsigned int u16;#define SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; extern u8 gsmg[10 ]; extern u8 Led_Buff[6 ];void smg_display () ;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <reg52.h> #include "time.h" void Time0_Init () { EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = 0XFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 #ifndef _TIME_H #define _TIME_H void Time0_Init () ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <reg52.h> #include "time.h" #include "smg.h" u8 flag = 0 ; u8 hour, min, sec; void main () { ENLED = 0 ; ADDR3 = 1 ; Time0_Init(); while (1 ) { if (flag == 1 ) { flag = 0 ; sec++; if (sec >= 60 ) { sec = 0 ; min++; if (min >= 60 ) { min = 0 ; hour++; if (hour >= 24 ) { hour = 0 ; } } } } Led_Buff[0 ] = gsmg[sec % 10 ]; Led_Buff[1 ] = gsmg[sec / 10 ]; Led_Buff[2 ] = gsmg[min % 10 ]; Led_Buff[3 ] = gsmg[min / 10 ]; Led_Buff[4 ] = gsmg[hour % 10 ]; Led_Buff[5 ] = gsmg[hour / 10 ]; } } void time0 () interrupt 1 { static u16 count = 0 ; TH0 = 0XFC ; TL0 = 0x66 ; count++; if (count == 1000 ) { count = 0 ; flag = 1 ; } smg_display(); }
使用定时器做一个秒表
第一个数码管显示毫秒的十位,第二个数码管显示百位,第三数码管显示秒的个位,第四个数码管显示秒的十位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;#define SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 Led_Buff[4 ] = {0xFF , 0xFF , 0xFF , 0xFF }; u8 flag = 0 ; u8 sec = 0 ; u16 msec = 0 ; void main () { ENLED = 0 ; ADDR3 = 1 ; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = 0xFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; while (1 ) { if (flag == 1 ) { flag = 0 ; msec++; if (msec == 1000 ) { msec = 0 ; sec++; if (sec >= 60 ) { sec = 0 ; } } } Led_Buff[0 ] = gsmg[msec / 10 % 10 ]; Led_Buff[1 ] = gsmg[msec / 100 % 10 ]; Led_Buff[2 ] = gsmg[sec % 10 ]; Led_Buff[3 ] = gsmg[sec / 10 ]; } } void time0 () interrupt 1{ static u8 i = 0 ; TH0 = 0xFC ; TL0 = 0x66 ; flag = 1 ; SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; SMG_PORT = Led_Buff[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; SMG_PORT = Led_Buff[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; SMG_PORT = Led_Buff[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i = 0 ; SMG_PORT = Led_Buff[3 ]; break ; default : break ; } }
使用定时器做一个秒表倒计时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 #include <reg52.h> typedef unsigned char u8; #define LED_SMG_PORT P0 u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 Led_Buff[6 ] = {0XFF , 0XFF , 0XFF , 0XFF , 0XFF , 0XFF }; sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 flag = 0 ; void Time0_Init () ;void SMG_Show () ;void SMG_Scan () ;void main () { ENLED = 0 ; ADDR3 = 1 ; Time0_Init(); while (1 ) { if (1 == flag) { flag = 0 ; SMG_Show(); } } } void Time0_Init () { EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x02 ; TH0 = 0xA4 ; TL0 = 0xA4 ; ET0 = 1 ; TR0 = 1 ; } void SMG_Show () { static u8 min = 1 ; static u8 sec = 30 ; static u8 msec = 30 ; Led_Buff[0 ] = gsmg[msec % 10 ]; Led_Buff[1 ] = gsmg[msec / 10 ]; Led_Buff[2 ] = gsmg[sec % 10 ]; Led_Buff[3 ] = gsmg[sec / 10 ]; Led_Buff[4 ] = gsmg[min % 10 ]; Led_Buff[5 ] = gsmg[min / 10 ]; if (msec > 0 ) { msec--; if ((msec == 0 ) && (sec > 0 )) { msec = 99 ; sec--; if ((sec == 0 ) && (min > 0 )) { sec = 59 ; min--; if (min == 0 ) { min = 0 ; } } } } } void SMG_Scan () { static u8 i = 0 ; LED_SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i = 0 ; LED_SMG_PORT = Led_Buff[5 ]; break ; default : break ; } } void time0 () interrupt 1{ static u8 count = 0 ; TH0 = 0xA4 ; TL0 = 0xA4 ; count++; if (count > 100 ) { count = 0 ; flag = 1 ; } SMG_Scan(); }
数码管显示时钟,设置LED左右流水
smg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include "AllHead.h" u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 Led_Buff[7 ] = {0XFF , 0XFF , 0XFF , 0XFF , 0XFF , 0XFF , 0XFF }; void smg_display () { static u8 i = 0 ; LED_SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = Led_Buff[4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = Led_Buff[5 ]; break ; case 6 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; i = 0 ; LED_SMG_PORT = Led_Buff[6 ]; break ; default : break ; } } void smg_show () { static u8 hour, min, sec; Led_Buff[0 ] = gsmg[sec % 10 ]; Led_Buff[1 ] = gsmg[sec / 10 ]; Led_Buff[2 ] = gsmg[min % 10 ]; Led_Buff[3 ] = gsmg[min / 10 ]; Led_Buff[4 ] = gsmg[hour % 10 ]; Led_Buff[5 ] = gsmg[hour / 10 ]; sec++; if (sec >= 60 ) { sec = 0 ; min++; if (min >= 60 ) { min = 0 ; hour++; if (hour >= 24 ) { hour = 0 ; } } } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef _SMG_H #define _SMG_H typedef unsigned char u8; typedef unsigned int u16;#define LED_SMG_PORT P0 sbit ADDR0 = P1^0 ; sbit ADDR1 = P1^1 ; sbit ADDR2 = P1^2 ; sbit ADDR3 = P1^3 ; sbit ENLED = P1^4 ; extern u8 gsmg[10 ]; extern u8 Led_Buff[7 ];void smg_display () ;void smg_show () ;#endif
LED.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include "AllHead.h" void LED_Contral (u8 dir) { if (0 == dir) { static u8 Led_state = 0x01 ; Led_Buff[6 ] = ~Led_state; Led_state <<= 1 ; if (Led_state == 0 ) { Led_state = 0x01 ; } } if (1 == dir) { static u8 Led_state = 0x80 ; Led_Buff[6 ] = ~Led_state; Led_state >>= 1 ; if (Led_state == 0 ) { Led_state = 0x80 ; } } }
LED.h
1 2 3 4 5 6 #ifndef _LED_H #define _LED_H void LED_Contral (u8 dir) ; #endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <reg52.h> #include "time.h" void Time0_Init () { EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = 0XFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 #ifndef _TIME_H #define _TIME_H void Time0_Init () ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "AllHead.h" u8 flag1 = 0 ; u8 flag2 = 0 ; u8 hour, min, sec; void main () { ENLED = 0 ; ADDR3 = 1 ; Time0_Init(); while (1 ) { if (flag1 == 1 ) { flag1 = 0 ; smg_show(); } if (flag2 == 1 ) { flag2 = 0 ; LED_Contral(1 ); } } } void time0 () interrupt 1 { static u16 count1 = 0 ; static u16 count2 = 0 ; TH0 = 0XFC ; TL0 = 0x66 ; count1++; if (count1 == 1000 ) { count1 = 0 ; flag1 = 1 ; } count2++; if (count2 == 1000 ) { count2 = 0 ; flag2 = 1 ; } smg_display(); }
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ #include <reg52.h> #include "time.h" #include "smg.h" #include "LED.h" #endif
红绿灯
左边 LED8 和 LED9 一起亮作为绿灯,中间 LED5 和 LED6 一起亮作为黄灯,右边 LED2 和 LED3 一起亮作为红灯,用数码管的低 2 位做倒计时,让 LED 和数码管同时参与工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#define LED_SMG_PORT P0 sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 SMG_BUFF[7 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; u8 T0RH = 0 ; u8 T0RL = 0 ; bit flag = 1 ; void Smg_Scan () ; void Config_Time0 (u16 ms) ;void Traffic_Refresh () ;void main () { ENLED = 0 ; ADDR3 = 1 ; Config_Time0(1 ); while (1 ) { if (flag) { flag = 0 ; Traffic_Refresh(); } } } void Config_Time0 (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; } void Traffic_Refresh () { static u8 color = 2 ; static u8 time = 0 ; if (0 == time) { switch (color) { case 0 : color = 1 ; time = 5 ; SMG_BUFF[6 ] = 0xE7 ; break ; case 1 : color = 2 ; time = 30 ; SMG_BUFF[6 ] = 0xFC ; break ; case 2 : color = 0 ; time = 15 ; SMG_BUFF[6 ] = 0x3F ; break ; default : break ; } } else time--; SMG_BUFF[0 ] = gsmg[time % 10 ]; SMG_BUFF[1 ] = gsmg[time / 10 ]; } void Smg_Scan () { static u8 i; LED_SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = SMG_BUFF[i]; if (i < 6 ) i++; else i = 0 ; } void time0 () interrupt 1{ static u16 count = 0 ; TH0 = T0RH; TL0 = T0RL; Smg_Scan(); count++; if (count >= 1000 ) { count = 0 ; flag = 1 ; } }
点阵
点阵 LED 显示屏作为一种现代电子媒体,具有灵活的显示面积(可任意分割和拼装)、 高亮度、长寿命、数字化、实时性等特点,应用非常广泛,一个 8*8 的点阵就是由 64 个 LED 小灯组成
点阵 LED 最小单元如图所示:
LED点阵原理图
在图中大方框外侧的就是 点阵 LED 的引脚号,左侧的 8 个引脚是接的内部 LED 的阳极,上侧的 8 个引脚接的是内部 LED 的阴极。那么如果我们把 9 脚置成高电平、13 脚置成低电平的话,左上角的那个 LED 小灯就会亮了。特别注意,控制点阵左侧引脚的 74HC138 是原理图上的 U4,8 个引脚自上而下依次由 U4 的 Y0~Y7 输出来控制
74HC138原理图
由于ENLED接的是5号管脚,低电平使能,ADDR3接在4号管脚,同样也是低电平使能,所以要驱动U4的话,得让ENLED和ADDR3都为0,这样子芯片才能正常工作,这样子不会使U3驱动,所以各个功能模块互不打扰
取字模软件
双击打开该软件,首先选择“基本操作->新建图像”,设置图像的宽度和高度为8,点击确定后将在显示窗口出现一个8x8的白色格子,这个就类似于8x8LED 点阵,具体操作如下
可以看到上图 8*8 点阵区域非常小,我们可以将其放大,选择“模拟动画”, 后点击“放大格点”,
如下所示
由于取模软件是把黑色取为 1,白色取为 0,但我们点阵是 1 对应 LED 熄灭,0 对应 LED 点亮,所以我们要选“修改图像”菜单里的“黑白反显图像”这个选项,再点击“基本操作”菜单里边的“保存图像”可以把我们设计好的图片进行保存
然后设置取模数据的取模方式等内容,选择“参数设置”后点击“其他 选项”,具体操作如下:
这里的选项,要结合原理图,可以看到 P0 口控制的是一行,所以用“横向取模”,如果控制的是一列,就要选“纵向取模”。选中“字节倒序”这个选项,是因为原理图左边是低位 DB0,右边是高位 DB7,所以是字节倒序,点确定后,选择“取模方式”这个菜单,点一下“C51 格式”后,在“点阵生成区”自动产生了 8 个字节的数据,这 8 个字节的数据就是取出来的“模”,如图所示
在这个图片里,黑色的一个格子表示一位二进制的 1,白色的一个格子表示一位二进制的 0。第一个字节是0xFF,其实就是这个 8*8 图形的第一行,全黑就是 0xFF;第二个字节是 0x99,低位在左边,高位在右边
程序
点亮一个点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <reg52.h> sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit LED = P0 ^ 0 ; void main () { ENLED = 0 ; ADDR3 = 0 ; ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; LED = 0 ; while (1 ); }
点亮一行
一个数码管就是 8 个 LED 小灯,一个点阵是 64 个 LED 小灯。同样的道理,我们还可以把一个点阵理解成是 8 个数码管。 我们可以利用定时器中断和数码管动态显示的原理来把这个点阵全部点亮
点亮全部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <reg52.h> typedef unsigned char u8; #define LED_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 0 ; EA = 1 ; TMOD |= 0x01 ; TH0 = 0XFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; while (1 ); } void Time0 () interrupt 1{ static u8 i = 0 ; TH0 = 0XFC ; TL0 = 0x66 ; LED_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_PORT = 0x00 ; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_PORT = 0x00 ; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_PORT = 0x00 ; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_PORT = 0x00 ; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_PORT = 0x00 ; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_PORT = 0x00 ; break ; case 6 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_PORT = 0x00 ; break ; case 7 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 1 ; i = 0 ; LED_PORT = 0x00 ; break ; default : break ; } }
显示爱心
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #include <reg52.h> typedef unsigned char u8; #define LED_PORT P0 u8 code image[] = {0xFF , 0x99 , 0x00 , 0x00 , 0x00 , 0x81 , 0xC3 , 0xE7 }; sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 0 ; EA = 1 ; TMOD |= 0x01 ; TH0 = 0XFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; while (1 ); } void Time0 () interrupt 1{ static u8 i = 0 ; TH0 = 0XFC ; TL0 = 0x66 ; LED_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_PORT = image[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_PORT = image[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_PORT = image[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_PORT = image[3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_PORT = image[4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_PORT = image[5 ]; break ; case 6 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_PORT = image[6 ]; break ; case 7 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 1 ; i = 0 ; LED_PORT = image[7 ]; break ; default : break ; } }
点阵的动画显示
点阵的动画显示,就是对多张图片分别进行取模,使用程序算法巧妙的切换图片, 多张图片组合起来就成了一段动画了,我们所看到的动画片、游戏等等,它们的基本原理也都是这样的
点阵的纵向移动(向上移动)
我们要让点阵显示一个 I ❤ U 的动画,首先我们要把这个图形用取模软件画出来,如图所示:
这张图片共有 40 行,每 8 行组成一张点阵图片,并且每向上移动一行就出现了一张新图片,一共组成了 32 张图片
用一个变量 index 来代表每张图片的起始位置,每次从 index 起始向下数 8 行代表了当前的图片,250ms 改变一张图片,然后不停的动态刷新,这样图片就变成动画了。首先我们要对显示的图片进行横向取模,虽然这是 32 张图片,由于我们每一张图片都是和下一行连续的,所以实际的取模值只需要 40 个字节就可以完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <reg52.h> typedef unsigned char u8; #define LED_PORT P0 u8 code image[] = { 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xC3 , 0xE7 , 0xE7 , 0xE7 , 0xE7 , 0xE7 , 0xC3 , 0xFF , 0x99 , 0x00 , 0x00 , 0x00 , 0x81 , 0xC3 , 0xE7 , 0xFF , 0x99 , 0x99 , 0x99 , 0x99 , 0x99 , 0x81 , 0xC3 , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 0 ; EA = 1 ; TMOD |= 0x01 ; TH0 = 0XFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; while (1 ); } void Time0 () interrupt 1{ static u8 i = 0 ; static u8 tmr = 0 ; static u8 index = 0 ; TH0 = 0XFC ; TL0 = 0x66 ; LED_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_PORT = image[index + 0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_PORT = image[index + 1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_PORT = image[index + 2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_PORT = image[index + 3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_PORT = image[index + 4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_PORT = image[index + 5 ]; break ; case 6 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_PORT = image[index + 6 ]; break ; case 7 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 1 ; i = 0 ; LED_PORT = image[index + 7 ]; break ; default : break ; } tmr++; if (tmr >= 250 ) { tmr = 0 ; index++; if (index >= 32 ) { index = 0 ; } } }
向下移动(纵向)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned char code image[] = { 0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF , 0x99 ,0x99 ,0x99 ,0x99 ,0x99 ,0x81 ,0xC3 ,0xFF , 0x99 ,0x00 ,0x00 ,0x00 ,0x81 ,0xC3 ,0xE7 ,0xFF , 0xC3 ,0xE7 ,0xE7 ,0xE7 ,0xE7 ,0xE7 ,0xC3 ,0xFF , 0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF }; static unsigned char index = 31 ; if (tmr >= 250 ) { tmr = 0 ; if (index == 0 ) index = 31 ; else index--; }
向左移动(横向)
我们在进行硬件电路设计的时候,也得充分考虑软件编程的方便性。因为我们的程序是用 P0 来控制点阵的整行,所以对于我们这样的电路设计,上下移动程序是比较好编写的。那如果我们设计电路的时候知道我们的图形要左右移动,那我们设计电路画板子的时候就要尽可能的把点阵横过来放,有利于我们编程方便,减少软件工作量
利用二维数组实现
最上面的图形是横向连在一起的效果,而实际上我们要把它分解为 30 个帧, 每帧图片单独取模,取出来都是 8 个字节的数据,一共就是 30*8 个数据,我们用一个二维数组来存储它们(取模软件里还是选横向,字节倒序)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 # include <reg52.h> typedef unsigned char u8; #define LED_PORT P0 sbit ADDR0 = P1^0 ; sbit ADDR1 = P1^1 ; sbit ADDR2 = P1^2 ; sbit ADDR3 = P1^3 ; sbit ENLED = P1^4 ; u8 code image[30 ][8 ] = { {0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF }, {0xFF ,0x7F ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0x7F }, {0xFF ,0x3F ,0x7F ,0x7F ,0x7F ,0x7F ,0x7F ,0x3F }, {0xFF ,0x1F ,0x3F ,0x3F ,0x3F ,0x3F ,0x3F ,0x1F }, {0xFF ,0x0F ,0x9F ,0x9F ,0x9F ,0x9F ,0x9F ,0x0F }, {0xFF ,0x87 ,0xCF ,0xCF ,0xCF ,0xCF ,0xCF ,0x87 }, {0xFF ,0xC3 ,0xE7 ,0xE7 ,0xE7 ,0xE7 ,0xE7 ,0xC3 }, {0xFF ,0xE1 ,0x73 ,0x73 ,0x73 ,0xF3 ,0xF3 ,0xE1 }, {0xFF ,0x70 ,0x39 ,0x39 ,0x39 ,0x79 ,0xF9 ,0xF0 }, {0xFF ,0x38 ,0x1C ,0x1C ,0x1C ,0x3C ,0x7C ,0xF8 }, {0xFF ,0x9C ,0x0E ,0x0E ,0x0E ,0x1E ,0x3E ,0x7C }, {0xFF ,0xCE ,0x07 ,0x07 ,0x07 ,0x0F ,0x1F ,0x3E }, {0xFF ,0x67 ,0x03 ,0x03 ,0x03 ,0x07 ,0x0F ,0x9F }, {0xFF ,0x33 ,0x01 ,0x01 ,0x01 ,0x03 ,0x87 ,0xCF }, {0xFF ,0x99 ,0x00 ,0x00 ,0x00 ,0x81 ,0xC3 ,0xE7 }, {0xFF ,0xCC ,0x80 ,0x80 ,0x80 ,0xC0 ,0xE1 ,0xF3 }, {0xFF ,0xE6 ,0xC0 ,0xC0 ,0xC0 ,0xE0 ,0xF0 ,0xF9 }, {0xFF ,0x73 ,0x60 ,0x60 ,0x60 ,0x70 ,0x78 ,0xFC }, {0xFF ,0x39 ,0x30 ,0x30 ,0x30 ,0x38 ,0x3C ,0x7E }, {0xFF ,0x9C ,0x98 ,0x98 ,0x98 ,0x9C ,0x1E ,0x3F }, {0xFF ,0xCE ,0xCC ,0xCC ,0xCC ,0xCE ,0x0F ,0x1F }, {0xFF ,0x67 ,0x66 ,0x66 ,0x66 ,0x67 ,0x07 ,0x0F }, {0xFF ,0x33 ,0x33 ,0x33 ,0x33 ,0x33 ,0x03 ,0x87 }, {0xFF ,0x99 ,0x99 ,0x99 ,0x99 ,0x99 ,0x81 ,0xC3 }, {0xFF ,0xCC ,0xCC ,0xCC ,0xCC ,0xCC ,0xC0 ,0xE1 }, {0xFF ,0xE6 ,0xE6 ,0xE6 ,0xE6 ,0xE6 ,0xE0 ,0xF0 }, {0xFF ,0xF3 ,0xF3 ,0xF3 ,0xF3 ,0xF3 ,0xF0 ,0xF8 }, {0xFF ,0xF9 ,0xF9 ,0xF9 ,0xF9 ,0xF9 ,0xF8 ,0xFC }, {0xFF ,0xFC ,0xFC ,0xFC ,0xFC ,0xFC ,0xFC ,0xFE }, {0xFF ,0xFE ,0xFE ,0xFE ,0xFE ,0xFE ,0xFE ,0xFF } }; void main () { EA = 1 ; ENLED = 0 ; ADDR3 = 0 ; TMOD = 0x01 ; TH0 = 0xFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; while (1 ); } void InterruptTimer0 () interrupt 1{ static u8 i = 0 ; static u8 tmr = 0 ; static u8 index = 0 ; TH0 = 0xFC ; TL0 = 0x66 ; LED_PORT = 0xFF ; switch (i) { case 0 : ADDR2=0 ; ADDR1=0 ; ADDR0=0 ; i++; LED_PORT=image[index][0 ]; break ; case 1 : ADDR2=0 ; ADDR1=0 ; ADDR0=1 ; i++; LED_PORT=image[index][1 ]; break ; case 2 : ADDR2=0 ; ADDR1=1 ; ADDR0=0 ; i++; LED_PORT=image[index][2 ]; break ; case 3 : ADDR2=0 ; ADDR1=1 ; ADDR0=1 ; i++; LED_PORT=image[index][3 ]; break ; case 4 : ADDR2=1 ; ADDR1=0 ; ADDR0=0 ; i++; LED_PORT=image[index][4 ]; break ; case 5 : ADDR2=1 ; ADDR1=0 ; ADDR0=1 ; i++; LED_PORT=image[index][5 ]; break ; case 6 : ADDR2=1 ; ADDR1=1 ; ADDR0=0 ; i++; LED_PORT=image[index][6 ]; break ; case 7 : ADDR2=1 ; ADDR1=1 ; ADDR0=1 ; i=0 ; LED_PORT=image[index][7 ]; break ; default : break ; } tmr++; if (tmr >= 250 ) { tmr = 0 ; index++; if (index >= 30 ) { index = 0 ; } } }
向右移动(横向)
1 代码跟左移一样,只需把模的数组生成时把 "字节倒序" 关闭了重新生成即可
0~9的模
1 2 3 4 5 6 7 8 9 10 11 12 13 unsigned char code image[11 ][8 ] = { {0xC3 , 0x81 , 0x99 , 0x99 , 0x99 , 0x99 , 0x81 , 0xC3 }, {0xEF , 0xE7 , 0xE3 , 0xE7 , 0xE7 , 0xE7 , 0xE7 , 0xC3 }, {0xC3 , 0x81 , 0x9D , 0x87 , 0xC3 , 0xF9 , 0xC1 , 0x81 }, {0xC3 , 0x81 , 0x9D , 0xC7 , 0xC7 , 0x9D , 0x81 , 0xC3 }, {0xCF , 0xC7 , 0xC3 , 0xC9 , 0xC9 , 0x81 , 0xCF , 0xCF }, {0x81 , 0xC1 , 0xF9 , 0xC3 , 0x87 , 0x9D , 0x81 , 0xC3 }, {0xC3 , 0x81 , 0xF9 , 0xC1 , 0x81 , 0x99 , 0x81 , 0xC3 }, {0x81 , 0x81 , 0x9F , 0xCF , 0xCF , 0xE7 , 0xE7 , 0xE7 }, {0xC3 , 0x81 , 0x99 , 0xC3 , 0xC3 , 0x99 , 0x81 , 0xC3 }, {0xC3 , 0x81 , 0x99 , 0x81 , 0x83 , 0x9F , 0x83 , 0xC1 }, {0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 }, };
按键
常用的按键电路有两种形式,独立式按键和矩阵式按键
独立按键
原理图
当按键 K1 按下时,+5V 通过电阻 R1 然后再通过按键 K1 最终进入 GND 形成一条通路,那么这条线路的全部电压都加到了 R1 这个电阻上, KeyIn1 这个引脚就是个低电平。当松开按键后,线路断开,就不会有电流通过,那么 KeyIn1 和+5V 就应该是等电位,是一个高电平。我们就可以通过 KeyIn1 这个 IO 口的高低电平来判断是否有按键按下。低电平代表按键按下,高电平代表按键弹起
按键是接到了 P2 口上,P2 口上电默认是准双向 IO 口\
准双向 IO 口的电路,如图所示:
我们要读取外部按键信号的时候,单片机必须先给该引脚写“1”,也就是高电平,这样我们才能正确读取到外部按键信号,这种具有上拉的准双向 IO 口,如果要正常读取外部信号的状态,必须首先得保证自己内部输出的是 1,如果内部输出 0,则无论外部信号是 1 还是 0,这个引脚读进来的都是 0
矩阵按键
如果需要使用很多的按键时,做成独立按键会大量占用 IO 口, 所以一般都会引入矩阵按键
原理图
如果 KeyOut1 输出一个低电平,KeyOut1 就相当于是 GND,这时候 KeyOut2、 KeyOut3、KeyOut4 都必须输出高电平,它们都输出高电平才能保证与它们相连的三路按键不会对这一路产生干扰
按键消抖
通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开, 而是在闭合和断开的瞬间伴随了一连串的抖动
抖动时间是由按键的机械特性决定的,一般都会在 10ms 以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。 当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。 按键消抖可分为硬件消抖和软件消抖。在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状 检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作
程序
按键按一次,数码管数值+1(使用Delay函数消抖)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <reg52.h> #include "Delay.h" #define LED_SMG_PORT P0 #define KEY_PORT P2 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit KeyIn1 = P2 ^ 4 ; sbit KeyIn2 = P2 ^ 5 ; sbit KeyIn3 = P2 ^ 6 ; sbit KeyIn4 = P2 ^ 7 ; u8 code gsmg[] = {0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; void main () { bit backup = 1 ; bit keybuf = 1 ; u8 count = 0 ; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; KEY_PORT = 0xF7 ; LED_SMG_PORT = gsmg[count]; while (1 ) { keybuf = KeyIn1; if (keybuf != backup) { Delay_ms(10 ); if (keybuf == KeyIn1) { if (backup == 0 ) { count++; if (count == 10 ) { count = 0 ; } LED_SMG_PORT = gsmg[count]; } backup = keybuf; } } } }
实际做项目开发的时候,程序量往往很大,各种状态值也很多,如果使用Delay的话,会影响主函数的运行,所以我们可以采用定时器中断进行按键消抖
我们启用一个定时中断,每 2ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 8 次后,看看这连续 8 次的按键状态是否是一致的。 8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,假如左边时间是起始 0 时刻,每经过 2ms 左移一次,每移动一次,判断当前连续的 8 次按键状态是不是全 1 或者全 0,如果是全 1 则判定为弹起,如果是全 0 则判定为按下,如果 0 和 1 交错,就认为是抖动
按键按一次,数码管数值+1(使用定时器中断消抖)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include <reg52.h> typedef unsigned char u8;typedef unsigned int u16;#define LED_SMG_PORT P0 #define KEY_PORT P2 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit KeyIn1 = P2 ^ 4 ; sbit KeyIn2 = P2 ^ 5 ; sbit KeyIn3 = P2 ^ 6 ; sbit KeyIn4 = P2 ^ 7 ; u8 code gsmg[] = {0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; bit key_state = 1 ; void main () { bit backup = 1 ; u8 count = 0 ; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; EA = 1 ; TMOD &= 0x0F ; TMOD |= 0x01 ; TH0 = 0xF8 ; TL0 = 0xCD ; ET0 = 1 ; TR0 = 1 ; KEY_PORT = 0xF7 ; LED_SMG_PORT = gsmg[count]; while (1 ) { if (key_state != backup) { if (backup == 0 ) { count++; if (count == 10 ) { count = 0 ; } LED_SMG_PORT = gsmg[count]; } backup = key_state; } } } void time0 () interrupt 1{ static u8 key_buf = 0xFF ; TH0 = 0xF8 ; TL0 = 0xCD ; key_buf = (key_buf << 1 ) | KeyIn1; if (key_buf == 0x00 ) { key_state = 0 ; } if (key_buf == 0xFF ) { key_state = 1 ; } else {} }
矩阵按键相当于 4 组每组各 4 个独立按键,一共是 16 个按键,在按键扫描中断中,我们每次让矩阵按键的一个 KeyOut 输出低电平,其它三个输出高电平,判断当前所有 KeyIn 的状态,下次中断时再让下一个 KeyOut 输出低电平,其它三个输出高电平,再次判断所有 KeyIn,通过 速的中断不停的循环进行判断,就可以最终确定哪个按键按下了,现在有 4 个 KeyOut 输出,要中断 4 次才能完成一次全部按键的扫描,显然再采用 2ms 中断判断 8 次扫描值的方式时间就太长了 (2*4*8=64ms),那么我们就改用 1ms 中断判断 4 次采样值( 即只判断低4位0000 xxxx ),这样消抖时间还是 16ms(1*4*4)
矩阵按键的扫描,并且将按键值显示在第一个数码管上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;#define LED_SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit KeyOut4 = P2 ^ 0 ; sbit KeyOut3 = P2 ^ 1 ; sbit KeyOut2 = P2 ^ 2 ; sbit KeyOut1 = P2 ^ 3 ; sbit KeyIn1 = P2 ^ 4 ; sbit KeyIn2 = P2 ^ 5 ; sbit KeyIn3 = P2 ^ 6 ; sbit KeyIn4 = P2 ^ 7 ; u8 code gsmg[] = {0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; u8 key_state[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; void main () { u8 i, j; u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; EA = 1 ; TMOD &= 0x0F ; TMOD |= 0x01 ; TH0 = 0xFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; LED_SMG_PORT = gsmg[0 ]; while (1 ) { for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (backup[i][j] != key_state[i][j]) { if (backup[i][j] != 0 ) { LED_SMG_PORT = gsmg[i * 4 + j]; } backup[i][j] = key_state[i][j]; } } } } } void time0 () interrupt 1{ u8 i = 0 ; static u8 KeyOut = 0 ; static u8 key_buf[4 ][4 ] = { {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; TH0 = 0xFC ; TL0 = 0x66 ; key_buf[KeyOut][0 ] = (key_buf[KeyOut][0 ] << 1 ) | KeyIn1; key_buf[KeyOut][1 ] = (key_buf[KeyOut][1 ] << 1 ) | KeyIn2; key_buf[KeyOut][2 ] = (key_buf[KeyOut][2 ] << 1 ) | KeyIn3; key_buf[KeyOut][3 ] = (key_buf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if ((key_buf[KeyOut][i] & 0x0F ) == 0x00 ) { key_state[KeyOut][i] = 0 ; } else if ((key_buf[KeyOut][i] & 0x0F ) == 0x0F ) { key_state[KeyOut][i] = 1 ; } } KeyOut++; if (KeyOut == 4 ) { KeyOut = 0 ; } switch (KeyOut) { case 0 : KeyOut4 = 1 ; KeyOut1 = 0 ; break ; case 1 : KeyOut1 = 1 ; KeyOut2 = 0 ; break ; case 2 : KeyOut2 = 1 ; KeyOut3 = 0 ; break ; case 3 : KeyOut3 = 1 ; KeyOut4 = 0 ; break ; default : break ; } }
行列式扫描法 – 在数码管上显示对应键值(01 ~ 16)
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <reg52.h> #include "key.h" u8 Key_Scan () { static u8 key1 = NO_KEY, key2, key3; static u8 time = 0 ; u8 key_sta = 1 ; u8 Key_value; u8 temp; key3 = key2; key2 = key1; time++; KEY_PORT = 0xF0 ; if ((key_sta == 1 ) && ((KEY_PORT & 0xF0 ) != 0xF0 )) { KEY_PORT = 0xF7 ; if ((KEY_PORT & 0xF7 ) != 0xF7 ) { temp = 0x07 | (KEY_PORT & 0xF0 ); key1 = temp; } KEY_PORT = 0xFB ; if ((KEY_PORT & 0xFB ) != 0xFB ) { temp = 0x0B | (KEY_PORT & 0xF0 ); key1 = temp; } KEY_PORT = 0xFD ; if ((KEY_PORT & 0xFD ) != 0xFD ) { temp = 0x0D | (KEY_PORT & 0xF0 ); key1 = temp; } KEY_PORT = 0xFE ; if ((KEY_PORT & 0xFE ) != 0xFE ) { temp = 0x0E | (KEY_PORT & 0xF0 ); key1 = temp; } if (time == 3 ) { time = 0 ; if ((key1 == key2) && (key1 == key3) && (key1 != NO_KEY)) { Key_value = key1; key_sta = 0 ; } } } else if ((KEY_PORT & 0xF0 ) == 0xF0 ) { key_sta = 1 ; time = 0 ; } return Key_value; }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _KEY_H_ #define _KEY_H_ typedef unsigned char u8; #define NO_KEY 0XFF #define KEY_PORT P2 u8 Key_Scan () ; #endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include <reg52.h> #include "key.h" #define LED_SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 code KeyCode[] = { 0xE7 , 0xD7 , 0xB7 , 0x77 , 0xEB , 0xDB , 0xBB , 0x7B , 0xED , 0xDD , 0xBD , 0x7D , 0xEE , 0xDE , 0xBE , 0x7E }; u8 code SMG_Char[] = {0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 }; u8 SMG_buff[2 ] = {0xFF , 0xFF }; u8 num = NO_KEY; void Time0_Init () ;void SMG_Dispaly () ;void main () { u8 key_num = NO_KEY; u8 i; ADDR3 = 1 ; ENLED = 0 ; Time0_Init(); while (1 ) { key_num = Key_Scan(); if (key_num != NO_KEY) { for (i = 0 ; i < 16 ; i++) { if (key_num == KeyCode[i]) { num = i; break ; } } } SMG_buff[0 ] = SMG_Char[(num + 1 ) % 10 ]; SMG_buff[1 ] = SMG_Char[(num + 1 ) / 10 ]; } } void Time0_Init () { EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = 0XFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; } void SMG_Dispaly () { static u8 i = 0 ; LED_SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = SMG_buff[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i = 0 ; LED_SMG_PORT = SMG_buff[1 ]; break ; default : break ; } } void time0 () interrupt 1{ TH0 = 0xFC ; TL0 = 0x66 ; if (num != NO_KEY) SMG_Dispaly(); }
线翻转扫描法-- 在数码管上显示对应键值(01 ~ 16)
使所有列为 高电平 时,检测所有行是否有 低电平 ,如果有,就记录行线值;然后再翻转,使所有行都为 高电平,检测所有列的值,由于有按键按下,列的值也会有变化,记录列的值,将行与列的键值拼接起来,从而就可以检测到全部按键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <reg52.h> #include "key.h" u8 Key_Scan () { static u8 key1 = NO_KEY, key2, key3; static u8 time = 0 ; u8 key_sta = 1 ; u8 Key_value; u8 temp; key3 = key2; key2 = key1; time++; KEY_PORT = 0xF0 ; if ((key_sta == 1 ) && ((KEY_PORT & 0xF0 ) != 0xF0 )) { KEY_PORT = 0xF0 ; if ((KEY_PORT & 0xF0 ) != 0xF0 ) { temp = KEY_PORT & 0xF0 ; } KEY_PORT = 0x0F ; if ((KEY_PORT & 0x0F ) != 0x0F ) { key1 = temp | (KEY_PORT & 0x0F ); } if (time == 3 ) { time = 0 ; if ((key1 == key2) && (key1 == key3) && (key1 != NO_KEY)) { Key_value = key1; key_sta = 0 ; } } } else if ((KEY_PORT & 0xF0 ) == 0xF0 ) { key_sta = 1 ; time = 0 ; } return Key_value; }
简单加减法计算器
smg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #include "AllHead.h" u8 code gsmg[] = {0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; u8 SMG_BUFF[6 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; void SMG_Scan () { static u8 i = 0 ; LED_SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = SMG_BUFF[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = SMG_BUFF[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_SMG_PORT = SMG_BUFF[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_SMG_PORT = SMG_BUFF[3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = SMG_BUFF[4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i = 0 ; LED_SMG_PORT = SMG_BUFF[5 ]; break ; default : break ; } } void SMG_Show (u32 num) { signed char i; u8 sign = 0 ; u8 buf[6 ]; if (num < 0 ) { sign = 1 ; num = -num; } else { sign = 0 ; } for (i = 0 ; i < 6 ; i++) { buf[i] = num % 10 ; num /= 10 ; } for (i = 5 ; i > 0 ; i--) { if (0 == buf[i]) { buf[i] = 0xFF ; } else { break ; } } if (sign == 1 ) { if (i < 5 ) { SMG_BUFF[i + 1 ] = 0xBF ; } } for (; i >= 0 ; i--) { SMG_BUFF[i] = gsmg[buf[i]]; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef __SMG_H #define __SMG_H #define LED_SMG_PORT P0 sbit ADDR0 = P1^0 ; sbit ADDR1 = P1^1 ; sbit ADDR2 = P1^2 ; sbit ADDR3 = P1^3 ; sbit ENLED = P1^4 ; void SMG_Scan () ;void SMG_Show (u32 num) ;extern u8 code gsmg[];extern u8 SMG_BUFF[6 ];#endif
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 #include "AllHead.h" u8 code KeyCodeMap[4 ][4 ] = { { 0x31 , 0x32 , 0x33 , 0x26 }, { 0x34 , 0x35 , 0x36 , 0x25 }, { 0x37 , 0x38 , 0x39 , 0x28 }, { 0x30 , 0x1B , 0x0D , 0x27 } }; u8 key_sate[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; void Key_Driver () { u8 i, j; static u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (backup[i][j] != key_sate[i][j]) { if (backup[i][j] != 0 ) { Key_Action(KeyCodeMap[i][j]); } backup[i][j] = key_sate[i][j]; } } } } void Key_Action (u8 keycode) { static char oprt = 0 ; static long result = 0 ; static long addend = 0 ; if ((keycode >= 0x30 ) && (keycode <= 0x39 )) { addend = (addend * 10 ) + (keycode - 0x30 ); SMG_Show(addend); } else if (0x26 == keycode) { oprt = 0 ; result = addend; addend = 0 ; SMG_Show(addend); } else if (0x28 == keycode) { oprt = 1 ; result = addend; addend = 0 ; SMG_Show(addend); } else if (0x0D == keycode) { if (0 == oprt) { result += addend; } else { result -= addend; } addend = 0 ; SMG_Show(result); } else if (0x1B == keycode) { addend = 0 ; result = 0 ; SMG_Show(addend); } } void Key_Scan () { u8 i = 0 ; static u8 KeyOut = 0 ; static u8 keybuf[4 ][4 ] = {{0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; keybuf[KeyOut][0 ] = (keybuf[KeyOut][0 ] << 1 ) | KeyIn1; keybuf[KeyOut][1 ] = (keybuf[KeyOut][1 ] << 1 ) | KeyIn2; keybuf[KeyOut][2 ] = (keybuf[KeyOut][2 ] << 1 ) | KeyIn3; keybuf[KeyOut][3 ] = (keybuf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == (keybuf[KeyOut][i] & 0x0F )) { key_sate[KeyOut][i] = 0 ; } else if (0x0F == (keybuf[KeyOut][i] & 0x0F )) { key_sate[KeyOut][i] = 1 ; } } KeyOut++; if (KeyOut >= 4 ) { KeyOut = 0 ; } switch (KeyOut) { case 0 : KeyOut4 = 1 ; KeyOut1 = 0 ; break ; case 1 : KeyOut1 = 1 ; KeyOut2 = 0 ; break ; case 2 : KeyOut2 = 1 ; KeyOut3 = 0 ; break ; case 3 : KeyOut3 = 1 ; KeyOut4 = 0 ; break ; default : break ; } }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef __KEY_H #define __KEY_H sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; sbit KeyOut1 = P2^3 ; sbit KeyOut2 = P2^2 ; sbit KeyOut3 = P2^1 ; sbit KeyOut4 = P2^0 ; void Key_Driver () ;void Key_Action (u8 keycode) ;void Key_Scan () ;extern u8 code KeyCodeMap[4 ][4 ];extern u8 key_sate[4 ][4 ];#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 #include "AllHead.h" void Time_Init () { EA = 1 ; TMOD |= 0x01 ; TH0 = 0xFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 #ifndef __TIME_H #define __TIME_H void Time_Init () ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "AllHead.h" void main () { ENLED = 0 ; ADDR3 = 1 ; Time_Init(); SMG_BUFF[0 ] = gsmg[0 ]; while (1 ) { Key_Driver(); } } void time0 () interrupt 1{ TH0 = 0xFC ; TL0 = 0x66 ; SMG_Scan(); Key_Scan(); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8;typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "key.h" #include "smg.h" #include "time.h" #endif
按键1实现流水灯,按键2数码管显示0到99,按键3数码管显示时钟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;#define LED_SMG_PORT P0 sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit KeyOut1 = P2 ^ 3 ; sbit KeyIn1 = P2 ^ 4 ; sbit KeyIn2 = P2 ^ 5 ; sbit KeyIn3 = P2 ^ 6 ; sbit KeyIn4 = P2 ^ 7 ; u8 code gsmg[10 ] = {0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 }; u8 SMG_BUFF[7 ] = {0XFF , 0XFF , 0XFF , 0XFF , 0XFF , 0XFF , 0xFF }; u8 key_state[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; u8 flag1 = 0 ; u8 flag2 = 0 ; u8 KeyDown_State; void time0_Init () ;void Key_Driver () ;void Key_Test () ;void Key_Scan () ;void SMG_Scan () ;void SMG_show () ;void LED_Contrl () ;void SMG_count () ;void Key_Handler () ;void Parameter_Init () ;void main () { ENLED = 0 ; ADDR3 = 1 ; time0_Init(); while (1 ) { Key_Driver(); Key_Handler(); } } void time0_Init () { EA = 1 ; TMOD |= 0x01 ; TH0 = 0xFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; } void Key_Driver () { u8 i , j; static u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (key_state[i][j] != backup[i][j]) { if (backup[i][j] != 0 ) { Key_Test(); } backup[i][j] = key_state[i][j]; } } } } void Key_Test () { if (0 == KeyIn1) { Parameter_Init(); KeyDown_State = 1 ; } else if (0 == KeyIn2) { Parameter_Init(); KeyDown_State = 2 ; } else if (0 == KeyIn3) { Parameter_Init(); KeyDown_State = 3 ; } } void Key_Handler () { if (1 == KeyDown_State) { if (1 == flag1) { flag1 = 0 ; LED_Contrl(); } } if (2 == KeyDown_State) { if (1 == flag2) { flag2 = 0 ; SMG_count(); } } if (3 == KeyDown_State) { if (1 == flag2) { flag2 = 0 ; SMG_show(); } } } void Key_Scan () { static u8 i = 0 ; static KeyOut = 0 ; static keybuf[4 ][4 ] = {{0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; keybuf[KeyOut][0 ] = (keybuf[KeyOut][0 ] << 1 ) | KeyIn1; keybuf[KeyOut][1 ] = (keybuf[KeyOut][1 ] << 1 ) | KeyIn2; keybuf[KeyOut][2 ] = (keybuf[KeyOut][2 ] << 1 ) | KeyIn3; keybuf[KeyOut][3 ] = (keybuf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == (keybuf[KeyOut][i] & 0x0F )) { key_state[KeyOut][i] = 0 ; } if (0x0F == (keybuf[KeyOut][i] & 0x0F )) { key_state[KeyOut][i] = 1 ; } } KeyOut1 = 0 ; } void SMG_Scan () { static u8 i = 0 ; LED_SMG_PORT = 0xFF ; switch (i) { case 0 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = SMG_BUFF[0 ]; break ; case 1 : ADDR2 = 0 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = SMG_BUFF[1 ]; break ; case 2 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 0 ; i++; LED_SMG_PORT = SMG_BUFF[2 ]; break ; case 3 : ADDR2 = 0 ; ADDR1 = 1 ; ADDR0 = 1 ; i++; LED_SMG_PORT = SMG_BUFF[3 ]; break ; case 4 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 0 ; i++; LED_SMG_PORT = SMG_BUFF[4 ]; break ; case 5 : ADDR2 = 1 ; ADDR1 = 0 ; ADDR0 = 1 ; i++; LED_SMG_PORT = SMG_BUFF[5 ]; break ; case 6 : ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; i = 0 ; LED_SMG_PORT = SMG_BUFF[6 ]; break ; } } void LED_Contrl () { static u8 Led_State = 0x80 ; SMG_BUFF[6 ] = ~Led_State; Led_State >>= 1 ; if (Led_State == 0 ) { Led_State = 0x80 ; } } void SMG_count () { static u8 sec = 0 ; sec++; if (sec >= 100 ) { sec = 0 ; } SMG_BUFF[0 ] = gsmg[sec % 10 ]; SMG_BUFF[1 ] = gsmg[sec / 10 ]; } void SMG_show () { static u8 hour = 15 ; static u8 min = 35 ; static u8 sec = 30 ; SMG_BUFF[0 ] = gsmg[sec % 10 ]; SMG_BUFF[1 ] = gsmg[sec / 10 ]; SMG_BUFF[2 ] = gsmg[min % 10 ]; SMG_BUFF[3 ] = gsmg[min / 10 ]; SMG_BUFF[4 ] = gsmg[hour % 10 ]; SMG_BUFF[5 ] = gsmg[hour / 10 ]; sec++; if (sec >= 60 ) { sec = 0 ; min++; if (min >= 60 ) { min = 0 ; hour++; if (hour >= 24 ) { hour = 0 ; } } } } void Parameter_Init () { u8 i; for (i = 0 ; i < 7 ; i++) { SMG_BUFF[i] = 0xFF ; } } void time0 () interrupt 1{ static u16 count1 = 0 ; static u16 count2 = 0 ; TH0 = 0xFC ; TL0 = 0x66 ; count1++; count2++; if (count1 >= 500 ) { count1 = 0 ; flag1 = 1 ; } if (count2 >= 1000 ) { count2 = 0 ; flag2 = 1 ; } Key_Scan(); SMG_Scan(); }
四个按键控制四个LED亮灭
KEY.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <reg52.h> #include "KEY.h" u8 Key_State[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; u8 KeyDown_State; void Key_Driver () { static u8 i, j; static u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (Key_State[i][j] != backup[i][j]) { if (backup[i][j] != 0 ) { Key_Test(); } backup[i][j] = Key_State[i][j]; } } } } void Key_Test () { if (0 == KeyIn1) { KeyDown_State = 1 ; } else if (0 == KeyIn2) { KeyDown_State = 2 ; } else if (0 == KeyIn3) { KeyDown_State = 3 ; } else if (0 == KeyIn4) { KeyDown_State = 4 ; } } void Key_Handler () { if (1 == KeyDown_State) { KeyDown_State = 0 ; LED2 = !LED2; } else if (2 == KeyDown_State) { KeyDown_State = 0 ; LED3 = !LED3; } else if (3 == KeyDown_State) { KeyDown_State = 0 ; LED4 = !LED4; } else if (4 == KeyDown_State) { KeyDown_State = 0 ; LED5 = !LED5; } } void Key_Scan () { static u8 i = 0 ; static u8 KeyOut = 0 ; static u8 keybuf[4 ][4 ] = {{0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; keybuf[KeyOut][0 ] = (keybuf[KeyOut][0 ] << 1 ) | KeyIn1; keybuf[KeyOut][1 ] = (keybuf[KeyOut][1 ] << 1 ) | KeyIn2; keybuf[KeyOut][2 ] = (keybuf[KeyOut][2 ] << 1 ) | KeyIn3; keybuf[KeyOut][3 ] = (keybuf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 0 ; } else if (0x0F == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 1 ; } } KeyOut1 = 0 ; }
KEY.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef _KEY_H_ #define _KEY_H_ typedef unsigned char u8; sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; sbit KeyOut1 = P2^3 ; sbit LED2 = P0^0 ; sbit LED3 = P0^1 ; sbit LED4 = P0^2 ; sbit LED5 = P0^3 ; void Key_Driver () ;void Key_Scan () ;void Key_Test () ;void Key_Handler () ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <reg52.h> #include "KEY.h" sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { LED2 = 1 ; LED3 = 1 ; LED4 = 1 ; LED5 = 1 ; ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; EA = 1 ; TMOD |= 0x01 ; TH0 = 0xF8 ; TL0 = 0xCD ; ET0 = 1 ; TR0 = 1 ; while (1 ) { Key_Driver(); Key_Handler(); } } void time0 () interrupt 1{ TH0 = 0xF8 ; TL0 = 0xCD ; Key_Scan(); }
使用按键控制秒表的启动与停止
回车键:开启/暂停;Esc:清0
最右边的两个数码管显示毫秒,左边四个数码管显示秒数
smg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include "AllHead.h" u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 SMG_BUFF[6 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; bit Watch_Run = 0 ; bit Watch_Refresh = 1 ; u8 Decimal = 0 ; u16 Integer = 0 ; void Watch_Display () { signed char i; u8 buf[4 ]; SMG_BUFF[0 ] = gsmg[Decimal % 10 ]; SMG_BUFF[1 ] = gsmg[Decimal / 10 ]; buf[0 ] = Integer % 10 ; buf[1 ] = (Integer / 10 ) % 10 ; buf[2 ] = (Integer / 100 ) % 10 ; buf[3 ] = (Integer / 1000 ) % 10 ; for (i = 3 ; i > 0 ; i--) { if (0 == buf[i]) { SMG_BUFF[i + 2 ] = 0xFF ; } else break ; } for (; i >= 0 ; i--) { SMG_BUFF[i + 2 ] = gsmg[buf[i]]; } SMG_BUFF[2 ] &= 0x7F ; } void Watch_Action () { if (Watch_Run) Watch_Run = 0 ; else Watch_Run = 1 ; } void Watch_Reset () { Watch_Run = 0 ; Decimal = 0 ; Integer = 0 ; Watch_Refresh = 1 ; } void Smg_Scan () { static u8 i; LED_SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = SMG_BUFF[i]; if (i < 5 ) i++; else i = 0 ; } void Watch_Count () { if (Watch_Run) { Decimal++; if (Decimal >= 100 ) { Decimal = 0 ; Integer++; if (Integer >= 10000 ) { Integer = 0 ; } } Watch_Refresh = 1 ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef _SMG_H_ #define _SMG_H_ #define LED_SMG_PORT P0 void Watch_Display () ;void Watch_Action () ;void Watch_Reset () ;void Smg_Scan () ;void Watch_Count () ;extern bit Watch_Refresh;#endif
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include "AllHead.h" u8 Key_State[4 ] = {1 , 1 , 1 , 1 }; void Key_Driver () { u8 i = 0 ; static u8 backup[4 ] = {1 , 1 , 1 , 1 }; for (i = 0 ; i < 4 ; i++) { if (backup[i] != Key_State[i]) { if (backup[i] != 0 ) { if (1 == i) { Watch_Reset(); } else if (2 == i) { Watch_Action(); } } backup[i] = Key_State[i]; } } } void Key_Scan () { u8 i = 0 ; static u8 keybuf[4 ] = {0xFF , 0xFF , 0xFF , 0xFF }; keybuf[0 ] = (keybuf[0 ] << 1 ) | KeyIn1; keybuf[1 ] = (keybuf[1 ] << 1 ) | KeyIn2; keybuf[2 ] = (keybuf[2 ] << 1 ) | KeyIn3; keybuf[3 ] = (keybuf[3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == keybuf[i]) { Key_State[i] = 0 ; } else if (0xFF == keybuf[i]) { Key_State[i] = 1 ; } } }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef _KEY_H_ #define _KEY_H_ #define KEY_PORT P2 sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; void Key_Driver () ;void Key_Scan () ;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Config_Time0 (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Config_Time0 (u16 ms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include "AllHead.h" sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 1 ; KEY_PORT = 0xFE ; Config_Time0(2 ); while (1 ) { if (Watch_Refresh) { Watch_Refresh = 0 ; Watch_Display(); } Key_Driver(); } } void time0 () interrupt 1{ static u8 count = 0 ; TH0 = T0RH; TL0 = T0RL; Key_Scan(); Smg_Scan(); count++; if (count >= 5 ) { count = 0 ; Watch_Count(); } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "key.h" #include "time.h" #include "smg.h" #endif
实现按键长按功能
打开开关后,数码管显示数字 0,按向上的按键数字加 1,按向下的按键数字减 1,长按向上按键 1 秒后,数字会持续增加,长按向下按键 1 秒后,数字会持续减小。设定好数字后,按下回车按键,时间就会进行倒计时,当倒计时到 0 的时候,用蜂鸣器和板子上的 8 个 LED 小灯做炸弹效果, 蜂鸣器持续响,LED 小灯全亮
smg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include "AllHead.h" u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 SMG_BUFF[7 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; void SMG_Show (u32 num) { signed char i; u8 buf[6 ]; for (i = 0 ; i < 6 ; i++) { buf[i] = num % 10 ; num /= 10 ; } for (i = 5 ; i > 0 ; i--) { if (0 == buf[i]) { SMG_BUFF[i] = 0xFF ; } else break ; } for (; i >= 0 ; i--) { SMG_BUFF[i] = gsmg[buf[i]]; } } void Smg_Scan () { static u8 i; LED_SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = SMG_BUFF[i]; if (i < 6 ) i++; else i = 0 ; }
smg.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _SMG_H_ #define _SMG_H_ #define LED_SMG_PORT P0 void SMG_Show (u32 num) ;void Smg_Scan () ;extern u8 SMG_BUFF[7 ];#endif
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include "AllHead.h" u8 Key_State[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; u8 code KeyCodeMap[4 ][4 ] = { { 0x31 , 0x32 , 0x33 , 0x26 }, { 0x34 , 0x35 , 0x36 , 0x25 }, { 0x37 , 0x38 , 0x39 , 0x28 }, { 0x30 , 0x1B , 0x0D , 0x27 } }; u32 pdata KeyDownTime[4 ][4 ] = {{0 , 0 , 0 , 0 }, {0 , 0 , 0 , 0 }, {0 , 0 , 0 , 0 }, {0 , 0 , 0 , 0 }}; bit enBuzz = 0 ; bit flag1s = 0 ; bit flagStart = 0 ; u16 CountDown = 0 ; void Key_Action (u8 keycode) { if (0x26 == keycode) { if (CountDown < 9999 ) { CountDown++; SMG_Show(CountDown); } } else if (0x28 == keycode) { if (CountDown > 1 ) { CountDown--; SMG_Show(CountDown); } } else if (0x0D == keycode) { flagStart = 1 ; } else if (0x1B == keycode) { enBuzz = 0 ; SMG_BUFF[6 ] = 0xFF ; flagStart = 0 ; CountDown = 0 ; SMG_Show(CountDown); } } void Key_Driver () { u8 i, j; static u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; static u32 pdata TimeThr[4 ][4 ] = { {1000 , 1000 , 1000 , 1000 }, {1000 , 1000 , 1000 , 1000 }, {1000 , 1000 , 1000 , 1000 }, {1000 , 1000 , 1000 , 1000 } }; for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (backup[i][j] != Key_State[i][j]) { if (backup[i][j] != 0 ) { Key_Action(KeyCodeMap[i][j]); } backup[i][j] = Key_State[i][j]; } if (KeyDownTime[i][j] > 0 ) { if (KeyDownTime[i][j] >= TimeThr[i][j]) { Key_Action(KeyCodeMap[i][j]); TimeThr[i][j] += 200 ; } } else TimeThr[i][j] = 1000 ; } } } void Key_Scan () { u8 i = 0 ; static u8 KeyOut = 0 ; static u8 keybuf[4 ][4 ] = {{0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; keybuf[KeyOut][0 ] = (keybuf[KeyOut][0 ] << 1 ) | KeyIn1; keybuf[KeyOut][1 ] = (keybuf[KeyOut][1 ] << 1 ) | KeyIn2; keybuf[KeyOut][2 ] = (keybuf[KeyOut][2 ] << 1 ) | KeyIn3; keybuf[KeyOut][3 ] = (keybuf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 0 ; KeyDownTime[KeyOut][i] += 4 ; } else if (0x0F == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 1 ; KeyDownTime[KeyOut][i] = 0 ; } } KeyOut++; if (KeyOut >= 4 ) { KeyOut = 0 ; } switch (KeyOut) { case 0 : KeyOut4 = 1 ; KeyOut1 = 0 ; break ; case 1 : KeyOut1 = 1 ; KeyOut2 = 0 ; break ; case 2 : KeyOut2 = 1 ; KeyOut3 = 0 ; break ; case 3 : KeyOut3 = 1 ; KeyOut4 = 0 ; break ; default : break ; } }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef _KEY_H_ #define _KEY_H_ sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; sbit KeyOut1 = P2^3 ; sbit KeyOut2 = P2^2 ; sbit KeyOut3 = P2^1 ; sbit KeyOut4 = P2^0 ; void Key_Action (u8 keycode) ;void Key_Driver () ;void Key_Scan () ;extern u16 CountDown; extern bit enBuzz;extern bit flag1s;extern bit flagStart;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Config_Time0 (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef _KEY_H_ #define _KEY_H_ sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; sbit KeyOut1 = P2^3 ; sbit KeyOut2 = P2^2 ; sbit KeyOut3 = P2^1 ; sbit KeyOut4 = P2^0 ; void Key_Action (u8 keycode) ;void Key_Driver () ;void Key_Scan () ;extern u16 CountDown; extern bit enBuzz;extern bit flag1s;extern bit flagStart;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include "AllHead.h" sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit BUZZ = P1 ^ 6 ; void main () { ENLED = 0 ; ADDR3 = 1 ; Config_Time0(1 ); SMG_Show(0 ); while (1 ) { Key_Driver(); if (flagStart && flag1s) { flag1s = 0 ; if (CountDown > 0 ) { CountDown--; SMG_Show(CountDown); if (0 == CountDown) { enBuzz = 1 ; SMG_BUFF[6 ] = 0x00 ; } } } } } void time0 () interrupt 1{ static u16 count = 0 ; TH0 = T0RH; TL0 = T0RL; if (enBuzz) BUZZ = ~BUZZ; else BUZZ = 1 ; if (flagStart) { count++; if (count >= 1000 ) { count = 0 ; flag1s = 1 ; } } else flag1s = 0 ; Key_Scan(); Smg_Scan(); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "key.h" #include "time.h" #include "smg.h" #endif
步进电机
步进电机是将电脉冲信号转变为角位移或线位移的开环控制元件。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,即给电机加一个脉冲信号,电机则转过一个步距角。这一线性关系的存在,加上步进电机只有周期性的误差而无累计误差等特点
工作原理
通常步进电机的转子为永磁体,当电流流过定子绕组时,定子绕组产生一矢量磁场。输入一个电脉冲,电动机转动一个角度前进一步。它输出的角位移与输入的脉冲 数成正比、转速与脉冲频率成正比。改变绕组通电的顺序,电机就会反转。所以可以控制脉冲数量、频率及电动机各相绕组的通电顺序来控制步进电机的转动。 具体看下图:
步进电机工作原理:当定子的矢量磁场旋转一个角度。转子也随着该磁场转步距角。每输入一个电脉冲 ,电动机转动一个角度前进一步。它输出的角位移与输入的脉冲数成正比、转速与脉冲频率成正比。改变绕组通电的顺序,电机就会反转 。所以可以控制脉冲数量、频率及电动机各相绕维组的通电顺序来控制步进电机的转动
28BYJ-48 步进电机简介
28——步进电机的有效最大外径是 28 毫米
B——表示是步进电机
Y——表示是永磁式
J——表示是减速型
48——表示四相八拍
28BYJ48 步进电机自带减速器,为四相无线步进电机,直径为 28mm,实物如下所示:
28BYJ48 电机内部结构等效图如下所示:
步进电机一共有 5 根引线,其中红色的是公共端,连接到 5V 电源,接下来的橙、黄、粉、蓝就对应了 A、B、C、D 相;如果要导通 A 相绕组,就只需将橙色线接地即可,B 相则黄色接地,依此类推
绕组控制顺序表:(必须按顺序不能跳过哪步否则电机不会被磁力吸附)
橙、黄、粉、蓝 --- A、B、C、D
A --> AB --> B --> BC --> C --> CD --> D --> DA
28BYJ48 步进电机旋转驱动方式如下表:
28BYJ48 步进电机实际上是:减速齿轮+步进电机组成
原理图
板子需要把 跳线帽 跳到左边那列(J13~J16),可以使用 P1.0 到 P1.3 控制步 进电机了,如要再使用显示部分的话,就要再换回到右侧
问:如果大家既想让显示部分正常工作,又想让电机工作该怎么办呢?
跳线帽保持在右侧,用杜邦线把步进电机的控制引脚 (即左侧的排针)连接到其它的暂不使用的单片机 IO 上即可
单片机的 IO 口可以直接输出 0V 和 5V 的电压,但是电流驱动能力,也就是带载能力非常有限,所以我们在每相的控制线上都增加一个三极管来提高驱动能力。由图中可以看出,若要使 A 相导通,则必须是 Q2 导通,此时 A 相也就是橙色线就相当于接地了,于是 A 相绕组导通,此时单片机 P1 口低 4 位应输出 0b1110,即 0xE;如要 A、B 相同时导通,那么就是 Q2、Q3 导通,P1 口低 4 位应输出 0b1100,即 0xC,依此类推,可以得到下面的八拍节拍的 IO 控制代码数组:
1 unsigned char code BeatCode[8 ] = { 0x0E , 0x0C , 0x0D , 0x09 , 0x0B , 0x03 , 0x07 , 0x06 };
28BYJ48 步进电机主要参数如下所示:
表中给出的参数是≥550,单位是 P.P.S,即每秒脉冲数,这里的意思就是说:电机保证 在你每秒给出 550 个步进脉冲的情况下,可以正常启动。那么换算成单节拍持续时间就是 1s/550=1.8ms,为了让电机能够启动,我们控制节拍刷新时间大于 1.8ms 就可以了
问:八拍模式时,步进电机转过一圈是需要 64 个节拍,而我们程序中是每个节拍持续 2ms,那么转一圈就应该是 128ms,即 1 秒钟转 7 圈多,可怎么看上去它好像是 7 秒多才转了一圈呢?
原因在于 “减速” 上,位于最中心的那个白色小齿轮才是步进电机的转子输出,64 个节拍只是让这个小齿轮转了一圈,然后它带动那个浅蓝色的大齿轮,这就 是一级减速,每 2 个齿轮都构成一级减速,一共就有了 4 级减速;电机参数表中的减速比是1:64,转子转 64 圈,最终输出轴才会 转一圈,也就是需要 64*64=4096 个节拍输出轴才转过一圈,2ms*4096=8192ms,8 秒多才转 一圈;4096 个节拍转动一圈,那么一个节拍转动的角度——步进角度就是 360/4096,看一下表中的步进角度参数 5.625/64,算一下就知道这两个值是相等的
问:厂家的参数为什么会有误差呢?
28BYJ-48 最初的设计目的是用来控制空调的扇叶的,扇叶的活动范围是不会超过180度的在这种应用场合下,厂商给出一个近似的整数减速比 1:64 已经足够精确了,这也是合情合理的;我们不一定是要用它来驱动空调扇叶,我们可以让它转动很多圈来干别的
程序
这里只用到了 P1 中的低4位,养成好习惯把整个 P1 先赋给一个变量,然后不影响到其他位的状态
中断转动任意角度
运行后会发现角度并不精确,这是因为真实准确的减速比并不是这个值 1:64,而是 1:63.684,所以把程序里 4096 改成4076 会发现误差小很多了
StartMotor 函数中对 EA 的两次操作是因为:STC89C52 单片机是 8 位单片机(即按一个字节进行的),要操作 多个字节(不论是读还是写)就必须分多次进行了; beats 这个变量是 unsigned long 型,它要占用 4 个字节,那么对它的赋值最少也要分 4 次才能完成;所以执行前先关闭了中断,而等它执行完后,才又重新打开了中断。在它执行过程中单片机是不会响应中断的,即中断函数 time0 不会被执行,即使这时候定时器溢出了,中断发生了,也只能等待 EA 重新置 1 后,才能得到响应,中断函数 time0 才会被执行
注意需要把跳线帽跳到左边那列(J13~J16)
步进电机基础转动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <reg52.h> typedef unsigned char u8; typedef unsigned long u32;#define MOTOR_PORT P1 u32 beats = 0 ; void Time0_Init () ; void StartMotor (u32 angle) ;void main () { Time0_Init(); StartMotor(360 * 2 + 180 ); while (1 ); } void Time0_Init () { EA = 1 ; TMOD |= 0x01 ; TH0 = 0xF8 ; TL0 = 0xCD ; ET0 = 1 ; TR0 = 1 ; } void StartMotor (u32 angle) { EA = 0 ; beats = (angle * 4076 ) / 360 ; EA = 1 ; } void time0 () interrupt 1{ u8 temp; static u8 index = 0 ; u8 code BeatCode[8 ] = {0x0E , 0x0C , 0x0D , 0x09 , 0x0B , 0x03 , 0x07 , 0x06 }; TH0 = 0xF8 ; TL0 = 0xCD ; if (beats != 0 ) { temp = MOTOR_PORT; temp &= 0xF0 ; temp |= BeatCode[index]; MOTOR_PORT = temp; index++; index &= 0x07 ; beats--; } else MOTOR_PORT |= 0x0F ; }
按键控制步进电机
1~9键:是转动圈数,上下键:正转和反转,左右键:+90度和-90度
中断函数用一个静态bit变量实现二分频,即2ms定时,用于控制电机
Motor.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include "AllHead.h" signed long beats = 0 ; void StartMotor (signed long angle) { EA = 0 ; beats = (angle * 4076 ) / 360 ; EA = 1 ; } void StopMotor () { EA = 0 ; beats = 0 ; EA = 1 ; } void TurnMotor () { u8 temp; static u8 index = 0 ; u8 code BeatCode[8 ] = {0x0E , 0x0C , 0x0D , 0x09 , 0x0B , 0x03 , 0x07 , 0x06 }; if (beats != 0 ) { if (beats > 0 ) { index++; index &= 0x07 ; beats--; } else { index--; index &= 0x07 ; beats++; } temp = MOTOR_PORT; temp &= 0xF0 ; temp |= BeatCode[index]; MOTOR_PORT = temp; } else { MOTOR_PORT |= 0x0F ; } }
Motor.h
1 2 3 4 5 6 7 8 9 10 #ifndef _MOTOR_H_ #define _MOTOR_H_ #define MOTOR_PORT P1 void StartMotor (signed long angle) ;void StopMotor () ;void TurnMotor () ;#endif
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include "AllHead.h" u8 code KeyCodeMap[4 ][4 ] = { { 0x31 , 0x32 , 0x33 , 0x26 }, { 0x34 , 0x35 , 0x36 , 0x25 }, { 0x37 , 0x38 , 0x39 , 0x28 }, { 0x30 , 0x1B , 0x0D , 0x27 } }; u8 Key_State[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; void Key_Driver () { u8 i, j; static u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (backup[i][j] != Key_State[i][j]) { if (backup[i][j] != 0 ) { Key_Action(KeyCodeMap[i][j]); } backup[i][j] = Key_State[i][j]; } } } } void Key_Action (u8 keycode) { static bit dirMotor = 0 ; if ((keycode >= 0x30 ) && (keycode <= 0x39 )) { if (0 == dirMotor) StartMotor(360 * (keycode - 0x30 )); else StartMotor(-360 * (keycode - 0x30 )); } else if (0x26 == keycode) { dirMotor = 0 ; } else if (0x28 == keycode) { dirMotor = 1 ; } else if (0x25 == keycode) { StartMotor(90 ); } else if (0x27 == keycode) { StartMotor(-90 ); } else if (0x1B == keycode) { StopMotor(); } } void Key_Scan () { u8 i = 0 ; static u8 KeyOut = 0 ; static u8 keybuf[4 ][4 ] = {{0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; keybuf[KeyOut][0 ] = (keybuf[KeyOut][0 ] << 1 ) | KeyIn1; keybuf[KeyOut][1 ] = (keybuf[KeyOut][1 ] << 1 ) | KeyIn2; keybuf[KeyOut][2 ] = (keybuf[KeyOut][2 ] << 1 ) | KeyIn3; keybuf[KeyOut][3 ] = (keybuf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 0 ; } else if (0x0F == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 1 ; } } KeyOut++; if (KeyOut >= 4 ) { KeyOut = 0 ; } switch (KeyOut) { case 0 : KeyOut4 = 1 ; KeyOut1 = 0 ; break ; case 1 : KeyOut1 = 1 ; KeyOut2 = 0 ; break ; case 2 : KeyOut2 = 1 ; KeyOut3 = 0 ; break ; case 3 : KeyOut3 = 1 ; KeyOut4 = 0 ; break ; default : break ; } }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef _KEY_H_ #define _KEY_H_ sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; sbit KeyOut1 = P2^3 ; sbit KeyOut2 = P2^2 ; sbit KeyOut3 = P2^1 ; sbit KeyOut4 = P2^0 ; void Key_Driver () ; void Key_Action (u8 keycode) ;void Key_Scan () ;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 #include "AllHead.h" void Time0_Init () { EA = 1 ; TMOD |= 0x01 ; TH0 = 0xFC ; TL0 = 0x66 ; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 #ifndef _TIME_H_ #define _TIME_H_ void Time0_Init () ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include "AllHead.h" void main () { Time0_Init(); while (1 ) { Key_Driver(); } } void time0 () interrupt 1{ static bit div = 0 ; TH0 = 0xFC ; TL0 = 0x66 ; Key_Scan(); div = ~div; if (1 == div) { TurnMotor(); } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; #include <reg52.h> #include "key.h" #include "time.h" #include "Motor.h" #endif
蜂鸣器
蜂鸣器从结构区分分为压电式蜂鸣器和电磁式蜂鸣器。压电式为压电陶瓷片发音,电流 比较小一些,电磁式蜂鸣器为线圈通电震动发音,体积比较小
按照驱动方式分为有源蜂鸣器和无源蜂鸣器。这里的有源和无源不是指电源,而是振荡源。有源蜂鸣器内部带了振荡源,给了 BUZZ 引脚一个低电平,蜂鸣器就会直接响。而无源蜂鸣器内部是不带振荡源的,要让他响必须给 500Hz~4.5KHz 之间的脉冲频率信号来驱动它才会响
原理图
蜂鸣器电流依然相对较大,因此需要用三极管驱动,并且加了一个 100 欧的电阻作为限流电阻。此外还加了一个 D4 二极管,这个二极管叫做续流二极管
1 2 3 4 reload = 65536 - (11059200 /12 )/(frequ*2 ); T0RH = (unsigned char )(reload >> 8 ); T0RL = (unsigned char )reload;
程序
蜂鸣器演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;sbit BUZZ = P1 ^ 6 ; u8 T0RH = 0 ; u8 T0RL = 0 ; void Open_BUZZ (u16 frequ) ;void Stop_BUZZ () ;void main () { u16 i; EA = 1 ; TMOD |= 0x01 ; while (1 ) { Open_BUZZ(4000 ); for (i = 0 ; i < 40000 ; i++); Stop_BUZZ(); for (i = 0 ; i < 40000 ; i++); Open_BUZZ(1000 ); for (i = 0 ; i < 40000 ; i++); Stop_BUZZ(); for (i = 0 ; i < 40000 ; i++); } } void Open_BUZZ (u16 frequ) { u16 reload; reload = 65535 - (11059200 / 12 ) / (frequ * 2 ); T0RH = (unsigned char )(reload >> 8 ); T0RL = (unsigned char )reload; TH0 = 0xFF ; TL0 = 0xFE ; ET0 = 1 ; TR0 = 1 ; } void Stop_BUZZ () { ET0 = 0 ; TR0 = 0 ; } void time0 () interrupt 1{ TH0 = T0RH; TL0 = T0RL; BUZZ = ~BUZZ; }
门铃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <reg52.h> typedef unsigned int u16; typedef unsigned char u8;sbit BUZZ = P1 ^ 6 ; u8 ding, dong, flag, stop; u16 count; void Time0_Tnit () ; void Mark_Init () ;void main () { Time0_Tnit(); Mark_Init(); while (1 ); } void Time0_Tnit () { TMOD = 0X01 ; TH0 = 0Xff ; TL0 = 0X06 ; EA = 1 ; ET0 = 1 ; TR0 = 1 ; } void Mark_Init () { ding = 0 ; dong = 0 ; count = 0 ; flag = 0 ; stop = 0 ; } void time0 () interrupt 1{ count++; TH0 = 0Xff ; TL0 = 0X06 ; if (count == 2000 ) { count = 0 ; if (flag == 0 ) { flag = ~flag; } else { flag = 0 ; stop = 1 ; } } if (flag == 0 ) { ding++; if (ding == 1 ) { ding = 0 ; BUZZ = ~BUZZ; } } else { dong++; if (dong == 2 ) { dong = 0 ; BUZZ = ~BUZZ; } } }
PWM
PWM 又叫脉冲宽度调制,它利用微处理器的数字输出来对模拟电路进行控制的一种有效的技术,其实就是使用数字信号达到一个模拟信号的效果。脉冲宽度调制,就是改变脉冲宽度来实现不同的效果
这是一个周期是 10ms,即频率是 100Hz 的波形,但是每个周期内,高低电平脉冲宽度各不相同,这就是 PWM 的本质
占空比是指高电平的时间占整个周期的比例。比如第一部分波形的占空比是 40%,第二部分波形占空比是 60%,第三部分波形占空比是 80%,这就是 PWM 的解释
1 2 3 周期与频率的关系是互为倒数,周期单位是s,所以需要转换,如下面: 10 ms = 0.01 s --> 1 /0.01 s = 100 Hz100 Hz = 1 / 100 = 0.01 s = 10 ms
程序
呼吸灯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;sbit ADDR0 = P1 ^ 0 ; sbit ADDR1 = P1 ^ 1 ; sbit ADDR2 = P1 ^ 2 ; sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit PWMOUT = P0 ^ 0 ; u32 Period_Count = 0 ; u8 HighRH = 0 ; u8 HighRL = 0 ; u8 LowRH = 0 ; u8 LowRL = 0 ; u8 T1RH = 0 ; u8 T1RL = 0 ; void Config_PWM (u16 freq, u16 duty) ; void Time1_Init (u16 ms) ;void main () { ENLED = 0 ; ADDR3 = 1 ; ADDR2 = 1 ; ADDR1 = 1 ; ADDR0 = 0 ; Config_PWM(100 , 10 ); Config_Time1(50 ); while (1 ); } void Config_PWM (u16 freq, u16 duty) { u16 high, low; Period_Count = (11059200 / 12 ) / freq; high = (Period_Count * duty) / 100 ; low = Period_Count - high; high = 65536 - high + 12 ; low = 65536 - low + 12 ; HighRH = (unsigned char )(high >> 8 ); HighRL = (unsigned char )high; LowRH = (unsigned char )(low >> 8 ); LowRL = (unsigned char )low; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = HighRH; TL0 = HighRL; ET0 = 1 ; TR0 = 1 ; PWMOUT = 1 ; } void Time1_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 12 ; T1RH = (unsigned char )(temp >> 8 ); T1RL = (unsigned char )temp; EA = 1 ; TMOD &= 0x0F ; TMOD |= 0x10 ; TH1 = T1RH; TL1 = T1RL; ET1 = 1 ; TR1 = 1 ; } void Adjust_duty (u16 duty) { u16 high, low; high = (Period_Count * duty) / 100 ; low = Period_Count - high; high = 65536 - high + 12 ; low = 65536 - low + 12 ; HighRH = (unsigned char )(high >> 8 ); HighRL = (unsigned char )high; LowRH = (unsigned char )(low >> 8 ); LowRL = (unsigned char )low; } void time0 () interrupt 1{ if (1 == PWMOUT) { TH0 = LowRH; TL0 = LowRL; PWMOUT = 0 ; } else { TH0 = HighRH; TL0 = HighRL; PWMOUT = 1 ; } } void time1 () interrupt 3{ static bit dir = 0 ; static u8 index = 0 ; u8 code table[13 ] = {5 , 18 , 30 , 41 , 51 , 60 , 68 , 75 , 81 , 86 , 90 , 93 , 95 }; TH1 = T1RH; TL1 = T1RL; Adjust_duty(table[index]); if (0 == dir) { index++; if (index >= 12 ) { dir = 1 ; } } else { index--; if (0 == index) { dir = 0 ; } } }
UART 串口通信
UART 即通用异步收发器,串行通信通常用于单片机和电脑之间以及单片机和单片机之间的通信
串口通信相关术语
通信的方式可以分为多种,按照数据传送方式可分为串行通信和并行通信。按照通信的数据同步方式,可分为异同通信和同步通信。按照数据的传输方向又可分为单工、半双工和全双工通信。
1 2 3 4 5 6 全双工:通信双方可以在同一时刻互相传输数据 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线 单工:通信只能有一方发送到另一方,不能反向传输 异步:通信双方各自约定通信速率 同步:通信双方靠一根时钟线来约定通信速率 总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)
STC89C52 有两个引脚是专门用来做 UART 串行通信的,一个是 P3.0 一个是 P3.1,它们 还分别有另外的名字叫做 RXD 和 TXD,由它们组成的通信接口就叫做串行接口,简称串口。 用两个单片机进行 UART 串口通信
GND 表示单片机系统电源的参考地,TXD 是串行发送引脚,RXD 是串行接收引脚。两个单片机之间要通信,首先电源基准得一样
(1)串行通信
串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。其只需要少数几条线就可以在系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的远距离通信。如下图所示:
串行通信的特点:传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,但数据的传送控制比并行通信复杂。
(2)并行通信
并行通信通常是将数据字节的各位用多条数据线同时进行传送,通常是 8 位、16 位、32 位等数据一起传输。如下图所示:
并行通信的特点:控制简单、传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接收存在困难,抗干扰能力差。
(3)异步通信
异步通信是指通信的发送与接收设备使用各自的时钟控制数据的发送和接收过程。为使双方的收发协调,要求发送和接收设备的时钟尽可能一致。
异步通信是以字符(构成的帧)为单位进行传输,字符与字符之间的间隙(时间间隔)是任意的,但每个字符中的各位是以固定的时间传送的,即字符之间不 一定有“位间隔”的整数倍的关系,但同一字符内的各位之间的距离均为“ 位间隔”的整数倍。如下图所示:
异步通信的特点:不要求收发双方时钟的严格一致,实现容易,设备开销较小,但每个字符要附加 2~3 位用于起止位,各帧之间还有间隔,因此传输效率不高。
(4)同步通信
同步通信时要建立发送方时钟对接收方时钟的直接控制,使双方达到完全同步。此时,传输数据的位之间的距离均为“位间隔”的整数倍,同时传送的字符间不留间隙,即保持位同步关系,也保持字符同步关系。发送方对接收方的同步可以通过两种方法实现。如下图所示:
(5)单工通信
单工是指数据传输仅能沿一个方向,不能实现反向传输。如下图所示:
(6)半双工通信
半双工是指数据传输可以沿两个方向,但需要分时进行。如下图所示:
(7)全双工通信
全双工是指数据可以同时进行双向传输。如下图所示:
(8)通信速率
比特率是 每秒钟传输二进制代码的位数,单位是:位/秒( bps)。如每秒钟传送 240 个字符,而每个字符格式包含 10 位(1 个起始位、1 个停止位、8 个数据位),这时的比特率为:
10 位 × 240 个/秒 = 2400 b p s 10 \text{位} ×240 \text{个/秒} = 2400 bps
10 位 × 240 个 / 秒 = 2400 b p s
波特率: 它表示每秒钟传输了多少个码元。通信中常用时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。用 0V 表示数字 0,5V 表示数字 1,那么 一个码元可以表示两种状态 0 和 1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;如果在通信传输中,有 0V、 2V、4V 以及 6V 分别表示二进制数 00、 01、 10、 11,那么每个码元可以表示四种状态,即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。由于很多常见的通信中一个码元都是表示两种状态,所以我们常常直接以 波特率来表示比特率。
波特率就是发送二进制数据位的速率,习惯上用 baud 表示,即我们发送一位二进制数据的持续时间 = 1/baud。在通信之前,单片机 1 和单片机 2 首先都要明确的约定好它们之间的通信波特率,必须保持一致,收发双方才能正常实现通信
问:约定好速度后,我们还要考虑第二个问题,数据什么时候是起始,什么时候是结束呢?
不管是提前接收还是延迟接收,数据都会接收错误。在 UART 通信的时候, 一个字节是 8 位, 规定 当没有通信信号发生时,通信线路保持高电平,当要 发送数据之前,先发一位 0 表示起始位,然后 发送 8 位数据位,数据位是 先低后高的顺序,数据位发完后 再发一位 1 表示停止位,加起来一共发送 10位;而接收方呢,原本一直保持的高电平, 一旦检测到了一位低电平,那就知道了要 开始准备接收数据了,接收到 8 位数据位后,然后 检测到高电平(停止位),再 准备下一个数据的接收
RS232 通信接口
●RS232 通信接口 :就是台式电脑那些 “9针"和"9孔”(公头/母头)串行接口,虽然RS232也有 “RXD”,“TXD”,“GND”,但是却不能直接和单片机连接,因为它们的电平不相同,不是所有的电路都是 5V 代表高电平而 0V 代表低电平的,对于RS232标准来说,它是 “反逻辑”,即 “低电平代表的是 1,而高电平代表的是 0”,所以需要用一个电平转换芯片 “MAX232” 作为中间人将它们两电平互相转化从而可以互相通信
CH340T芯片
这个芯片就可以实现把标准 RS232 串口电平转换成我们单片机能够识别和承受的 UART 0V/5V 电平。其实 RS232 串口和 UART 串口,它们的协议类型是一样的,只是电平标准不同而已,而 MAX232 这个芯片起到的就是中间人的作用,它把 UART 电平转换成 RS232 电平,也把 RS232 电平转换成 UART 电平,从而实现标准 RS232 接口和单片机 UART 之间的通信连接
IO 口模拟 UART 串口通信
把 P3.0 和 P3.1 当做 IO 口来进行模拟实际串口通信的过程 (了解即可)
串口调试助手的使用
串口调试助手的实质就是利用电脑上的 UART 通信接口,发送数据给我们的单片机,也可以把我们的单片机发送的数据接收到这个调试助手界面上
配置波特率的时候,我们用的是定时器 T0 的模式 2(8位自动装载模式)。模式 2 中,不再是 TH0 代表高 8 位,TL0 代表低 8 位了,而只有 TL0 在进行计数,当 TL0 溢出后,不仅仅会让 TF0 变 1,而且还会将 TH0 中的内容重新自动装到 TL0 中,好处是:可以把想要的定时器初值提前存在 TH0 中,当 TL0 溢出后,TH0 自动把初值就重新送入 TL0 了,全自动的,不需要程序中再给 TL0 重新赋值 了
IO 口模拟 UART 串口通信(串口发送数据则接收该数据+1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include <reg52.h> typedef unsigned char u8;typedef unsigned int u16;sbit PIN_RXD = P3 ^ 0 ; sbit PIN_TXD = P3 ^ 1 ; bit RxdOrTxd = 0 ; bit RxdEnd = 0 ; bit TxdEnd = 0 ; u8 RxdBuf = 0 ; u8 TxdBuf = 0 ; void ConfigUART (u16 baud) ;void StartTXD (u8 dat) ;void StartRXD () ;void main () { EA = 1 ; ConfigUART(9600 ); while (1 ) { while (PIN_RXD); StartRXD(); while (!RxdEnd); StartTXD(RxdBuf + 1 ); while (!TxdEnd); } } void ConfigUART (u16 baud) { TMOD &= 0xF0 ; TMOD |= 0x02 ; TH0 = 256 - (11059200 / 12 ) / baud; } void StartRXD () { TL0 = 256 - ((256 - TH0) >> 1 ); ET0 = 1 ; TR0 = 1 ; RxdEnd = 0 ; RxdOrTxd = 0 ; } void StartTXD (u8 dat) { TxdBuf = dat; TL0 = TH0; ET0 = 1 ; TR0 = 1 ; PIN_TXD = 0 ; TxdEnd = 0 ; RxdOrTxd = 1 ; } void Timer0 () interrupt 1{ static u8 cnt = 0 ; if (RxdOrTxd) { cnt++; if (cnt <= 8 ) { PIN_TXD = TxdBuf & 0x01 ; TxdBuf >>= 1 ; } else if (cnt == 9 ) { PIN_TXD = 1 ; } else { cnt = 0 ; TR0 = 0 ; TxdEnd = 1 ; } } else { if (cnt == 0 ) { if (!PIN_RXD) { RxdBuf = 0 ; cnt++; } else { TR0 = 0 ; } } else if (cnt <= 8 ) { RxdBuf >>= 1 ; if (PIN_RXD) { RxdBuf |= 0x80 ; } cnt++; } else { cnt = 0 ; TR0 = 0 ; if (PIN_RXD) { RxdEnd = 1 ; } } } }
USB 转串口通信
●USB 转串口通信,笔记本跟单片机通信需要在电路上添加一个 “USB 转串口芯片”,就可以成功实现 USB 通信协议和标准UART 串行通信协议的转换
图中左下方 J1 和 J2 是两个跳线的组合,我们需要用跳线帽把中间和下边的针短接在一起。右侧的 CH340T 这个电路,把电源、晶振接好后,6 脚和 7 脚的 DP 和 DM 分别接 USB 口的 2 个数据引脚上去,3 脚和 4 脚通过跳线接到了单片机的 TXD 和 RXD 上去
串口相关寄存器
串口控制寄存器 SCON
SM2 :多机通信控制位,主要用于方式 2 和方式 3。当 SM2=1 时可以利用收到的 RB8 来控制是否激活 RI(RB8=0 时不激活 RI,收到的信息丢弃;RB8=1 时收到的数据进入 SBUF,并激活 RI,进而在中断服务中将数据从 SBUF 读走)。当 SM2=0 时,不论收到的 RB8 为 0 和 1,均可以使收到的数据进入 SBUF,并激活 RI (即此时 RB8 不具有控制 RI 激活的功能)。通过控制 SM2,可以实现多机通信。
REN :允许串行接收位。由软件置 REN=1,则启动串行口接收数据;若软件置REN=0,则禁止接收。
TB8 :在方式 2 或方式 3 中,是发送数据的第 9 位,可以用软件规定其作用。 可以用作数据的奇偶校验位,或在多机通信中,作为地址帧/数据帧的标志位。 在方式 0 和方式 1 中,该位未用到。
RB8 :在方式 2 或方式 3 中,是接收到数据的第 9 位,作为奇偶校验位或地址帧/数据帧的标志位。在方式 1 时,若 SM2=0,则 RB8 是接收到的停止位。
TI :发送中断标志位。在方式 0 时,当串行发送第 8 位数据结束时,或在其它方式,串行发送停止位的开始时,由内部硬件使 TI 置 1,向 CPU 发中断申请。 在中断服务程序中,必须用软件将其清 0,取消此中断申请。
RI :接收中断标志位。在方式 0 时,当串行接收第 8 位数据结束时,或在其它方式,串行接收停止位的中间时,由内部硬件使 RI 置 1,向 CPU 发中断申请。 也必须在中断服务程序中,用软件将其清 0,取消此中断申请。
SM0 和 SM1 为工作方式选择位:
模式1: 1 位起始位,8 位数据位和 1 位停止位
波特率发生器:波特率发生器只能由 定时器 T1 或 定时器 T2 产生,定时器 T1必须使用模式 2,也就是自动重装载模式
电源控制寄存器 PCON
SMOD :波特率倍增位。在串口方式 1、方式 2、方式 3 时,波特率与 SMOD 有 关,当 SMOD=1 时,波特率提高一倍。复位时,SMOD=0。
SBUF寄存器
串口通信的发送和接收电路在物理上有 2 个名字相同的 SBUF 寄存器,它们的地址也都 是 0x99,但是一个用来做 发送缓冲,一个用来做 接收缓冲,每次只操作 SBUF,单片机会自动根据对它执行的是“读”还是“写”操作来选择是接收 SBUF 还是 发送 SBUF
1 2 TH1 = TL1 = 256 - (晶振值 / 12 / 2 / 16 / 波特率)
256 :定时器模式2 , 8 位定时器的溢出值,也就是 TL1 的溢出值
晶振值 :11059200
12 :一个机器周期(STC89C52中一个时钟周期等于12个时钟周期)
16 :串口模块将一位信号采集16次,将其中7,8,9次取出来,如果这三次中两次对如果是高电平就认为这位数据是1.
波特率 :要设定的波特率
1 2 3 4 PCON |= 0x80 TH1 = TL1 = 256 - (晶振值 / 12 / 16 / 波特率)
串口初始化步骤
配置串口为模式 1 (SCON 寄存器)
配置定时器 T1 为模式 2,即自动重装模式(TMOD 寄存器)
根据波特率计算 TH1 和 TL1 的初值,如果有需要可以使用 PCON 进行波特率加倍
打开定时器控制寄存器 TR1,让定时器跑起来
注意 :
在使用 T1 做波特率发生器的时候,千万不要再使能 T1 的 中断
因为接收和发送触发的是同一个串口中断,所以在串口中断函数中就必须先判断是哪种中断,然后再作出相应的处理(UART中断号是4)
程序
串口发送数据则接收到的数据为 发送的数据+1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;void UART_Init (u16 baud) ;void main () { UART_Init(9600 ); while (1 ); } void UART_Init (u16 baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - (11059200 / 12 / 32 ) / baud; TL1 = TH1; EA = 1 ; ES = 1 ; ET1 = 0 ; TR1 = 1 ; } void UART () interrupt 4{ u8 rec_data; if (RI) { RI = 0 ; rec_data = SBUF; SBUF = rec_data + 1 ; } if (TI) { TI = 0 ; } }
单片机串口调试助手发送的数据,在数码管上显示出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#define LED_SMG_PORT P0 sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 SMG_BUFF[6 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; u8 T0RH = 0 ; u8 T0RL = 0 ; u8 RxdByte = 0 ; void Time0_Init (u16 ms) ;void UART_Init (u16 baud) ;void Smg_Scan () ;void main () { ENLED = 0 ; ADDR3 = 1 ; Time0_Init(1 ); UART_Init(9600 ); while (1 ) { SMG_BUFF[0 ] = gsmg[RxdByte & 0x0F ]; SMG_BUFF[1 ] = gsmg[RxdByte >> 4 ]; } } void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; } void UART_Init (u16 baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - (11059200 / 12 / 32 ) / baud; TL1 = TH1; EA = 1 ; ES = 1 ; ET1 = 0 ; TR1 = 1 ; } void Smg_Scan () { static u8 i; LED_SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = SMG_BUFF[i]; if (i < 5 ) i++; else i = 0 ; } void time0 () interrupt 1{ TH0 = T0RH; TL0 = T0RL; Smg_Scan(); } void UART () interrupt 4{ if (RI) { RI = 0 ; RxdByte = SBUF; SBUF = RxdByte; } if (TI) { TI = 0 ; } }
问:用文本格式直接发送一个“12”,串口调试助手返回十六进制显示的是 31、32 两个数据,而数码管显示的是 32,为什么?
对于 ASCII 码表来说,数字本身是字符而非数据,所以如果发送“12”的话,实际上是分别发送了“1”和“2”两个字符,单片机呢,先收到第一个字符“1”,在数码管上会显示出 31 这个对应数字,但是马上就又收到了“2”这个字符,数码管瞬间从 31 变成了 32, 而我们视觉上呢,是没有办法发现这种快速变化的,所以我们感觉数码管直接显示的是 32
接到任意字节数据后改变流水灯流水的方向,并且将数据显示在数码管上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#define LED_SMG_PORT P0 sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 SMG_BUFF[7 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; u8 T0RH = 0 ; u8 T0RL = 0 ; u8 RxdByte = 0 ; u8 flag = 0 ; u8 dirflag = 0 ; void Config_Time0 (u16 ms) ;void UART_Init (u16 baud) ;void Smg_Scan () ;void LED_Fall (u8 dir) ;void main () { ENLED = 0 ; ADDR3 = 1 ; Config_Time0(1 ); UART_Init(9600 ); while (1 ) { if (0 == dirflag) { if (1 == flag) { flag = 0 ; LED_Fall(0 ); } } else if (1 == dirflag) { if (1 == flag) { flag = 0 ; LED_Fall(1 ); } } SMG_BUFF[0 ] = gsmg[RxdByte & 0x0F ]; SMG_BUFF[1 ] = gsmg[RxdByte >> 4 ]; } } void Config_Time0 (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; } void UART_Init (u16 baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - (11059200 / 12 / 32 ) / baud; TL1 = TH1; EA = 1 ; ES = 1 ; ET1 = 0 ; TR1 = 1 ; } void Smg_Scan () { static u8 i; LED_SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = SMG_BUFF[i]; if (i < 6 ) i++; else i = 0 ; } void LED_Fall (u8 dir) { if (0 == dir) { static u8 Led_state = 0x01 ; SMG_BUFF[6 ] = ~Led_state; Led_state <<= 1 ; if (0 == Led_state) { Led_state = 0x01 ; } } else if (1 == dir) { static u8 Led_state = 0x80 ; SMG_BUFF[6 ] = ~Led_state; Led_state >>= 1 ; if (0 == Led_state) { Led_state = 0x80 ; } } } void time0 () interrupt 1{ static u16 count = 0 ; TH0 = T0RH; TL0 = T0RL; Smg_Scan(); count++; if (count >= 1000 ) { count = 0 ; flag = 1 ; } } void UART () interrupt 4{ if (RI) { RI = 0 ; RxdByte = SBUF; SBUF = RxdByte; dirflag = !dirflag; } if (TI) { TI = 0 ; } }
接到任意字节数据后改变流水灯流水的方向,并且将数据显示在数码管上,当接收到大写 “B”,蜂鸣器响(要用文本模式发送才有效)
这个程序就是在上面代码的基础上加,先定义一个flagBuzz控制蜂鸣器状态的(0表示关),在串口中断那里,当接收到数据为" B "时,flagBuzz置1,在定时器0那一直扫描,当flagBuzz == 1 时,蜂鸣器取反(响),当flagBuzz == 0 时,蜂鸣器不响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#define LED_SMG_PORT P0 sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; sbit BUZZ = P1 ^ 6 ; u8 gsmg[10 ] = {0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 }; u8 SMG_BUFF[7 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; u8 T0RH = 0 ; u8 T0RL = 0 ; u8 RxdByte = 0 ; u8 flag = 0 ; u8 dirflag = 0 ; u8 flagBuzz = 0 ; void Config_Time0 (u16 ms) ;void UART_Init (u16 baud) ;void Smg_Scan () ;void LED_Fall (u8 dir) ;void main () { ENLED = 0 ; ADDR3 = 1 ; Config_Time0(1 ); UART_Init(9600 ); while (1 ) { if (0 == dirflag) { if (1 == flag) { flag = 0 ; LED_Fall(0 ); } } else if (1 == dirflag) { if (1 == flag) { flag = 0 ; LED_Fall(1 ); } } SMG_BUFF[0 ] = gsmg[RxdByte & 0x0F ]; SMG_BUFF[1 ] = gsmg[RxdByte >> 4 ]; } } void Config_Time0 (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; } void UART_Init (u16 baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - (11059200 / 12 / 32 ) / baud; TL1 = TH1; EA = 1 ; ES = 1 ; ET1 = 0 ; TR1 = 1 ; } void Smg_Scan () { static u8 i; LED_SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = SMG_BUFF[i]; if (i < 6 ) i++; else i = 0 ; } void LED_Fall (u8 dir) { if (0 == dir) { static u8 Led_state = 0x01 ; SMG_BUFF[6 ] = ~Led_state; Led_state <<= 1 ; if (0 == Led_state) { Led_state = 0x01 ; } } else if (1 == dir) { static u8 Led_state = 0x80 ; SMG_BUFF[6 ] = ~Led_state; Led_state >>= 1 ; if (0 == Led_state) { Led_state = 0x80 ; } } } void time0 () interrupt 1{ static u16 count = 0 ; TH0 = T0RH; TL0 = T0RL; Smg_Scan(); count++; if (count >= 1000 ) { count = 0 ; flag = 1 ; } if (1 == flagBuzz) { BUZZ = ~BUZZ; } else BUZZ = 1 ; } void UART () interrupt 4{ if (RI) { RI = 0 ; RxdByte = SBUF; SBUF = RxdByte; dirflag = !dirflag; if ('B' == RxdByte) { flagBuzz = 1 ; } else flagBuzz = 0 ; } if (TI) { TI = 0 ; } }
接收上位机下发的命令,根据命令值分别把不同数组的数据回发给上位机
程序用到了指针的自增运算,也就是+1 运算,还有sizeof()
这个程序还应用到一个小技巧,前边讲了串口发送中断标志位 TI 是硬件置位,软件清零的。如果我们想一次发送多个数据的时候,就需要把第一个字节写入 SBUF,然后再等待发送中断,在后续中断中再发送剩余的数据,这样我们的数据发送过程就被拆分到了两个地方——主循环内和中断服务函数内,无疑就使得程序结构变得零散了。所以我们可以改成在启动发送的时候,不是向 SBUF 中写入第一个待发的字节,而是直接让 TI = 1,这时候会马上进入串口中断,因为中断标志位置 1 了,但是串口线上并没有发送任何数据,于是,所有的数据发送都可以在中断中进行,而不用再分为两部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <reg52.h> typedef unsigned char u8; bit cmdArrived = 0 ; u8 cmdIndex = 0 ; u8 cntTxd = 0 ; u8 *ptrTxd; u8 array1[1 ] = {1 }; u8 array2[2 ] = {1 , 2 }; u8 array3[4 ] = {1 , 2 , 3 , 4 }; u8 array4[8 ] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 }; void UART_Init (u16 baud) ;void main () { UART_Init(9600 ); while (1 ) { if (cmdArrived) { cmdArrived = 0 ; switch (cmdIndex) { case 1 : ptrTxd = array1; cntTxd = sizeof (array1); TI = 1 ; break ; case 2 : ptrTxd = array2; cntTxd = sizeof (array2); TI = 1 ; break ; case 3 : ptrTxd = array3; cntTxd = sizeof (array3); TI = 1 ; break ; case 4 : ptrTxd = array4; cntTxd = sizeof (array4); TI = 1 ; break ; default : break ; } } } } void UART_Init (u16 baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - (11059200 / 12 / 32 ) / baud; TL1 = TH1; EA = 1 ; ES = 1 ; ET1 = 0 ; TR1 = 1 ; } void UART () interrupt 4{ if (RI) { RI = 0 ; cmdIndex = SBUF; cmdArrived = 1 ; } if (TI) { TI = 0 ; if (cntTxd > 0 ) { SBUF = *ptrTxd; cntTxd--; ptrTxd++; } } }
1602液晶
数据手册重要内容
1602 液晶可以显示 2 行,每行 16 个 字符,这个 2mA 仅仅是指液晶,而它的黄绿背光都是用 LED 做的,所以功耗 不会太小的, 一二十毫安 还是有的
1602 液晶引脚功能
编号
符号
引脚说明
编号
符号
引脚说明
1
VSS
电源地
9
D2
Data I/O
2
VDD
电源正极
10
D3
Data I/O
3
VL
液晶显示偏压信号
11
D4
Data I/O
4
RS
数据/命令选择端(H/L)
12
D5
Data I/O
5
R/W
读/写选择端(H/L)
13
D6
Data I/O
6
E
使能信号
14
D7
Data I/O
7
D0
Data I/O
15
BLA
背光源正极
8
D1
Data I/O
16
BLK
背光源负极
● 液晶的电源 1 脚 2 脚以及背光电源 15 脚 16 脚,正常接即可
● 3 脚叫做液晶显示偏压信号,调整显示的黑点和不显示的之间的对比度,调好就清晰多了
● 4 脚是数据命令选择端 (这个引脚接到了 ADDR0 上,通过跳线帽和 P1.0 连接);高电平是数据,低电平是命令
● 5 脚是读写选择端 (这个引脚接到了 ADDR1 上,通过跳线帽和 P1.1 连接);高电平是读,低电平是写
● 6 脚是使能信号 (很重要!),液晶的读写命令和数据,都要靠它才能正常读写,这个引脚通过跳线帽接到了 ENLCD 上 (P1.5管脚)
● 7 到 14 引脚就是 8 个数据引脚了,通过这 8 个引脚读写数据和命令 (接到了P0口)
1602原理图
1602 液晶的读写时序
1602 内部 RAM 结构图
第一行的地址是 0x00H 到 0x27,第二行的地址从 0x40 到 0x67,其中第一行 0x00 到 0x0F 是与液晶上第一行 16 个字符显示位置相对应的,第二行 0x40 到 0x4F 是与第二行 16 个字符显示位置相对应的。而每行都多出来一部分,是为了显示移动字幕设置的
1602 液晶状态字
液晶有一个状态字字节,通过读取这个状态字的内容,可以知道 1602 液晶的一些内部情况
这个状态字节有 8 个位,最高位表示了当前液晶是不是忙,如果这个位是 1 表示液 晶正“忙”,禁止读写数据或者命令,如果是 0,则可以进行读写。而低 7 位就表示了当前数据地址指针的位置
1602 的基本操作时序,一共有 4 个(单片机读外部状态前,必须先保证自己是高电平)
首先把用到的总线接口做一个统一声明
1 2 3 4 #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ;
读状态:RS = L,R/W = H,E = H
因为P0口总线也是流水灯数码管等等共用的,读取后如果一直是高电平会影响其他外设,所以通常要把这个引脚拉低来释放总线,这里用了一个 do…while 循环语句来实现(判断while是否为1,为1则执行上面的语句,不为1则退出循环)
通过判断 sta 最高位的值来了解当前液晶是否处于忙状态,也可以得知当前数据的指针位置,如果当前读到的状态是不忙,那么程序可以进行读写操作,如果当前状态是忙,那么还得继续等待重新判断液晶的状态
1 2 3 4 5 6 7 8 LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; }while (sta & 0x80 );
读数据:RS = H,R/W = H,E = H (不常用)
写指令:RS = L,R/W = L,D0~D7 = 指令码,E = 高脉冲
这个指令一共有 4 条语句,其中前三条语句顺序无所谓,但是 E = 高脉冲这一句很关键。E = 高脉冲,意思就是:E 使能引脚先从低拉高,再从高拉低,形成一个高脉冲
写数据:RS = H,R/W = L,D0~D7 = 数据,E = 高脉冲
写数据和写指令是类似的,就是把 RS 改成 H,把总线改成数据即可
注:这里用的1602液晶所使用的接口时序是摩托罗拉公司所创立的 6800时序 ,还有另外一种时序是 Intel 公司的 8080时序,也有部分液晶模块采用,只是相对来说比较少见
1602 液晶的使能引脚 E, 高电平的时候是 有效,低电平的时候是 无效,前面也提到了高电平时会影响 P0 口,因此正常情况下,如果我们没有使用液晶的话,那么程序开始写一句 LCD1602_E=0,就可以避免1602 干扰到其它外设。之前的程序没有加这句,是因为板子在这个引脚上加了一个 15K 的下拉电阻,这个下拉电阻就可以保证这个引脚上电后默认是 低电平(但是在实际开发过程中,就不必要这样了。如果这是个实际产品,能用软件去处理的,我们就不会用硬件去实现)
1602 液晶的指令
显示模式设置
写指令 0x38,设置 16x2 显示,5x7 点阵,8 位数据接口。这条指令对这个1602液晶来说是固定的,必须写 0x38(仔细看会发现我们的液晶实际上内部点阵是 5x8 的)
显示开/关以及光标设置指令
这里有 2 条指令,第一条指令,一个字节中 8 位,其中高 5 位是固定的 0b00001,低 3位分别用 DCB 从高到低表示, D=1 表示开显示, D=0 表示关显示; C=1 表示显示光标, C=0 表示不显示光标; B=1 表示光标闪烁, B=0 表示光标不闪烁;第二条指令,高 6 位是固定的 0b000001,低 2 位分别用 NS 从高到低表示,其中 N=1 表示读或者写一个字符后,指针自动加 1,光标自动加 1, N=0 表示读或者写一个字符 后指针自动减 1,光标自动减 1; S=1 表示写一个字符后,整屏显示左移(N=1)或右移(N=0),以达到光标不移动而屏幕移动的效果,而 S=0 表示写一 个字符后,整屏显示不移动
数据控制
清屏指令
写入 0x01 表示显示清屏,其中包含了数据指针清零,所有的显示清零。写入 0x02 则仅仅是数据指针清零,显示不清零
RAM 地址设置指令
该指令码的 最高位为 1, 低 7 位为 RAM 的地址,RAM 地址与液晶上字符的关系如上映射图所示。通常,我们在读写数据之前都要先设置好地址,然后再进行数据的读写操作
通信时序解析
所谓“时序”从字面意义上来理解,一是 时间问题,二是 顺序问题
读操作时序
RS 引脚和 R/W 引脚,这两个引脚先进行变化,因为是读操作,所以 R/W 引脚首先要置为高电平,而不管它原来是什么。(读指令还是读数据,都是读操作,而且都有可能,所以 RS 引脚既有可能是置为高电平,也有可能是置为低电平),而 RS 和 R/W 变化了经过 Tsp1 这么长时间后,使能引脚 E 才能从低电平到高电平发生变化,经过了 tD 这么长时间后,LCD1602 输出 DB 的数据就是有效数据了(就可以来读取 DB 的数据了),读完了之后,我们要先把使能 E 拉低,经过一段时间后 RS、R/W 和 DB 才可以变化继续为下一次读写做准备了
写操作时序
写操作时序和读操作时序的差别,就是写操作时序中,DB 的改变是由单片机来完成的,因此要放到使能引脚 E 的变化之前进行操作
注意时间轴,如果没有标明(其实大部分也都是不标明的),那么从左往右的方向为时间正向轴,即时间在增长
上图框出并注明了看懂此图的一些常识:
(1) 时序图最左边一般是某一根引脚的标识,表示此行图线体现该引脚的变化,上图分别标明了RS、R/W、E、DB0~DB7四类引脚的时序变化
(2) 有线交叉状的部分,表示电平在变化,如上所标注
(3) 两条平行线分别对应高低电平,也正好吻合(2)中电平变化的说法
(4) 上图中,密封的菱形部分,注意要密封,表示数据有效,Valid Data这个词也显示了这点
需要十分注意的是,时序图里各个引脚的电平变化,基于的时间轴是一致的。一定要严格按照时间轴的增长方向来精确地观察时序图。要让器件严格的遵守时序图的变化。在类似于18B20这样的单总线器件对此要求尤为严格
以上几点,并不是LCD1602的时序图所特有的,绝大部分的时序图都遵循着这样的一般规则,所以要慢慢的习惯于这样的规则
时序图时间标签
tC :指的是使能引脚 E 从本次上升沿到下次上升沿的最短时间是 400ns
tPW :指的是使能引脚 E 高电平的持续时间最短是 150ns
tR tF :指的是使能引脚 E 的上升沿时间和下降沿时间,不能超过 25ns
tSP1 :指的是 RS 和 R/W 引脚使能后至少保持 30ns,使能引脚 E 才可以变成高电平
tHD1 :指的是使能引脚 E 变成低电平后,至少保持 10ns 之后,RS 和 R/W 才能进行变化
tD :指的是使能引脚 E 变成高电平后,最多 100ns 后,1602 就把数据送出来了
tHD2 :指的是读操作过程中,使能引脚 E 变成低电平后,至少保持 20ns,DB 数据总线才可以进行变化
tSP2 :指的是 DB 数据总线准备好后,至少保持 40ns,使能引脚 E 才可以从低到高进行使能变化
tHD2 :指的是写操作过程中,要引脚 E 变成低电平后,至少保持 10ns,DB 数据总线才可以变化
要懂得估计主控芯片的指令时间,可以在官方数据手册上查到MCU的一些级别参数。比如我们现在用STC51做为主控芯片,外部11.0592MHz晶振,指令周期就是一个时钟周期为(1/11.0592MHz)s,所以至少确定了它执行一条指令的时间是us级别的。我们看到,以上给的时间参数全部是ns级别的,所以即便我们在程序里不加延时程序,也应该可以很好的配合LCD1602的时序要求了
程序
在LCD1602上显示字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <reg52.h> typedef unsigned char u8; #define LCD1602_DB P0 sbit LCD1602_RS = P1 ^ 0 ; sbit LCD1602_RW = P1 ^ 1 ; sbit LCD1602_E = P1 ^ 5 ; void Lcd1602_Init () ;void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;void main () { Lcd1602_Init(); Lcd_ShowStr(5 , 0 , "Hello" ); Lcd_ShowStr(0 , 1 , "You are the best" ); while (1 ); } void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
左移字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 #include <reg52.h> typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#define LCD1602_DB P0 sbit LCD1602_RS = P1 ^ 0 ; sbit LCD1602_RW = P1 ^ 1 ; sbit LCD1602_E = P1 ^ 5 ; bit flag = 0 ; u8 T0RH = 0 ; u8 T0RL = 0 ; u8 code str1[] = "Come on!" ; u8 code str2[] = "Amazing!" ; void Time0_Init (u16 ms) ;void Lcd1602_Init () ;void Lcd_ShowStr (u8 x, u8 y, u8 *str, u8 len) ;void main () { u8 i; u8 index = 0 ; u8 pdata bufMove1[16 + sizeof (str1) + 16 ]; u8 pdata bufMove2[16 + sizeof (str2) + 16 ]; Time0_Init(10 ); Lcd1602_Init(); for (i = 0 ; i < 16 ; i++) { bufMove1[i] = ' ' ; bufMove2[i] = ' ' ; } for (i = 0 ; i < (sizeof (str1) - 1 ); i++) { bufMove1[16 + i] = str1[i]; bufMove2[16 + i] = str2[i]; } for (i = (16 + sizeof (str1) - 1 ); i < sizeof (bufMove1); i++) { bufMove1[i] = ' ' ; bufMove2[i] = ' ' ; } while (1 ) { if (flag) { flag = 0 ; Lcd_ShowStr(0 , 0 , bufMove1 + index, 16 ); Lcd_ShowStr(0 , 1 , bufMove2 + index, 16 ); index++; if (index >= (16 + sizeof (str1) - 1 )) { index = 0 ; } } } } void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; } void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str, u8 len) { Lcd_SetCursor(x, y); while (len--) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); } void time0 () interrupt 1{ static u8 count = 0 ; TH0 = T0RH; TL0 = T0RL; count++; if (count >= 50 ) { count = 0 ; flag = 1 ; } }
计算器实例
计算器不考虑连加,连减等连续计算,不考虑小数情况。加减乘除分别用上下左右来替代,回车表示等于,ESC 表示归 0
main.c 文件实现所有应用层的操作函数,即计算器功能所需要信息显示、按键动作响应等,另外还包括主循环和定时中断的调度
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include "AllHead.h" u8 code KeyCodeMap[4 ][4 ] = { { '1' , '2' , '3' , 0x26 }, { '4' , '5' , '6' , 0x25 }, { '7' , '8' , '9' , 0x28 }, { '0' , 0x1B , 0x0D , 0x27 } }; u8 pdata Key_State[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; void Key_Driver () { u8 i, j; static u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (backup[i][j] != Key_State[i][j]) { if (backup[i][j] != 0 ) { Key_Action(KeyCodeMap[i][j]); } backup[i][j] = Key_State[i][j]; } } } } void Key_Scan () { u8 i = 0 ; static u8 KeyOut = 0 ; static u8 keybuf[4 ][4 ] = {{0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; keybuf[KeyOut][0 ] = (keybuf[KeyOut][0 ] << 1 ) | KeyIn1; keybuf[KeyOut][1 ] = (keybuf[KeyOut][1 ] << 1 ) | KeyIn2; keybuf[KeyOut][2 ] = (keybuf[KeyOut][2 ] << 1 ) | KeyIn3; keybuf[KeyOut][3 ] = (keybuf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 0 ; } else if (0x0F == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 1 ; } } KeyOut++; if (KeyOut >= 4 ) { KeyOut = 0 ; } switch (KeyOut) { case 0 : KeyOut4 = 1 ; KeyOut1 = 0 ; break ; case 1 : KeyOut1 = 1 ; KeyOut2 = 0 ; break ; case 2 : KeyOut2 = 1 ; KeyOut3 = 0 ; break ; case 3 : KeyOut3 = 1 ; KeyOut4 = 0 ; break ; default : break ; } }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef _KEY_H_ #define _KEY_H_ sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; sbit KeyOut1 = P2^3 ; sbit KeyOut2 = P2^2 ; sbit KeyOut3 = P2^1 ; sbit KeyOut4 = P2^0 ; extern void Key_Driver () ;extern void Key_Scan () ;extern void Key_Action (u8 keycode) ;#endif
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd_AreaClear (u8 x, u8 y, u8 len) { Lcd_SetCursor(x, y); while (len--) { Lcd_WriteDat(' ' ); } } void Lcd_FullClear () { Lcd_WriteCmd(0x01 ); } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd_AreaClear (u8 x, u8 y, u8 len) ;extern void Lcd_FullClear () ;extern void Lcd1602_Init () ;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 #include "AllHead.h" u8 step = 0 ; u8 oprt = 0 ; signed long num1 = 0 ; signed long num2 = 0 ; signed long result = 0 ; void main () { Time0_Init(1 ); Lcd1602_Init(); Lcd_ShowStr(15 , 1 , "0" ); while (1 ) { Key_Driver(); } } u8 LongToString (u8 *str, signed long dat) { signed char i = 0 ; u8 len = 0 ; u8 buf[12 ]; if (dat < 0 ) { dat = -dat; *str++ = '-' ; len++; } do { buf[i++] = dat % 10 ; dat /= 10 ; } while (dat > 0 ); len += i; while (i-- > 0 ) { *str++ = buf[i] + '0' ; } *str = '\0' ; return len; } void ShowOprt (u8 y, u8 type) { switch (type) { case 0 : Lcd_ShowStr(0 , y, "+" ); break ; case 1 : Lcd_ShowStr(0 , y, "-" ); break ; case 2 : Lcd_ShowStr(0 , y, "*" ); break ; case 3 : Lcd_ShowStr(0 , y, "/" ); break ; default : break ; } } void Reset () { num1 = 0 ; num2 = 0 ; step = 0 ; Lcd_FullClear(); } void NumKey_Action (u8 num) { u8 len = 0 ; u8 str[12 ]; if (step > 1 ) { Reset(); } if (0 == step) { num1 = num1 * 10 + num; len = LongToString(str, num1); Lcd_ShowStr(16 - len, 1 , str); } else { num2 = num2 * 10 + num; len = LongToString(str, num2); Lcd_ShowStr(16 - len, 1 , str); } } void Oprt_KeyAction (u8 type) { u8 len = 0 ; u8 str[12 ]; if (0 == step) { len = LongToString(str, num1); Lcd_AreaClear(0 , 0 , 16 - len); Lcd_ShowStr(16 - len, 0 , str); ShowOprt(1 , type); Lcd_AreaClear(1 , 1 , 14 ); Lcd_ShowStr(15 , 1 , "0" ); oprt = type; step = 1 ; } } void GetResult () { u8 len = 0 ; u8 str[12 ]; if (1 == step) { step = 2 ; switch (oprt) { case 0 : result = num1 + num2; break ; case 1 : result = num1 - num2; break ; case 2 : result = num1 * num2; break ; case 3 : result = num1 / num2; break ; default : break ; } len = LongToString(str, num2); ShowOprt(0 , oprt); Lcd_AreaClear(1 , 0 , 16 - 1 - len); Lcd_ShowStr(16 - len, 0 , str); len = LongToString(str, result); Lcd_ShowStr(0 , 1 , "=" ); Lcd_AreaClear(1 , 1 , 16 - 1 - len); Lcd_ShowStr(16 - len, 1 , str); } } void Key_Action (u8 keycode) { if ((keycode >= '0' ) && (keycode <= '9' )) { NumKey_Action(keycode - '0' ); } else if (0x26 == keycode) { Oprt_KeyAction(0 ); } else if (0x28 == keycode) { Oprt_KeyAction(1 ); } else if (0x25 == keycode) { Oprt_KeyAction(2 ); } else if (0x27 == keycode) { Oprt_KeyAction(3 ); } else if (0x0D == keycode) { GetResult(); } else if (0x1B == keycode) { Reset(); Lcd_ShowStr(15 , 1 , "0" ); } } void time0 () interrupt 1{ TH0 = T0RH; TL0 = T0RL; Key_Scan(); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "key.h" #include "time.h" #include "Lcd1602.h" #endif
串口控制蜂鸣器与LCD1602液晶
通过电脑串口调试助手下发三个不同的命令,第一条指令: buzz on 可以让蜂鸣器响;第二条指令:buzz off 可以让蜂鸣器不响;第三条指令:showstr , 这个命令空格后边,可以添加任何字符串,让后边的字符串在 1602 液晶上显示出来,同时不管发送什么命令,单片机收到后把命令原封不动的再通过串口发送给电脑
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd_AreaClear (u8 x, u8 y, u8 len) { Lcd_SetCursor(x, y); while (len--) { Lcd_WriteDat(' ' ); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd_AreaClear (u8 x, u8 y, u8 len) ;extern void Lcd1602_Init () ;#endif
UART.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include "AllHead.h" bit flagFrame = 0 ; bit flagTxd = 0 ; u8 cntRxd = 0 ; u8 pdata bufRxd[64 ]; void UAER_Init (u16 baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - (11059200 / 12 / 32 ) / baud; TL1 = TH1; EA = 1 ; ES = 1 ; ET1 = 0 ; TR1 = 1 ; } void Uart_Write (u8 *buf, u8 len) { while (len--) { flagTxd = 0 ; SBUF = *buf++; while (!flagTxd); } } u8 UartRead (u8 *buf, u8 len) { u8 i = 0 ; if (len > cntRxd) { len = cntRxd; } for (i = 0 ; i < len; i++) { *buf++ = bufRxd[i]; } cntRxd = 0 ; return len; } void Uart_RxMonitor (u8 ms) { static u8 cntbkp = 0 ; static u8 idletmr = 0 ; if (cntRxd > 0 ) { if (cntbkp != cntRxd) { cntbkp = cntRxd; idletmr = 0 ; } else { if (idletmr < 30 ) { idletmr += ms; if (idletmr >= 30 ) { flagFrame = 1 ; } } } } else cntbkp = 0 ; } void Uart_Driver () { u8 len; u8 pdata buf[40 ]; if (flagFrame) { flagFrame = 0 ; len = UartRead(buf, sizeof (buf)); Uart_Action(buf, len); } } void UART () interrupt 4{ if (RI) { RI = 0 ; if (cntRxd < sizeof (bufRxd)) { bufRxd[cntRxd++] = SBUF; } } if (TI) { TI = 0 ; flagTxd = 1 ; } }
UART.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _UART_H_ #define _UART_H_ extern void UAER_Init (u16 baud) ;extern void Uart_Write (u8 *buf, u8 len) ;u8 UartRead (u8 *buf, u8 len) ; extern void Uart_RxMonitor (u8 ms) ;extern void Uart_Driver () ;extern void Uart_Action (u8 *buf, u8 len) ;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include "AllHead.h" sbit BUZZ = P1 ^ 6 ; bit flagBuzzOn = 0 ; void main () { Time0_Init(1 ); UAER_Init(9600 ); Lcd1602_Init(); while (1 ) { Uart_Driver(); } } bit Cmp_Memory (u8 *ptr1, u8 *ptr2, u8 len) { while (len--) { if (*ptr1++ != *ptr2++) { return 0 ; } } return 1 ; } void Uart_Action (u8 *buf, u8 len) { u8 i; u8 code cmd0[] = "buzz on" ; u8 code cmd1[] = "buzz off" ; u8 code cmd2[] = "showstr " ; u8 code cmdLen[] = {sizeof (cmd0) - 1 , sizeof (cmd1) - 1 , sizeof (cmd2) - 1 ,}; u8 code *cmdPtr[] = {&cmd0[0 ], &cmd1[0 ], &cmd2[0 ],}; for (i = 0 ; i < sizeof (cmdLen); i++) { if (len >= cmdLen[i]) { if (Cmp_Memory(buf, cmdPtr[i], cmdLen[i])) { break ; } } } switch (i) { case 0 : flagBuzzOn = 1 ; break ; case 1 : flagBuzzOn = 0 ; break ; case 2 : buf[len] = '\0' ; Lcd_ShowStr(0 , 0 , buf + cmdLen[2 ]); i = len - cmdLen[2 ]; if (i < 16 ) { Lcd_AreaClear(i, 0 , 16 - i); } break ; default : Uart_Write("bad command.\r\n" , sizeof ("bad command.\r\n" ) - 1 ); return ; } buf[len++] = '\r' ; buf[len++] = '\n' ; Uart_Write(buf, len); } void time0 () interrupt 1{ TH0 = T0RH; TL0 = T0RL; if (flagBuzzOn) { BUZZ = ~BUZZ; } else BUZZ = 1 ; Uart_RxMonitor(1 ); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "time.h" #include "Lcd1602.h" #include "UART.h" #endif
I2C 总线与 EEPROM
UART 通信属于异步通信,多用于板间通信,比如单片机和电脑,这个设备和另外一个设备之间的通信,I2C属于同步通信,多用于板内通信
I2C 时序
I2C 总线是由时钟总线 SCL 和数据总线 SDA 两条线构成,连接到总线上的所有器件的 SCL 都连到一起,所有 SDA 都连到一起,I2C 总线是 开漏引脚并联 的结构,因此我们外部要添加上拉电阻, 所有接入的器件保持高电平,这条线才是高电平,而任何一个器件输出一个低电平,那这条线就会保持低电平,因此可以做到任何一个器件都可以拉低电平, 也就是任何一个器件都可以作为主机;但绝大多数情况下我们都是用单片机来做主机, 而总线上挂的多个器件,每一个都像电话机一样有自己唯一的地址,在信息传输的过程中, 通过这唯一的地址就可以正常识别到属于自己的信息,在 KST-51 开发板上,就挂接了 2 个I2C 设备,一个是 24C02,一个是 PCF8591
I2C 时序流程图
I2C 分为起始信号、数据传输部分、停止信号。其中数据传输部分,可以一次通信过程传输很多个字节,字节数是不受限制的,而每个字节的数据最后也跟了一位,这一位叫做应答位,通常用 ACK 表示,有点类似于 UART 的停止位
I2C 通信流程解析
数据有效性规定
I2C 总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线(SCL)上的信号为低电平期间,数据线(SDA)上的高电平或低电平状态才允许变化。每次数据传输都以字节为单位,每次传输的字节数不受限制。如下图:
起始和停止信号
SCL 线为高电平期间,SDA 线由高电平向低电平的变化表示起始信号;SCL 线为高电平期间,SDA 线由低电平向高电平的变化表示终止信号。起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态。如下图:
起始条件 :SCL高电平期间,SDA从高电平切换到低电平
终止条件 :SCL高电平期间,SDA从低电平切换到高电平
应答响应
每当发送器件传输完一个字节的数据后,后面必须紧跟一个校验位,这个校验位是接收端通过控制 SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成,数据传送可以继续进行。这个校验位其实就是数据或地址传输过程中的响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号即特定的低电平脉冲, 发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号即特定的高电平脉冲,发送方接收到该信号后会产生一个停止信号,结束信号传输。应答响应时序图如下:
I2C 通信是高位在前,低位在后,每一个字节必须保证 8 位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有 9 位)。这些信号中,起始信号是必需的,结束信号和应答信号都可以不要
发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
总线的寻址方式
I2C 总线寻址按照从机地址位数可分为两种,一种是 7 位,另一种是 10 位。采用 7 位的寻址字节(寻址字节是起始信号后的第一个字节)的位定义如下:
D7~D1 位组成从机的地址。D0 位是数据传送方向位,为“ 0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的 7 位寻址位有 4 位是固定位,3 位是可编程位,这时仅能寻址 8 个同样的器件,即可以有 8 个同样的器件接入到该 I2C 总线系统中。
数据传输
I2C 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。在起始信号后必须传送一个从机的地址(7 位),第 8 位是数据的传送方向位(R/W),用“ 0 ”表示主机发送(写)数据(W),“ 1 ”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
A2、A1、A0 都是接的 GND,也就是说都是 0,因此 24C02 的 7 位地址实际上是二进制的 0b1010000,也就是 0x50,AT24C02的固定地址 (8位) 为1010,可配置地址本开发板上为000,所以addr + W为0xA0(发送数据(写)),addr + R为0xA1(请求数据(读))
在总线的一次数据传送过程中,可以有以下几种组合方式:
a、主机向从机发送数据,数据传送方向在整个传送过程中不变
注意:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号。
b、主机在第一个字节后,立即从从机读数据
c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反
发送一个字节 :SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
接收一个字节 :SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
EEPROM芯片
在实际的应用中,保存在单片机 RAM 中的数据,掉电后就丢失了,保存在单片机 FLASH 中的数据,又不能随意改变,也就是不能用它来记录变化的数值。一般都是使用 EEPROM 来保存数据, 特点就是掉电后不丢失。我们板子上使用的这个器件是 24C02,是一个容量大小是 2Kbits, 也就是 256 个字节的 EEPROM。一般情况下,EEPROM 拥有 30 万到 100 万次的寿命,也就是它可以反复写入 30-100 万次,而读取次数是无限的。24C02 是一个基于 I2C 通信协议的器件,但是要分清楚,I2C 是一个通信协议,它拥有严密的通信时序逻辑要求, 而 EEPROM 是一个器件,只是这个器件采样了 I2C 协议的接口与单片机相连而已,二者并没有必然的联系,EEPROM 可以用其它接口,I2C 也可以用在其它很多器件上
板子上的 EEPROM 器件型号是 24C02,设备地址高4位是固定的 0b1010,低3位由原理图可知是接地(即低电平),所以设备地址是 0b1010000(0x50),如果发送的这个地址确实存在,那么这个地址的器件应该回应一个 ACK(拉低 SDA 即输出“0”),如果不存在,就没“人”回应 ACK(SDA将保持高电平即“1”)
EEPROM 单字节读写操作时序
EEPROM 写数据流程
● 第一步,首先是 I2C 的起始信号,接着跟上首字节,也就是 I2C 的器件地址,并且在读写方向上选择“写”操作
● 第二步,发送数据的存储地址。24C02 一共 256 个字节的存储空间,地址从 0x00~0xFF,我们想把数据存储在哪个位置,此刻写的就是哪个地址
● 第三步,发送要存储的数据第一个字节、第二个字节……注意在写数据的过程中,EEPROM 每个字节都会回应一个“应答位 0”,来告诉我们写 EEPROM 数据成功,如果没有回应答位,说明写入不成功。
在写数据的过程中,每成功写入一个字节,EEPROM 存储空间的地址就会自动加 1,当加到 0xFF 后,再写一个字节,地址会溢出又变成了 0x00
EEPROM 读数据流程
● 第一步,首先是 I2C 的起始信号,接着跟上首字节,也就是 I2C 的器件地址,并且在读写方向上选择“写”操作。选择写操作,是为了把所要读的数据的存储地址先写进去,告诉 EEPROM 我们要读取哪个地址的数据。
● 第二步,发送要读取的数据的地址,注意是地址而非存在 EEPROM 中的数据,通知 EEPROM 我要哪个分机的信息。
● 第三步,重新发送 I2C 起始信号和器件地址,并且在方向位选择“读”操作。
这三步当中,每一个字节实际上都是在“写”,所以每一个字节 EEPROM 都会回应一个“应答位 0”。
● 第四步,读取从器件发回的数据,读一个字节,如果还想继续读下一个字节,就发送一个“应答位 ACK(0)”,如果不想读了,告诉 EEPROM,我不想要数据了,别再发数据了,那就发送一个“非应答位 NAK(1)”。
A、单片机是主机,24C02 是从机
B、无论是读是写,SCL 始终都是由主机控制的
C、写的时候应答信号由 从机 给出,表示从机是否正确接收了数据
D、读的时候应答信号则由 主机 给出,表示是否继续读下去
EEPROM 多字节读写操作时序
给 EEPROM 发送数据后,先保存在了 EEPROM的缓存里,EEPROM 必须要把缓存中的数据搬移到“非易失”的区域,才能达到掉电不丢失的效果。而往非易失区域写需要一定的时间,每种器件不完全一样,24C02 的 这个写入时间最高不超过 5ms (在往非易失区域写的过程,EEPROM 是不会再响应我们的访问的,不仅接收不到我们的数据,我们即使用 I2C 标准的寻址模式去寻址,EEPROM 都不会应答,就如同这个总线上没有这个器件一样。数据写入非易失区域完毕后,EEPROM 再次恢复正常,可以正常读写了)
EEPROM 的页写入
在向 EEPROM 连续写入多个字节的数据时,如果每写一个字节都要等待几 ms 的话,整体上的写入效率就太低了。所以就想了一个办法,把 EEPROM 分页管理。 24C01、24C02 这两个型号是 8 个字节一个页,而 24C04、24C08、24C16 是 16 个字节一页。 开发板上用的型号是 24C02,一共是 256 个字节,8 个字节一页,那么就一共有 32 页。 分配好页之后,如果我们在同一个页内连续写入几个字节后,最后再发送停止位的时序。 EEPROM 检测到这个停止位后,就会一次性把这一页的数据写到非易失区域,就不需要写一个字节检测一次了,并且页写入的时间也不会超过 5ms。如果我们写入的数据跨页了,那么写完了一页之后,我们要发送一个停止位,然后等待并且检测 EEPROM 的空闲模式,一直等到把上一页数据完全写到非易失区域后,再进行下一页的写入,这样就可以在很大程度上提高数据的写入效率
程序
访问 EEPROM 的地址
用 I2C 的协议来寻址 0x50,另外再寻址一个不存在的地址 0x62,寻址完毕后,把返回的 ACK 显示到我们的 1602 液晶上(这里0表示地址存在,1表示地址不存在)
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd1602_Init () ;#endif
I2C.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include "AllHead.h" #define I2C_Delay() {_nop_();_nop_();_nop_();_nop_();} void I2C_Start () { I2C_SCL = 1 ; I2C_SDA = 1 ; I2C_Delay(); I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 0 ; } void I2C_Stop () { I2C_SCL = 0 ; I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SDA = 1 ; I2C_Delay(); } bit I2C_Write (u8 dat) { bit ack; u8 i; for (i = 0 ; i < 8 ; i++) { if ((dat & 0x80 ) > 0 ) I2C_SDA = 1 ; else I2C_SDA = 0 ; dat <<= 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; ack = I2C_SDA; I2C_Delay(); I2C_SCL = 0 ; return (~ack); } bit I2C_Addressing (u8 addr) { bit ack; I2C_Start(); ack = I2C_Write(addr << 1 ); I2C_Stop(); return ack; }
I2C.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _I2C_H_ #define _I2C_H_ sbit I2C_SCL = P3^7 ; sbit I2C_SDA = P3^6 ; void I2C_Start () ;void I2C_Stop () ;bit I2C_Write (u8 dat) ; bit I2C_Addressing (u8 addr) ; #endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "AllHead.h" void main () { bit ack; u8 str[10 ]; Lcd1602_Init(); ack = I2C_Addressing(0x50 ); str[0 ] = '5' ; str[1 ] = '0' ; str[2 ] = ':' ; str[3 ] = (unsigned char )ack + '0' ; str[4 ] = '\0' ; Lcd_ShowStr(0 , 0 , str); ack = I2C_Addressing(0x62 ); str[0 ] = '6' ; str[1 ] = '2' ; str[2 ] = ':' ; str[3 ] = (unsigned char )ack + '0' ; str[4 ] = '\0' ; Lcd_ShowStr(8 , 0 , str); while (1 ); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "Lcd1602.h" #include "I2C.h" #include "intrins.h" #endif
读取 EEPROM 地址上的一个数据,将读出来的数据加 1,再写到 EEPROM 地址上
读取 EEPROM 的 0x02 这个地址上的一个数据,不管这个数据之前是多少,都将读出来的数据加 1,再写到 EEPROM 的 0x02 这个地址上
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd1602_Init () ;#endif
I2C.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include "AllHead.h" #define I2C_Delay() {_nop_();_nop_();_nop_();_nop_();} void I2C_Start () { I2C_SCL = 1 ; I2C_SDA = 1 ; I2C_Delay(); I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 0 ; } void I2C_Stop () { I2C_SCL = 0 ; I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SDA = 1 ; I2C_Delay(); } bit I2C_Write (u8 dat) { bit ack; u8 i; for (i = 0 ; i < 8 ; i++) { if ((dat & 0x80 ) > 0 ) I2C_SDA = 1 ; else I2C_SDA = 0 ; dat <<= 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; ack = I2C_SDA; I2C_Delay(); I2C_SCL = 0 ; return (~ack); } u8 I2C_ReadNACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; } u8 I2C_ReadACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; }
I2C.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _I2C_H_ #define _I2C_H_ sbit I2C_SCL = P3^7 ; sbit I2C_SDA = P3^6 ; void I2C_Start () ;void I2C_Stop () ;bit I2C_Write (u8 dat) ; u8 I2C_ReadNACK () ; u8 I2C_ReadACK () ; #endif
EEPROM.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include "AllHead.h" u8 EEPROM_ReadByte (u8 addr) { u8 receive; I2C_Start(); I2C_Write(0xA0 ); I2C_Write(addr); I2C_Start(); I2C_Write(0xA1 ); receive = I2C_ReadNACK(); I2C_Stop(); return receive; } void EEPROM_WriteByte (u8 addr, u8 dat) { I2C_Start(); I2C_Write(0xA0 ); I2C_Write(addr); I2C_Write(dat); I2C_Stop(); }
EEPROM.h
1 2 3 4 5 6 7 #ifndef _EEPROM_H_ #define _EEPROM_H_ u8 EEPROM_ReadByte (u8 addr) ; void EEPROM_WriteByte (u8 addr, u8 dat) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include "AllHead.h" void main () { u8 dat; u8 str[10 ]; Lcd1602_Init(); dat = EEPROM_ReadByte(0x02 ); str[0 ] = (dat / 100 ) + '0' ; str[1 ] = (dat / 10 % 10 ) + '0' ; str[2 ] = (dat % 10 ) + '0' ; str[3 ] = '\0' ; Lcd_ShowStr(0 , 0 , str); dat++; EEPROM_WriteByte(0x02 , dat); while (1 ); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "Lcd1602.h" #include "I2C.h" #include "intrins.h" #include "EEPROM.h" #endif
EEPROM页写入 – 读取 EEPROM 地址上的多个数据,将读出来的数据分别 + 1 3 5···,再写到 EEPROM 地址上
函数 MemToStr:可以把一段内存数据转换成十六进制字符串的形式。由于我们从 EEPROM 读出来的是正常的数据,而 1602 液晶接收的是 ASCII 码字符,因此我们要通过液晶把数据显示出来必须先通过一步转换。就是把每一个字节的数据高 4 位 和低 4 位分开,和 9 进行比较,如果小于等于 9,则直接加 '0' 转为 0~9 的 ASCII 码;如果大于 9,则先减掉 10 再加 ‘A’ 即可转为 A~F 的 ASCII 码
函数 EEPROM_Read:在读之前,要查询一下当前是否可以进行读写操作,EEPROM 正常响应才可以进行。进行后,读最后一个字节之前的,全部给出 ACK,而读完了最后一个字节, 我们要给出一个 NAK
函数 EEPROM_Write:每次写操作之前,都要进行查询判断当前 EEPROM 是否响应,正常响应后才可以写数据
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd1602_Init () ;#endif
I2C.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include "AllHead.h" #define I2C_Delay() {_nop_();_nop_();_nop_();_nop_();} void I2C_Start () { I2C_SCL = 1 ; I2C_SDA = 1 ; I2C_Delay(); I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 0 ; } void I2C_Stop () { I2C_SCL = 0 ; I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SDA = 1 ; I2C_Delay(); } bit I2C_Write (u8 dat) { bit ack; u8 i; for (i = 0 ; i < 8 ; i++) { if ((dat & 0x80 ) > 0 ) I2C_SDA = 1 ; else I2C_SDA = 0 ; dat <<= 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; ack = I2C_SDA; I2C_Delay(); I2C_SCL = 0 ; return (~ack); } u8 I2C_ReadNACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; } u8 I2C_ReadACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; }
I2C.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _I2C_H_ #define _I2C_H_ sbit I2C_SCL = P3^7 ; sbit I2C_SDA = P3^6 ; void I2C_Start () ;void I2C_Stop () ;bit I2C_Write (u8 dat) ; u8 I2C_ReadNACK () ; u8 I2C_ReadACK () ; #endif
EEPROM.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include "AllHead.h" void EEPROM_Read (u8 *buf, u8 addr, u8 len) { do { I2C_Start(); if (I2C_Write(0xA0 )) { break ; } I2C_Stop(); } while (1 ); I2C_Write(addr); I2C_Start(); I2C_Write(0xA1 ); while (len > 1 ) { *buf++ = I2C_ReadACK(); len--; } *buf = I2C_ReadNACK(); I2C_Stop(); } void EEPROM_Write (u8 *buf, u8 addr, u8 len) { while (len > 0 ) { do { I2C_Start(); if (I2C_Write(0xA0 )) { break ; } I2C_Stop(); } while (1 ); I2C_Write(addr); while (len > 0 ) { I2C_Write(*buf++); len--; addr++; if (0 == (addr & 0x07 )) { break ; } } I2C_Stop(); } }
EEPROM.h
1 2 3 4 5 6 7 #ifndef _EEPROM_H_ #define _EEPROM_H_ void EEPROM_Read (u8 *buf, u8 addr, u8 len) ;void EEPROM_Write (u8 *buf, u8 addr, u8 len) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "AllHead.h" void MemToStr (u8 *str, u8 *src, u8 len) ;void main () { u8 i; u8 buf[5 ]; u8 str[20 ]; Lcd1602_Init(); EEPROM_Read(buf, 0x8E , sizeof (buf)); MemToStr(str, buf, sizeof (buf)); Lcd_ShowStr(0 , 0 , str); for (i = 0 ; i < sizeof (buf); i++) { buf[i] = buf[i] + 1 + i; } EEPROM_Write(buf, 0x8E , sizeof (buf)); while (1 ); } void MemToStr (u8 *str, u8 *src, u8 len) { u8 temp; while (len--) { temp = *src >> 4 ; if (temp <= 9 ) *str++ = temp + '0' ; else *str++ = temp - 10 + 'A' ; temp = *src & 0x0F ; if (temp <= 9 ) *str++ = temp + '0' ; else *str++ = temp - 10 + 'A' ; *str++ = ' ' ; src++; } *str++ = '\0' ; }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "Lcd1602.h" #include "I2C.h" #include "intrins.h" #include "EEPROM.h" #endif
I2C 和 EEPROM 的综合实验
上电后,1602 的第一行显示 EEPROM 从 0x20 地址开始的 16 个字符,第二行显示 EERPOM 从 0x40 开始的 16 个字符。可以通过 UART 串口通信来改变 EEPROM 内部的这个数据,并且同时也改变了 1602 显示的内容,下次上电的时候,直接会显示更新过的内容
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd1602_Init () ;#endif
UART.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include "AllHead.h" bit flagFrame = 0 ; bit flagTxd = 0 ; u8 cntRxd = 0 ; u8 pdata bufRxd[64 ]; void UAER_Init (u16 baud) { SCON = 0x50 ; TMOD &= 0x0F ; TMOD |= 0x20 ; TH1 = 256 - (11059200 / 12 / 32 ) / baud; TL1 = TH1; EA = 1 ; ES = 1 ; ET1 = 0 ; TR1 = 1 ; } void Uart_Write (u8 *buf, u8 len) { while (len--) { flagTxd = 0 ; SBUF = *buf++; while (!flagTxd); } } u8 UartRead (u8 *buf, u8 len) { u8 i = 0 ; if (len > cntRxd) { len = cntRxd; } for (i = 0 ; i < len; i++) { *buf++ = bufRxd[i]; } cntRxd = 0 ; return len; } void Uart_RxMonitor (u8 ms) { static u8 cntbkp = 0 ; static u8 idletmr = 0 ; if (cntRxd > 0 ) { if (cntbkp != cntRxd) { cntbkp = cntRxd; idletmr = 0 ; } else { if (idletmr < 30 ) { idletmr += ms; if (idletmr >= 30 ) { flagFrame = 1 ; } } } } else cntbkp = 0 ; } void Uart_Driver () { u8 len; u8 pdata buf[40 ]; if (flagFrame) { flagFrame = 0 ; len = UartRead(buf, sizeof (buf)); Uart_Action(buf, len); } } void UART () interrupt 4{ if (RI) { RI = 0 ; if (cntRxd < sizeof (bufRxd)) { bufRxd[cntRxd++] = SBUF; } } if (TI) { TI = 0 ; flagTxd = 1 ; } }
UART.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _UART_H_ #define _UART_H_ extern void UAER_Init (u16 baud) ;extern void Uart_Write (u8 *buf, u8 len) ;u8 UartRead (u8 *buf, u8 len) ; extern void Uart_RxMonitor (u8 ms) ;extern void Uart_Driver () ;extern void Uart_Action (u8 *buf, u8 len) ;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
I2C.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include "AllHead.h" #define I2C_Delay() {_nop_();_nop_();_nop_();_nop_();} void I2C_Start () { I2C_SCL = 1 ; I2C_SDA = 1 ; I2C_Delay(); I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 0 ; } void I2C_Stop () { I2C_SCL = 0 ; I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SDA = 1 ; I2C_Delay(); } bit I2C_Write (u8 dat) { bit ack; u8 i; for (i = 0 ; i < 8 ; i++) { if ((dat & 0x80 ) > 0 ) I2C_SDA = 1 ; else I2C_SDA = 0 ; dat <<= 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; ack = I2C_SDA; I2C_Delay(); I2C_SCL = 0 ; return (~ack); } u8 I2C_ReadNACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; } u8 I2C_ReadACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; }
I2C.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _I2C_H_ #define _I2C_H_ sbit I2C_SCL = P3^7 ; sbit I2C_SDA = P3^6 ; void I2C_Start () ;void I2C_Stop () ;bit I2C_Write (u8 dat) ; u8 I2C_ReadNACK () ; u8 I2C_ReadACK () ; #endif
EEPROM.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include "AllHead.h" void EEPROM_Read (u8 *buf, u8 addr, u8 len) { do { I2C_Start(); if (I2C_Write(0xA0 )) { break ; } I2C_Stop(); } while (1 ); I2C_Write(addr); I2C_Start(); I2C_Write(0xA1 ); while (len > 1 ) { *buf++ = I2C_ReadACK(); len--; } *buf = I2C_ReadNACK(); I2C_Stop(); } void EEPROM_Write (u8 *buf, u8 addr, u8 len) { while (len > 0 ) { do { I2C_Start(); if (I2C_Write(0xA0 )) { break ; } I2C_Stop(); } while (1 ); I2C_Write(addr); while (len > 0 ) { I2C_Write(*buf++); len--; addr++; if (0 == (addr & 0x07 )) { break ; } } I2C_Stop(); } }
EEPROM.h
1 2 3 4 5 6 7 #ifndef _EEPROM_H_ #define _EEPROM_H_ void EEPROM_Read (u8 *buf, u8 addr, u8 len) ;void EEPROM_Write (u8 *buf, u8 addr, u8 len) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #include "AllHead.h" void Init_ShowStr () ;bit Cmp_Memory (u8 *ptr1, u8 *ptr2, u8 len) ; void TrimString16 (u8 *out, u8 *in) ;void main () { Time0_Init(1 ); UAER_Init(9600 ); Lcd1602_Init(); Init_ShowStr(); while (1 ) { Uart_Driver(); } } void Init_ShowStr () { u8 str[17 ]; str[16 ] = '\0' ; EEPROM_Read(str, 0x20 , 16 ); Lcd_ShowStr(0 , 0 , str); EEPROM_Read(str, 0x40 , 16 ); Lcd_ShowStr(0 , 1 , str); } bit Cmp_Memory (u8 *ptr1, u8 *ptr2, u8 len) { while (len--) { if (*ptr1++ != *ptr2++) { return 0 ; } else return 1 ; } } void TrimString16 (u8 *out, u8 *in) { u8 i = 0 ; while (*in != '\0' ) { *out++ = *in++; i++; if (i >= 16 ) break ; } for (; i < 16 ; i++) { *out++ = ' ' ; } *out = '\0' ; } void Uart_Action (u8 *buf, u8 len) { u8 i; u8 str[17 ]; u8 code cmd0[] = "showstr1 " ; u8 code cmd1[] = "showstr2 " ; u8 code cmdLen[] = {sizeof (cmd0) - 1 , sizeof (cmd1) - 1 }; u8 code *cmdPtr[] = { &cmd0[0 ], &cmd1[0 ] }; for (i = 0 ; i < sizeof (cmdLen); i++) { if (len >= cmdLen[i]) { if (Cmp_Memory(buf, cmdPtr[i], cmdLen[i])) { break ; } } } switch (i) { case 0 : buf[len] = '\0' ; TrimString16(str, buf + cmdLen[0 ]); Lcd_ShowStr(0 , 0 , str); EEPROM_Write(str, 0x20 , sizeof (str)); break ; case 1 : buf[len] = '\0' ; TrimString16(str, buf + cmdLen[1 ]); Lcd_ShowStr(0 , 1 , str); EEPROM_Write(str, 0x40 , sizeof (str)); break ; default : Uart_Write("bad command.\r\n" , sizeof ("bad command.\r\n" ) - 1 ); return ; } buf[len++] = '\r' ; buf[len++] = '\n' ; Uart_Write(buf, len); } void time () interrupt 1{ TH0 = T0RH; TL0 = T0RL; Uart_RxMonitor(1 ); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "time.h" #include "Lcd1602.h" #include "UART.h" #include "I2C.h" #include "intrins.h" #include "EEPROM.h" #endif
实时时钟 DS1302
BCD 码
BCD 码亦称二进码十进制数或二-十进制代码。用 4 位二进制数来表示 1 位十进制数中的 0~9 这 10 个数字。是一种二进制的数字编码形式,用二进制编码的十进制代码。十进制的一位数字,从 0 到 9,最大的数字就是 9,再加 1 就要进位,所以用 4 位二进制表示十进制,就是从 0b0000 到 0b1001,不存在 0b1010、 0b1011、0b1100、0b1101、0b1110、0b1111 这 6 个数字。BCD 码如果到了 0b1001,再加 1 的话,数字就变成 0b00010000 这样了,相当于用了 8 位的二进制数字表示了 2 位的十进制数字
从 DS1302 中读取出来的时钟数据均为 BCD 码格式,需转换为我们习惯的 10 进制
日期时间在时钟芯片中的存储格式就是 BCD 码,直接 取出表示十进制 1 位数字的 4 个二进制位然后再加上 0x30 就可组成一个ASCII码字节
需要注意:DS1302低位在前,高位在后
SPI 时序
SPI 是串行外围设备接口,是一种高速的、全双工、同步通信总线,常用于单片机和 EEPROM、FLASH、实时时钟、数字信号处理器等器件的通信
SPI 通信原理主要是主从方式通信,这种模式通常只有一个主机和一个或者多个从机,标准的 SPI 是 4 根线,分别是 SSEL(片选,也写作 SCS)、SCLK(时钟,也写作 SCK)、MOSI(主机输出从机输入)和 MISO(主机输入从机输出 )
SSEL :从设备片选使能信号。如果从设备是低电平使能的话,当拉低这个引脚后,从设备就会被选中,主机和这个被选中的从机进行通信
SCLK :时钟信号,由主机产生,和 I2C 通信的 SCL 有点类似
MOSI :主机给从机发送指令或者数据的通道
MISO :主机读取从机的状态或者数据的通道
读写数据时序的四种模式
CPOL:时钟的极性。通信的整个过程分为空闲时刻和通信时刻,如果 SCLK 在数据发送之前和之后的空闲状态是高电平,那么 CPOL=1,如果空闲状态 SCLK 是低电平,那么 CPOL=0
CPHA:时钟的相位。CPHA=1 表示数据的输出是在一个时钟周期的第一个沿上,那么数据的采样就是在第二个沿上了;CPHA=0 表示数据的采样是在一个时钟周期的第一个沿上,那么数据的输出就在第二个沿上
模式一
当数据未发送时以及发送完毕后,SCK 都是高电平,因此 CPOL=1。 可以看出,在 SCK 第一个沿的时候,MOSI 和 MISO 会发生变化,同时 SCK 第二个沿的时候,数据是稳定的,此刻采样数据是合适的,也就是上升沿即一个时钟周期的后沿锁存读取数据,即 CPHA=1。最后最隐蔽的 SSEL 片选,这个引脚通常用来决定是哪个从机和主机进行通信
另外几种模式如图所示:
实时时钟芯片 DS1302
DS1302 是 一种涓流充电时钟芯片,内含有一个实时时钟/日历和 31 字节静态 RAM,通过简单的串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、周、月、年的信息,每月的天数和闰年的天数可自动调整。时钟操作可通过 AM/PM 指示决定采用 24 或 12 小时格式。
DS1302 与单片机之间能简单地采用同步串行的方式进行通信,仅需用到三根通信线:①RES复位 ②I/O 数据线 ③SCLK 串行时钟。时钟/RAM 的读/写数据以一个字节或多达31 个字节的字符组方式通信。DS1302 工作时功耗很低保持数据和时钟信息时功率小于 1mW。
DS1302 由 DS1202 改进而来增加了以下的特性:双电源管脚用于主电源和备份电源供应,Vcc1 为可编程涓流充电电源,附加七个字节存储器。它广泛应用于电话、传真、便携式仪器以及电池供电的仪器仪表等产品领域下面。
主要的性能指标:
实时时钟具有能计算 2100 年之前的秒、分、时、日、星期、月、年的能力,还有闰年调整的能力;
31 个 8 位暂存数据存储 RAM;
串行 I/O 口方式使得管脚数量最少;
宽范围工作电压 2.0 - 5.5V;
工作在 2.0V 时,电流小于 300nA;
读/写时钟或 RAM 数据时有两种传送方式单字节传送和多字节传送字符组方式;
8 脚 DIP 封装或可选的 8 脚 SOIC 封装根据表面装配;
简单 3 线接口;
与 TTL 兼容 Vcc=5V;
可选工业级温度范围-40~+85
DS1302 芯片的管脚及功能
1、VCC2:主电源引脚
2、X1、X2:DS1302 外部晶振引脚,通常需外接 32.768K 晶振
3、GND:电源地
4、CE:使能引脚,也是复位引脚(新版本功能变)
5、I/O:串行数据引脚,数据输出或者输入都从这个引脚
6、SCLK:串行时钟引脚
7、VCC1:备用电源
DS1302 使用
操作 DS1302 的大致过程,就是将各种数据写入 DS1302 的寄存器,以设置它当前的时间的格式。然后使 DS1302 开始运作,DS1302 时钟会按照设置情况运转,再用单片机将其寄存器内的数据读出。再用液晶显示,就是我们常说的简易电子钟。所以总的来说 DS1302 的操作分 2 步(显示部分属于液晶显示的内容,不属于 DS1302 本身的内容)
DS1302 有一个控制寄存器、12 个日历、时钟寄存器和 31 个 RAM
控制寄存器
控制寄存器用于存放 DS1302 的控制命令字,DS1302 的 RST 引脚回到高电平后写入的第一个字节就为控制命令。它用于对 DS1302 读写过程进行控制,格式如下:
1、第 7 位永远都是 1,这一位如果是 0 的话,那写进去也是无效的
2、第 6 位,1 表示 RAM,寻址内部存储器地址;0 表示 CK,寻址内部寄存器;
3、第 5 到第 1 位,为 RAM 或者寄存器的地址;
4、最低位,高电平表示 RD,即下一步操作将要“读”;低电平表示 W,即下一步操作将要“写”。(与 AT24C02 寄存器类似,0读1写)
比如要读秒寄存器则命令为 1000 0001,反之写为 1000 0000
日历/时钟寄存器
DS1302 共有 12 个寄存器,其中有 7 个与日历、时钟相关,存放的数据为 BCD码形式。格式如下:
秒寄存器 :低四位为秒的个位,高的次三位为秒的十位。最高位CH 为DS1302 的运行标志,当 CH=0 时,DS1302 内部时钟运行,反之 CH=1 时停止。DS1302 内部是 BCD 码, 而秒的十位最大是 5,所以 3 个二进制位就够了
分寄存器 :最高位未使用,剩下的 7 位中高 3 位是分钟的十位,低 4 位是分钟的个位
小时寄存器 :时寄存器。最高位为 12/24 小时的格式选择位,该位为 1 时表示 12 小时格式,0 代表是 24 小时制。第六位固定是 0,当设置为 12 小时显示格式时,第 5 位的 1 (高电平)表示下午(PM),0 (低电平)表示上午(AM);而当设置为 24 小时格式时,高 3 位代表了小时的十位,低 4 位代表的是小时的个位
日寄存器 :高 2 位固定是 0,bit5 和 bit4 是日期的十位,低 4 位是日期的个位
月寄存器 :高 3 位固定是 0,bit4 是月的十位,低 4 位是月的个位
星期寄存器 :高 5 位固定是 0,低 3 位代表了星期
年寄存器 :高 4 位代表了年的十位,低 4 位代表了年的个位。这里的 00~ 99 指的是 2000 年~2099 年
写保护寄存器 :当该寄存器最高位 WP 为 1 时,表示禁止给任何其它寄存器或者那 31 个字节的 RAM 写数据,DS1302只读不写,所以要在往 DS1302 写数据之前确保 WP 为 0。
慢充电寄存器(涓细电流充电)寄存器 :当 DS1302 掉电时,可以马上调用外部电源保护时间数据。该寄存器就是配置备用电源的充电选项的。其中高四位(4 个 TCS)只有在 1010 的情况下才能使用充电选项;低四位的情况与 DS1302 内部电路有关。
DS1302 通信时序
在控制指令字输入后的下一个 SCLK 时钟的上升沿时,数据被写入 DS1302,数据输入从低位(位 0)开始。同样,在紧跟 8 位的控制指令字后的下一个 SCLK脉冲的下降沿读出 DS1302 的数据,读出数据时从低位 0 位到高位 7。其时序图如下所示:
上图就是 DS1302 的三个时序:复位时序,单字节写时序,单字节读时序;
CE(RST ):复位时序,即在 RST 引脚产生一个正脉冲,在整个读写器件,RST 要保持高电平,一次字节读写完毕之后,要注意把 RST 返回低电平准备下次读写周期;
单字节读时序 :注意读之前还是要先对寄存器写命令,前八个为控制寄存器地址,从最低位开始写;可以看到,写数据是在 SCLK 的上升沿实现,而读数据在 SCLK 的下降沿实现。所以,在单字节读时序中,写命令的第八个上升沿结束后紧接着的第八个下降沿就将要读寄存器的第一位数据读到数据线上了!这个就是 DS1302 操作中最特别的地方。当然读出来的数据也是最低位开始。
单字节写时序 :两个字节的数据配合 16 个上升沿将数据写入即可。DS1302 的时序里, 单片机要预先写一个字节指令,指明要写入的寄存器的地址以及后续的操作是写操作,然后再写入一个字节的数据
程序注意事项 :
★要记得在操作 DS1302 之前关闭写保护(即确保 WP 为 0);
★注意用延时来降低单片机的速度以配合器件时序;
★DS1302 读出来的数据是 BCD 码形式,要转换成我们习惯的 10 进制;
★读取字节之前,将 IO 设置为输入口,读取完之后,要将其改回输出口;
★在写程序的时候,建议实现开辟数组(内存空间)来集中放置 DS1302 的一系列数据,方便以后扩展键盘输入。
DS1302 的 BURST 模式
DS1302 的突发模式。突发模式也分为 RAM 突发模式和时钟突发模式,只看和时钟相关的 clock burst mode
当写指令到DS1302 的时候,只要将要写的 5 位地址全部写1,即读操作用0xBF, 写操作用 0xBE,这样的指令送给 DS1302 之后,它就会自动识别出来是 burst 模式,马上把所有的 8 个字节同时锁存到另外的 8 个字节的寄存器缓冲区内,这样时钟继续走,而读数据是从另外一个缓冲区内读取的。同样的道理,如果用 burst 模式写数据,那么也是先写到这个缓冲区内,最终 DS1302 会把这个缓冲区内的数据一次性送到它的时钟寄存器内。 要注意的是,不管是读还是写,只要使用时钟的 burst 模式,则必须一次性读写 8 个寄存器,要把时钟的寄存器完全读出来或者完全写进去
原理图
程序
读取 DS1302 的当前时间,并显示在液晶屏上
先将 2023 年 10 月 14 日星期六 16 点 36 分 30 秒这个时间写到 DS1302 内部,让 DS1302 正常运行,然后再不停的读取 DS1302 的当前时间,并显示在液晶屏上
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd1602_Init () ;#endif
DS1302.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #include "AllHead.h" u8 Write_addr[7 ] = {0x80 , 0x82 , 0x84 , 0x86 , 0x88 , 0x8a , 0x8c }; u8 Read_addr[7 ] = {0x81 , 0x83 , 0x85 , 0x87 , 0x89 , 0x8b , 0x8d }; u8 DS1302_Time[7 ] = {0x30 , 0x36 , 0x16 , 0x14 , 0x10 , 0x06 , 0x23 }; void DS1302_Write_Byte (u8 addr, u8 dat) { u8 i; DS1302_CE = 0 ; _nop_(); DS1302_SCK = 0 ; _nop_(); DS1302_CE = 1 ; _nop_(); for (i = 0 ; i < 8 ; i++) { DS1302_IO = addr & 0x01 ; addr >>= 1 ; DS1302_SCK = 1 ; _nop_(); DS1302_SCK = 0 ; _nop_(); } for (i = 0 ; i < 8 ; i++) { DS1302_IO = dat & 0x01 ; dat >>= 1 ; DS1302_SCK = 1 ; _nop_(); DS1302_SCK = 0 ; _nop_(); } DS1302_CE = 0 ; _nop_(); } u8 DS1302_Read_Byte (u8 addr) { u8 i; u8 dat; DS1302_CE = 0 ; _nop_(); DS1302_SCK = 0 ; _nop_(); DS1302_CE = 1 ; _nop_(); for (i = 0 ; i < 8 ; i++) { DS1302_IO = addr & 0x01 ; addr >>= 1 ; DS1302_SCK = 1 ; _nop_(); DS1302_SCK = 0 ; _nop_(); } for (i = 0 ; i < 8 ; i++) { dat >>= 1 ; if (DS1302_IO) dat |= 0x80 ; DS1302_SCK = 1 ; _nop_(); DS1302_SCK = 0 ; _nop_(); } DS1302_CE = 0 ; _nop_(); return dat; } void DS1302_Init (void ) { u8 i; DS1302_Write_Byte(0x8E , 0x00 ); for (i = 0 ; i < 7 ; i++) { DS1302_Write_Byte(Write_addr[i], DS1302_Time[i]); } DS1302_Write_Byte(0x8E , 0x80 ); } void DS1302_Read_Time (void ) { u8 i; for (i = 0 ; i < 7 ; i++) { DS1302_Time[i] = DS1302_Read_Byte(Read_addr[i]); } }
DS1302.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _DS1302_H_ #define _DS1302_H_ sbit DS1302_CE = P1^7 ; sbit DS1302_SCK = P3^5 ; sbit DS1302_IO = P3^4 ; void DS1302_Write_Byte (u8 addr,u8 dat) ;u8 DS1302_Read_Byte (u8 addr) ; void DS1302_Init (void ) ;void DS1302_Read_Time (void ) ;extern u8 DS1302_Time[7 ]; #endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include "AllHead.h" void main () { u8 str[12 ]; DS1302_Init(); Lcd1602_Init(); while (1 ) { DS1302_Read_Time(); str[0 ] = '2' ; str[1 ] = '0' ; str[2 ] = (DS1302_Time[6 ] >> 4 ) + '0' ; str[3 ] = (DS1302_Time[6 ] & 0x0F ) + '0' ; str[4 ] = '-' ; str[5 ] = (DS1302_Time[4 ] >> 4 ) + '0' ; str[6 ] = (DS1302_Time[4 ] & 0x0F ) + '0' ; str[7 ] = '-' ; str[8 ] = (DS1302_Time[3 ] >> 4 ) + '0' ; str[9 ] = (DS1302_Time[3 ] & 0x0F ) + '0' ; str[10 ] = '\0' ; Lcd_ShowStr(0 , 0 , str); str[0 ] = (DS1302_Time[5 ] & 0x0F ) + '0' ; str[1 ] = '\0' ; Lcd_ShowStr(11 , 0 , "week" ); Lcd_ShowStr(15 , 0 , str); str[0 ] = (DS1302_Time[2 ] >> 4 ) + '0' ; str[1 ] = (DS1302_Time[2 ] & 0x0F ) + '0' ; str[2 ] = ':' ; str[3 ] = (DS1302_Time[1 ] >> 4 ) + '0' ; str[4 ] = (DS1302_Time[1 ] & 0x0F ) + '0' ; str[5 ] = ':' ; str[6 ] = (DS1302_Time[0 ] >> 4 ) + '0' ; str[7 ] = (DS1302_Time[0 ] & 0x0F ) + '0' ; str[8 ] = '\0' ; Lcd_ShowStr(4 , 1 , str); } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "Lcd1602.h" #include "intrins.h" #include "DS1302.h" #endif
红外通信
红外光的基本原理
红外线是波长介于微波和可见光之间的电磁波,波长在 760 纳米到 1 毫米之间,是波形比红光长的非可见光。自然界中的一切物体,只要它的温度高于绝对零度(-273)就存在分子和原子的无规则运动,其表面就会不停的辐射红外线。红外发射管很常用,在我们的遥控器上都可以看到,它类似发光二极管,但是它发射出来的是红外光,是肉眼看不到的。红外发射管发射红外线的强度会随着电流的增大而增强
常见的红外发射管如图所示:
红外接收管内部是一个具有红外光敏感特征的 PN 节,属于光敏二极管,但是它只对红外光有反应。无红外光时,光敏管不导通,有红外光时,光敏管导通形成光电流,并且在一定范围内电流随着红外光的强度的增强而增大。典型的红外接收管如图所示:
红外遥控的原理
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。
红外遥控通信系统一般由红外发射装置和红外接收设备两大部分组成。
红外发射装置
红外发射装置,也就是通常我们说的红外遥控器是由键盘电路、红外编码电路、电源电路和红外发射电路组成。红外发射电路的主要元件为红外发光二极管。它实际上是一只特殊的发光二极管;由于其内部材料不同于普通发光二极管,因而在其两端施加一定电压时,它便发出的是红外线而不是可见光。目前大量的使用的红外发光二极管发出的红外线波长为 940nm 左右,外形与普通发光二极管相同。红外发光二极管有透明的,还有不透明的。红外遥控器和红外发光二极管如下图所示:
通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为 38kHz,这是由发射端所使用的 455kHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取 12,所以455kHz÷12≈37.9kHz≈38kHz。也有一些遥控系统采用 36kHz、 40 kHz、 56 kHz等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信号(二进制脉冲码)调制在 38KHz 的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。
二进制脉冲码的形式有多种,其中最为常用的是 NEC Protocol 的 PWM码(脉冲宽度调制)和 Philips RC-5 Protocol 的PPM 码脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率。
红外接收设备
红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥控接收器的主要作用是将遥控发射器发来的红外光信好转换成电信号,再放大、限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器。成品红外接收头的封装大致有两种:一种采用铁皮屏蔽;一种是塑料封装。均有三只引脚,即电源正( VDD)、电源负(GND)和数据输出(VOUT)。其外观实物图如下图所示:
正对接收头的凸起处看,从左至右,管脚依次是1:VOUT,2:GND,3:VDD。由于红外接收头在没有脉冲的时候为高电平,当收到脉冲的时候为低电平,所以可以通过外部中断的下降沿触发中断,在中断内通过计算高电平时间来判断接收到的数据是 0 还是 1。
NEC协议红外遥控器
其特征如下:
1、8 位地址和 8 位指令长度;
2、地址和命令 2 次传输(确保可靠性)
3、PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
4、载波频率为 38Khz;
5、位时间为 1.125ms 或 2.25ms
NEC 码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us脉冲+560us 低电平)。而红外接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。所以可以通过计算高电平时间判断接收到的数据是 0 还是 1。NEC 码位定义时序图如下图所示:
NEC 遥控指令的数据格式为:引导码、地址码、地址反码、控制码、控制反码(引导码、用户码、用户码(或者用户码反码)、按键键码和 键码反码)。引导码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。数据格式如下:
NEC 码还规定了连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平+97.94ms 高电平组成),如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次数。
原理图
红外接收流程
程序
数码管把遥控器的用户码和键码显示出来
smg.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "AllHead.h" u8 gsmg[16 ] = {0xC0 , 0xF9 , 0xA4 , 0xB0 , 0x99 , 0x92 , 0x82 , 0xF8 , 0x80 , 0x90 , 0x88 , 0x83 , 0xC6 , 0xA1 , 0x86 , 0x8E }; u8 SMG_BUFF[6 ] = {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }; void Smg_Scan () { static u8 i; LED_SMG_PORT = 0xFF ; P1 = (P1 & 0xF8 ) | i; LED_SMG_PORT = SMG_BUFF[i]; if (i < sizeof (SMG_BUFF) - 1 ) i++; else i = 0 ; }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _SMG_H_ #define _SMG_H_ #define LED_SMG_PORT P0 void Smg_Scan () ;extern u8 gsmg[16 ];extern u8 SMG_BUFF[6 ];#endif
ired.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include "AllHead.h" u8 gired_data[4 ]; void Ired_Init (void ) { IRD = 1 ; TMOD &= 0x0F ; TMOD |= 0x10 ; IT1 = 1 ; EA = 1 ; EX1 = 1 ; } void ired () interrupt 2 { u16 time_cnt = 0 ; u8 i, j; u8 ired_high_time = 0 ; if (0 == IRD) { time_cnt = 1000 ; while ((!IRD) && (time_cnt)) { Delay_10us(1 ); time_cnt--; if (0 == time_cnt) { return ; } } if (IRD) { time_cnt = 500 ; while ((IRD) && (time_cnt)) { Delay_10us(1 ); time_cnt--; if (0 == time_cnt) return ; } for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 8 ; j++) { time_cnt = 600 ; while ((!IRD) && (time_cnt)) { Delay_10us(1 ); time_cnt--; if (0 == time_cnt) return ; } while (IRD) { Delay_10us(10 ); ired_high_time++; if (ired_high_time > 20 ) return ; } gired_data[i] >>= 1 ; if (ired_high_time >= 8 ) gired_data[i] |= 0x80 ; ired_high_time = 0 ; } } } if (gired_data[2 ] != ~gired_data[3 ]) { for (i = 0 ; i < 4 ; i++) gired_data[i] = 0 ; return ; } } }
ired.h
1 2 3 4 5 6 7 8 9 10 #ifndef _IRED_H_ #define _IRED_H_ sbit IRD = P3^3 ; void Ired_Init (void ) ;extern u8 gired_data[4 ];#endif
time.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
Delay.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "AllHead.h" void Delay_10us (u16 ten_us) { while (ten_us--); }
Delay.h
1 2 3 4 5 6 #ifndef _DELAY_H_ #define _DELAY_H_ void Delay_10us (u16 ten_us) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include "AllHead.h" sbit ADDR3 = P1 ^ 3 ; sbit ENLED = P1 ^ 4 ; void main () { ENLED = 0 ; ADDR3 = 1 ; Time0_Init(1 ); Ired_Init(); PT0 = 1 ; while (1 ) { SMG_BUFF[5 ] = gsmg[gired_data[0 ] >> 4 ]; SMG_BUFF[4 ] = gsmg[gired_data[0 ] & 0x0F ]; SMG_BUFF[1 ] = gsmg[gired_data[2 ] >> 4 ]; SMG_BUFF[0 ] = gsmg[gired_data[2 ] & 0x0F ]; } } void time0 () interrupt 1{ TH0 = T0RH; TL0 = T0RL; Smg_Scan(); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "time.h" #include "smg.h" #include "intrins.h" #include "ired.h" #include "Delay.h" #endif
问:当我们按下遥控器按键的时候,数码管显示的数字会闪烁,这是什么原因呢?
单片机的程序都是顺序执行的,一旦我们按下遥控器按键,单片机就会进入遥控器解码的中断程序内,而这个程序执行的时间又比较长,要几十个毫秒,而如果数码管动态刷新间隔超过10ms后就会感觉到闪烁,因此这个闪烁是由于程序执行红外解码时,延误了数码管动态刷新造成的,所以解决方法就是设置定时器0中断为高优先级,因为定时器中断执行时间也就几十个us不会对解码有很大的影响
温度传感器 DS18B20
DS18B20 是温度传感器,单片机可以通过 1-Wire 协议与 DS18B20 进行通信,最终将温度读出。1-Wire 总线的硬件接口很简单,只需要把 DS18B20 的数据引脚和单片机的一个 IO 口接上就可以了
DS18B20 内部结构如下图所示:
ROM 中的 64 位序列号是出厂前被光刻好的,它可以看作是该 DS18B20 的地址序列号。光刻 ROM 的作用是使每一个 DS18B20 都各不相同,这样就可以实现一根总线上挂接多个 DS18B20 的目的。
DS18B20 温度传感器的内部存储器包括一个高速的暂存器 RAM 和一个非易失性的可电擦除的 EEPROM,后者存放高温度和低温度触发器 TH、TL 和配置寄存器。
配置寄存器是配置不同的位数来确定温度和数字的转化,配置寄存器结构如下:
低五位一直都是"1",TM 是测试模式位,用于设置 DS18B20 在工作模式还是在测试模式。在 DS18B20 出厂时该位被设置为 0,因此 DS18B20 在工作模式。R1 和R0 用来设置 DS18B20 的精度(分辨率),可设置为 9,10,11 或 12 位,对应的分辨率温度是 0.5℃,0.25℃,0.125℃和 0.0625℃。R0 和 R1 配置如下图:
在初始状态下默认的精度是 12 位,即 R0=1、R1=1。高速暂存存储器由 9 个字节组成,其分配如下:
当温度转换命令(44H)发布后,经转换所得的温度值以二字节补码形式存放在高速暂存存储器的第 0 和第 1 个字节。存储的两个字节,高字节的前 5 位是符号位S,单片机可通过单线接口读到该数据,读取时低位在前,高位在后,数据格式如下:
如果测得的温度大于 0,这 5 位符号位S为‘ 0’,只要将测到的数值乘以 0.0625(默认精度是 12 位)即可得到实际温度;如果温度小于 0,这 5 位符号位S为‘ 1’,测到的数值需要取反加 1 再乘以 0.0625 即可得到实际温度。温度与数据对应关系如下:
比如要计算 +85 度,数据输出十六进制是 0X0550,因为要计算的温度大于0,所以高字节的高 5位为 0,十六进制0X0550 对应的二进制为 0000 0101 0101 0000,该二进制对应的十进制为 1360,将这个值乘以 12 位精度 0.0625,所以可以得到+85 度。
又比如要计算 -0.5 度,数据输出十六进制是 FFF8,因为要计算的温度小于0,所以高字节的高 5位为 1,十六进制 FFF8 对应的二进制为 1111 1111 1111 1000,先对这个二进制数进行取反为 0000 0000 0000 0111,再进行加 1 ,则对应的十六进制为0x08, 十六进制 0x08 对应的十进制为 8,将这个值乘以 12 位精度 0.0625,算出来的值为 0.5,因为该温度小于 0 ,所以可以得到 -0.5 度。
读取温度数据
由于 DS18B20是单总线器件,所有的单总线器件都要求采用严格的信号时序,以保数据的完整性。DS18B20 时序包括如下几种:初始化时序、写(0 和 1)时序、 读(0和 1)时序。 DS18B20 发送所有的命令和数据都是字节的低位在前。这里我们简单介绍这几个信号的时序:
初始化时序
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少 480us(该时间的时间范围可以从 480 到 960 微秒),以产生复位脉冲。接着主机释放总线,外部的上拉电阻将单总线拉高,延时 15~60 us,并进入接收模式。接着 DS18B20 拉低总线 60~240 us,以产生低电平应答脉冲,若为低电平,还要做延时,其延时的时间从外部上拉电阻将单总线拉高算起最少要480 微妙。初始化时序图如下:
写时序
写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在 2 次独立的写时序之间至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总线。写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us。写 0时序:主机输出低电平,延时 60us,然后释放总线,延时 2us。写时序图如下:
读时序
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读时序之间至少需要 1us 的恢复时间。每个读时序都由主机发起,至少拉低总线 1us。主机在读时序期间必须释放总线,并且在时序起始后的 15us 之内采样总线状态。读时序图如下:
典型的读时序过程为:主机输出低电平延时 2us,然后主机转入输入模式延时 2us,然后读取单总线当前的电平,然后延时 50us。
DS18B20 的典型温度读取过程为:复位→发 SKIP ROM 命令(0XCC)→ 发开始转换命令(0X44)(启动温度转换) → 延时 → 复位 → 发送 SKIP ROM 命令(0XCC)→ 发读存储器命令(0XBE)→ 连续读出两个字节数据(即温度) → 结束
原理图
程序
将读到的温度值显示在 1602 液晶上,并且保留一位小数位
DS18B20.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include "AllHead.h" bit Get_18B20Ack () { bit ack; EA = 0 ; DS18B20_IO = 0 ; Delay_10us(50 ); DS18B20_IO = 1 ; Delay_10us(6 ); ack = DS18B20_IO; while (!DS18B20_IO); EA = 1 ; return ack; } void DS18B20_Write (u8 dat) { u8 i; EA = 0 ; for (i = 0 ; i < 8 ; i++) { DS18B20_IO = 0 ; _nop_(); _nop_(); if ((dat & 0x01 ) > 0 ) DS18B20_IO = 1 ; else DS18B20_IO = 0 ; dat >>= 1 ; Delay_10us(6 ); DS18B20_IO = 1 ; } EA = 1 ; } u8 DS18B20_Read () { u8 i; u8 dat = 0 ; EA = 0 ; for (i = 0 ; i < 8 ; i++) { DS18B20_IO = 0 ; _nop_(); _nop_(); DS18B20_IO = 1 ; _nop_(); _nop_(); dat >>= 1 ; if (DS18B20_IO) dat |= 0x80 ; Delay_10us(5 ); } EA = 1 ; return dat; } bit DS18B20_Start () { bit ack; ack = Get_18B20Ack(); if (0 == ack) { DS18B20_Write(0xCC ); DS18B20_Write(0x44 ); } return ~ack; } bit DS18B20_Read_Temp (int *temp) { bit ack; u8 LSB, MSB; ack = Get_18B20Ack(); if (0 == ack) { DS18B20_Write(0xCC ); DS18B20_Write(0xBE ); LSB = DS18B20_Read(); MSB = DS18B20_Read(); *temp = ((int )MSB << 8 ) + LSB; } return ~ack; }
DS18B20.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _DS18B20_H_ #define _DS18B20_H_ sbit DS18B20_IO = P3^2 ; bit Get_18B20Ack () ; void DS18B20_Write (u8 dat) ;u8 DS18B20_Read () ; bit DS18B20_Start () ; bit DS18B20_Read_Temp (int *temp) ; #endif
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd1602_Init () ;#endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
Delay.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "AllHead.h" void Delay_10us (u16 ten_us) { while (ten_us--); }
Delay.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include "AllHead.h" bit flag1s = 0 ; u8 IntToString (u8 *str, int dat) ; void main () { bit res; int temp; int intT, decT; u8 len; u8 str[12 ]; Time0_Init(10 ); DS18B20_Start(); Lcd1602_Init(); while (1 ) { if (flag1s) { flag1s = 0 ; res = DS18B20_Read_Temp(&temp); if (res) { intT = temp >> 4 ; decT = temp & 0x0F ; len = IntToString(str, intT); str[len++] = '.' ; decT = (decT * 10 ) / 16 ; str[len++] = decT + '0' ; while (len < 6 ) { str[len++] = ' ' ; } str[len] = '\0' ; Lcd_ShowStr(0 , 0 , str); } else { Lcd_ShowStr(0 , 0 , "error!" ); } DS18B20_Start(); } } } u8 IntToString (u8 *str, int dat) { signed char i = 0 ; u8 len = 0 ; u8 buf[6 ]; if (dat < 0 ) { dat = -dat; *str++ = '-' ; len++; } do { buf[i++] = dat % 10 ; dat /= 10 ; } while (dat > 0 ); len += i; while (i-- > 0 ) { *str++ = buf[i] + '0' ; } *str = '\0' ; return len; } void time0 () interrupt 1{ static u8 count = 0 ; TH0 = T0RH; TL0 = T0RL; count++; if (count >= 100 ) { count = 0 ; flag1s = 1 ; } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "time.h" #include "Lcd1602.h" #include "intrins.h" #include "Delay.h" #include "DS18B20.h" #endif
模数转换 A/D 与数模转换 D/A
A/D 和 D/A 的基本概念
A/D 是模拟量到数字量的转换,依靠的是模数转换器(ADC)。D/A 是数字量到模拟量的转换,依靠的是数模转换器(DAC)
模拟量:指变量在一定范围内连续变化的量,也就是在一定范围内可以取任意值,总之,任何两个数字之间都有无限个中间值,所以称之为连续变化的量,也就是模拟量
ADC 起到把连续的信号用离散的数字表达出来的作用
我们往杯子里倒水,水位会随着倒入的水量的多少而变化。现在就用这个米尺来测量我们杯子里的水位的高度。水位变化是连续的,而我们只能通过尺子上的刻度来读取水位的高度,获取我们想得到的水位的数字量信息。这个过程,就可以简单理解为我们电路中的 ADC 采样
A/D 的主要指标
AD 的种类很多,分为积分型、逐次逼近型、并行/串行比较型、Σ-Δ型等多种类型
ADC 的位数
一个 n 位的 ADC 表示这个 ADC 共有 2 的 n 次方个刻度。8 位的 ADC,输出的是从 0 ~ 255 一共 256 个数字量,也就是 2 的 8 次方个数据刻度
基准源
基准源,也叫基准电压,是 ADC 的一个重要指标,要想把输入 ADC 的信号测量准确, 那么基准源首先要准,基准源的偏差会直接导致转换结果的偏差。假如我们的基准源应该是 5.10 V,但是实际上提供的却是 4.5 V, 这样误把 4.5 V 当成了 5.10 V 来处理的话,偏差会比较大
分辨率
分辨率是数字量变化一个最小刻度时,模拟信号的变化量,定义为满刻度量程与 2^n -1 的 比值。假定 5.10V 的电压系统,使用 8 位的 ADC 进行测量,那么相当于 0~255 一共 256 个 刻度把 5.10V 平均分成了 255 份,那么分辨率就是 5.10 / 255 = 0.02V
INL(积分非线性度)和 DNL(差分非线性度)
分辨率是用来描述刻度划分的,而精度是用来描述准确程度的
和 ADC 精度关系重大的两个指标是 INL 和 DNL
INL 指的是 ADC 器件在所有的数值上对应的模拟值和真实值之间误差最大的那一个点的误差值,是 ADC 最重要的一个精度指标,单位是 LSB。LSB 是最低有效位的意思,那么它实际上对应的就是 ADC 的分辨率。一个基准为 5.10V 的 8 位 ADC, 它的分辨率就是 0.02V (5.10 / 255 = 0.02V),用它去测量一个电压信号,得到的结果是 100,就表示它测到的电压值是 100*0.02V=2V,假定它的 INL 是 1 LSB,就表示这个电压信号真实的准确值是在 1.98V~2.02V 之间的,按理想情况对应得到的数字应该是 99~101,测量误差是一个最低有效位,即 1LSB
DNL 表示的是 ADC 相邻两个刻度之间最大的差异,单位也是 LSB。一把分辨率是 1 毫米的尺子,相邻的刻度之间并不都刚好是 1 毫米,而总是会存在或大或小的误差。同理,一个 ADC 的两个刻度线之间也不总是准确的等于分辨率,也是存在误差,这个误差就是 DNL。 一个基准为 5.10V 的 8 位 ADC,假定它的 DNL 是 0.5 LSB,那么当它的转换结果从 100 增加 到 101 时,理想情况下实际电压应该增加 0.02V,但 DNL 为 0.5LSB 的情况下实际电压的增加值是在 0.01~0.03V 之间。 DNL 并非一定小于 1LSB,很多时候它会等于或大于 1LSB,这就相当于是一定程度上的刻度紊乱,当实际电压保持不变时,ADC 得出的结果可能会在几个数值之间跳动,很大程度上就是由于这个原因
转换速率
转换速率,是指 ADC 每秒能进行采样转换的最大次数,单位是 sps(或 s/s、sa/s),它与 ADC 完成一次从模拟到数字的转换所需要的时间互为倒数关系。ADC 的种类比较多,其中积分型的 ADC 转换时间是毫秒级的,属于低速 ADC;逐次逼近型 ADC 转换时间是微秒级的,属于中速 ADC;并行/串行的 ADC 的转换时间可达到纳秒级,属于高速 ADC
PCF8591 的硬件接口
PCF8591 是一个单电源低功耗的 8 位 CMOS 数据采集器件,具有 4 路模拟输入,1 路模拟输出和一个串行 I2C 总线接口用来与单片机通信。与24C02 类似,3 个地址引脚 A0、A1、A2 用于编程硬件地址,允许最多 8 个器件连接到 I2C 总线而不需要额外的片选电路。器件的地址、控制以及数据都是通过 I2C 总线来传输
PCF8591 的原理图
引脚 1、2、3、4 是 4 路模拟输入,引脚 5、6、7 是 I2C 总线的硬件地址,8 脚是数字地 GND,9 脚和 10 脚是 I2C 总线的 SDA 和 SCL。12 脚是时钟选择引脚,如果接高电平表示用外部时钟输入,接低电平则用内部时钟,我们板子上用的是内部时钟,因此 12 脚直接接 GND,同时 11 脚悬空。13 脚是模拟地 AGND,在实际开发中,如果有比较复杂的模拟电路, AGND 部分在布局布线上要特别处理,而且和 GND 的连接也有多种方式,板子上没有复杂的模拟部分电路,所以把 AGND 和 GND 接到一起。14 脚是基准源,15 脚是 DAC 的模拟输出,16 脚是供电电源 VCC
PCF8591 的 ADC 是逐次逼近型的,转换速率算是中速,但是它的速度瓶颈在 I2C 通信 上。由于 I 2C 通信速度较慢,所以最终的 PCF8591 的转换速度,直接取决于 I2C 的通信速率。 由于 I2C 速度的限制,所以 PCF8591 得算是个低速的 AD 和 DA 的集成,主要应用在一些转换速度要求不高,希望成本较低的场合
Vref 基准电压的提供有两种方法。一是采用简易的原则,直接接到 VCC 上去,但是由于 VCC 会受到整个线路的用电功耗情况影响,一来不是准确的 5V,实测大多在 4.8V 左右, 二来随着整个系统负载情况的变化会产生波动,所以只能用在简易的、对精度要求不高的场合。方法二是使用专门的基准电压器件,比如 TL431,它可以提供一个精度很高的 2.5V 的电压基准,如图所示:
图中 J17 是双排插针,可以根据自己的需求选择跳线帽短接还是使用杜邦线连接其它外部电路,二者都是可以的。在这个地方,直接把 J17 的 3 脚和 4 脚用跳线帽短路起来,那么 Vref 的基准源就是 2.5V 了。分别把 5 和 6、7 和 8、9 和 10、11 和 12 用跳线帽短接起来的话,那么 AIN0 实测的就是电位器的分压值,AIN1 和 AIN2 测的是 GND 的值,AIN3 测的是+5V 的值。这里需要注意的是,AIN3 虽然测的是+5V 的值,但是对于 AD 来说,只要输入信号超过 Vref 基准源,它得到的始终都是最大值,即 255,也就是说它实际上无法测量超过其 Vref 的电压信号的。需要注意的是,所有输入信号的电压值都不能超过 VCC,即+5V,否则可能会损坏 ADC 芯片
PCF8591 的软件编程
PCF8591 的通信接口是 I2C,那么编程肯定是要符合这个协议的。单片机对 PCF8591 进行初始化,一共发送三个字节即可。第一个字节,和 EEPROM 类似,是器件地址字节,其中 7 位代表地址,1 位代表读写方向。地址高 4 位固定是 0b1001,低三位是 A2,A1,A0, 这三位电路上都接了 GND,因此也就是 0b000,如图 所示
发送到 PCF8591 的第二个字节将被存储在控制寄存器,用于控制 PCF8591 的功能。其中第 3 位和第 7 位是固定的 0,另外 6 位各自有各自的作用,如图所示
控制字节的第 6 位是 DA 使能位,这一位置 1 表示 DA 输出引脚使能,会产生模拟电压输出功能。第 4 位和第 5 位可以实现把 PCF8591 的 4 路模拟输入配置成单端模式和差分模式,如图所示:
● 第 3、7 位 固定为 0
● 控制字节的 第 6 位是 DA 使能位 ,这一位置 1 表示 DA 输出引脚使能,会产生模拟电压输出功能
● 第 4 位和第 5 位 可以实现把 PCF8591 的 4 路模拟输入 配置成 单端模式和差分模式
● 第 2 位 是 自动增量控制位 ,自动增量的意思就是,比如我们一共有 4 个通道,当我们全部使用的时候,读完了通道 0,下一次再读,会自动进入通道 1 进行读取,不需要我们指定下一个通道,由于 A/D 每次读到的数据,都是上一次的转换结果,所以在使用自动增量功能的时候,要特别注意, 当前读到的是上一个通道的值
● 控制字节的 第 0 位和第 1 位 就是 通道0~3选择位 ( 00、01、10、11)
程序
AIN0、AIN1、AIN3 测到的电压值显示在液晶上,转动电位器会发现 AIN0 的值发生变化
简单显示 AIN0,AIN1,AIN3 电压在液晶(因为AIN2跟AIN1一样故没显示)
显示结果是 AIN1接地故保持是 0V,AIN3 是基准源2.5V故也保持 2.5V,AIN0可以通过扭动板子电位器使输出 0~2.5V
这里控制字节是 0x40|chn(也就是只使能了DA和选择通道号其余的暂时不操作)
val = (val*25) / 255; 255:因为这是8位的ADC,输出数字量是0~255
GetADCValue()函数里那两条读语句作用:当前的转换结果总是在下一个字节的 8 个 SCL 上才能读出,因此这里第一条语句的作用是产生一个整体的 SCL 时钟提供给 PCF8591 进行 A/D 转换, 第二次是读取当前的转换结果。如果我们只使用第二条语句的话,每次读到的都是上一次的转换结果
Lcd1602.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "AllHead.h" void Lcd_WaitReady () { u8 sta = 0 ; LCD1602_DB = 0xFF ; LCD1602_RS = 0 ; LCD1602_RW = 1 ; do { LCD1602_E = 1 ; sta = LCD1602_DB; LCD1602_E = 0 ; } while (sta & 0x80 ); } void Lcd_WriteCmd (u8 cmd) { Lcd_WaitReady(); LCD1602_RS = 0 ; LCD1602_RW = 0 ; LCD1602_DB = cmd; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_WriteDat (u8 dat) { Lcd_WaitReady(); LCD1602_RS = 1 ; LCD1602_RW = 0 ; LCD1602_DB = dat; LCD1602_E = 1 ; LCD1602_E = 0 ; } void Lcd_SetCursor (u8 x, u8 y) { u8 addr; if (0 == y) { addr = 0x00 + x; } else { addr = 0x40 + x; } Lcd_WriteCmd(addr | 0x80 ); } void Lcd_ShowStr (u8 x, u8 y, u8 *str) { Lcd_SetCursor(x, y); while (*str != '\0' ) { Lcd_WriteDat(*str++); } } void Lcd1602_Init () { Lcd_WriteCmd(0x38 ); Lcd_WriteCmd(0x0C ); Lcd_WriteCmd(0x06 ); Lcd_WriteCmd(0x01 ); }
Lcd1602.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _LCD1602_H_ #define _LCD1602_H_ #define LCD1602_DB P0 sbit LCD1602_RS = P1^0 ; sbit LCD1602_RW = P1^1 ; sbit LCD1602_E = P1^5 ; void Lcd_WaitReady () ;void Lcd_WriteCmd (u8 cmd) ;void Lcd_WriteDat (u8 dat) ;void Lcd_SetCursor (u8 x, u8 y) ;extern void Lcd_ShowStr (u8 x, u8 y, u8 *str) ;extern void Lcd1602_Init () ;#endif
I2C.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include "AllHead.h" #define I2C_Delay() {_nop_();_nop_();_nop_();_nop_();} void I2C_Start () { I2C_SCL = 1 ; I2C_SDA = 1 ; I2C_Delay(); I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 0 ; } void I2C_Stop () { I2C_SCL = 0 ; I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SDA = 1 ; I2C_Delay(); } bit I2C_Write (u8 dat) { bit ack; u8 i; for (i = 0 ; i < 8 ; i++) { if ((dat & 0x80 ) > 0 ) I2C_SDA = 1 ; else I2C_SDA = 0 ; dat <<= 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; ack = I2C_SDA; I2C_Delay(); I2C_SCL = 0 ; return (~ack); } u8 I2C_ReadNACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; } u8 I2C_ReadACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; }
I2C.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _I2C_H_ #define _I2C_H_ sbit I2C_SCL = P3^7 ; sbit I2C_SDA = P3^6 ; void I2C_Start () ;void I2C_Stop () ;bit I2C_Write (u8 dat) ; u8 I2C_ReadNACK () ; u8 I2C_ReadACK () ; #endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include "AllHead.h" bit flag300ms = 1 ; u8 IntToString (u8 *str, int dat) ; u8 ADC_Get_Value (u8 chn) ; void ValueToString (u8 *str, u8 val) ;void main () { u8 val; u8 str[10 ]; Time0_Init(10 ); Lcd1602_Init(); Lcd_ShowStr(0 , 0 , "AIN0 AIN1 AIN3" ); while (1 ) { if (flag300ms) { flag300ms = 0 ; val = ADC_Get_Value(0 ); ValueToString(str, val); Lcd_ShowStr(0 , 1 , str); val = ADC_Get_Value(1 ); ValueToString(str, val); Lcd_ShowStr(6 , 1 , str); val = ADC_Get_Value(3 ); ValueToString(str, val); Lcd_ShowStr(12 , 1 , str); } } } u8 ADC_Get_Value (u8 chn) { u8 val; I2C_Start(); if (!I2C_Write(0x90 )) { I2C_Stop(); return 0 ; } I2C_Write(0x40 | chn); I2C_Start(); I2C_Write(0x91 ); I2C_ReadACK(); val = I2C_ReadNACK(); I2C_Stop(); return val; } void ValueToString (u8 *str, u8 val) { val = (val * 25 ) / 255 ; str[0 ] = (val / 10 ) + '0' ; str[1 ] = '.' ; str[2 ] = (val % 10 ) + '0' ; str[3 ] = 'V' ; str[4 ] = '\0' ; } void time0 () interrupt 1{ static u8 count = 0 ; TH0 = T0RH; TL0 = T0RL; count++; if (count >= 30 ) { count = 0 ; flag300ms = 1 ; } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "time.h" #include "Lcd1602.h" #include "I2C.h" #include "intrins.h" #endif
A/D 差分输入信号
控制字的第 4 位和第 5 位是用于控制 PCF8591 的模拟输入引脚是 单端输入还是差分输入
从严格意义上来讲,其实所有的信号都是差分信号,因为所有的电压只能是相对于另外一个电压而言。但是大多数系统,我们都是把系统的 GND 作为基准点。而对于 A/D 来说的差分输入,通常情况下是除了 GND 以外,另外两路幅度相同,极性相反的输入信号
差分输入的话,就不是单个输入,而是由 2 个输入端构成的一组输入。PCF8591 一共是 4 个模拟输入端,可以配置成 4 种模式,最典型的是 4 个输入端构造成的两路差分模式,如图所示:
当控制字的第 4 位和第 5 位都是 1 的时候,那么 4 路模拟被配置成 2 路差分模式输入 channel 0 和 channel 1。以 channel 0 为例,其中 AIN0 是正向输入端,AIN1 是反向输入端,它们之间的信号输入是幅度相同,极性相反的信号,通过减法器后,得到的是两个输入通道的差值,如图所示
通常情况下,差分输入的中线是基准电压的一半,我们的基准电压是 2.5V,假如 1.25V 作为中线,V+是 AIN0 的输入波形,V-是 AIN1 的输入波形,Signal Value 就是经过减法器后的波形。很多 A/D 都采用差分的方式输入,因为差分输入方式比单端输入来说,有更强的抗干扰能力
单端输入信号时,如果一线上发生干扰变化,比如幅度增大 5mv,GND 不变,测到的数据会有偏差;而差分信号输入时,当外界存在干扰信号时,只要布线合理,大都同时被耦合到两条线上,幅度增大 5mv 会同时增大 5mv,而接收端关心的只是两个信号的差值,所以外界的这种共模噪声可以被完全抵消掉。由于两根信号的极性相反,它们对外辐射的电磁场可以相互抵消,有效的抑制释放到外界的电磁能量
D/A 输出
D/A 是和 A/D 刚好反方向的,一个 8 位的 D/A,从 0~255,代表了 0~2.55V 的话,那么用单片机给第三个字节发送 100,D/A 引脚就会输出一个 1V 的电压,发送 200 就输出 一个 2V 的电压
D/A 输出实验
用单片机给第三个字节发送 100,D/A 引脚就会输出一个 1V 的电压,发送 200 就输出 一个 2V 的电压,并且通过上、下按键可以增大或减小输出幅度值,每次增加或减小 0.1V。如果有万用表的话,可以直接测试一下板子上 AOUT 点的输出电压,观察它的变化
key.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include "AllHead.h" u8 code KeyCodeMap[4 ][4 ] = { { '1' , '2' , '3' , 0x26 }, { '4' , '5' , '6' , 0x25 }, { '7' , '8' , '9' , 0x28 }, { '0' , 0x1B , 0x0D , 0x27 } }; u8 pdata Key_State[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; void Key_Driver () { u8 i, j; static u8 backup[4 ][4 ] = {{1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }, {1 , 1 , 1 , 1 }}; for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 4 ; j++) { if (backup[i][j] != Key_State[i][j]) { if (backup[i][j] != 0 ) { Key_Action(KeyCodeMap[i][j]); } backup[i][j] = Key_State[i][j]; } } } } void Key_Scan () { u8 i = 0 ; static u8 KeyOut = 0 ; static u8 keybuf[4 ][4 ] = {{0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF }, {0xFF , 0xFF , 0xFF , 0xFF } }; keybuf[KeyOut][0 ] = (keybuf[KeyOut][0 ] << 1 ) | KeyIn1; keybuf[KeyOut][1 ] = (keybuf[KeyOut][1 ] << 1 ) | KeyIn2; keybuf[KeyOut][2 ] = (keybuf[KeyOut][2 ] << 1 ) | KeyIn3; keybuf[KeyOut][3 ] = (keybuf[KeyOut][3 ] << 1 ) | KeyIn4; for (i = 0 ; i < 4 ; i++) { if (0x00 == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 0 ; } else if (0x0F == (keybuf[KeyOut][i] & 0x0F )) { Key_State[KeyOut][i] = 1 ; } } KeyOut++; if (KeyOut >= 4 ) { KeyOut = 0 ; } switch (KeyOut) { case 0 : KeyOut4 = 1 ; KeyOut1 = 0 ; break ; case 1 : KeyOut1 = 1 ; KeyOut2 = 0 ; break ; case 2 : KeyOut2 = 1 ; KeyOut3 = 0 ; break ; case 3 : KeyOut3 = 1 ; KeyOut4 = 0 ; break ; default : break ; } }
key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef _KEY_H_ #define _KEY_H_ sbit KeyIn1 = P2^4 ; sbit KeyIn2 = P2^5 ; sbit KeyIn3 = P2^6 ; sbit KeyIn4 = P2^7 ; sbit KeyOut1 = P2^3 ; sbit KeyOut2 = P2^2 ; sbit KeyOut3 = P2^1 ; sbit KeyOut4 = P2^0 ; extern void Key_Driver () ;extern void Key_Scan () ;extern void Key_Action (u8 keycode) ;#endif
I2C.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include "AllHead.h" #define I2C_Delay() {_nop_();_nop_();_nop_();_nop_();} void I2C_Start () { I2C_SCL = 1 ; I2C_SDA = 1 ; I2C_Delay(); I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 0 ; } void I2C_Stop () { I2C_SCL = 0 ; I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SDA = 1 ; I2C_Delay(); } bit I2C_Write (u8 dat) { bit ack; u8 i; for (i = 0 ; i < 8 ; i++) { if ((dat & 0x80 ) > 0 ) I2C_SDA = 1 ; else I2C_SDA = 0 ; dat <<= 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; ack = I2C_SDA; I2C_Delay(); I2C_SCL = 0 ; return (~ack); } u8 I2C_ReadNACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 1 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; } u8 I2C_ReadACK () { u8 receive = 0 ; u8 i; I2C_SDA = 1 ; for (i = 0 ; i < 8 ; i++) { I2C_SCL = 0 ; I2C_Delay(); I2C_SCL = 1 ; receive <<= 1 ; if (1 == I2C_SDA) receive++; I2C_Delay(); I2C_SCL = 0 ; } I2C_SDA = 0 ; I2C_Delay(); I2C_SCL = 1 ; I2C_Delay(); I2C_SCL = 0 ; return receive; }
I2C.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef _I2C_H_ #define _I2C_H_ sbit I2C_SCL = P3^7 ; sbit I2C_SDA = P3^6 ; void I2C_Start () ;void I2C_Stop () ;bit I2C_Write (u8 dat) ; u8 I2C_ReadNACK () ; u8 I2C_ReadACK () ; #endif
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "AllHead.h" u8 T0RH = 0 ; u8 T0RL = 0 ; void Time0_Init (u16 ms) { u32 temp = 0 ; temp = 11059200 / 12 ; temp = (temp * ms) / 1000 ; temp = 65536 - temp; temp += 18 ; T0RH = (unsigned char )(temp >> 8 ); T0RL = (unsigned char )temp; EA = 1 ; TMOD &= 0xF0 ; TMOD |= 0x01 ; TH0 = T0RH; TL0 = T0RL; ET0 = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 9 #ifndef _TIME_H_ #define _TIME_H_ extern u8 T0RH; extern u8 T0RL;void Time0_Init (u16 ms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include "AllHead.h" void Key_Action (u8 keycode) ;void SetDACOut (unsigned char val) ;void main () { Time0_Init(1 ); while (1 ) { Key_Driver(); } } void Key_Action (u8 keycode) { static u8 volt = 0 ; if (0x26 == keycode) { if (volt < 25 ) { volt++; SetDACOut(volt * 255 / 25 ); } } else if (keycode == 0x28 ) { if (volt > 0 ) { volt--; SetDACOut(volt * 255 / 25 ); } } } void SetDACOut (unsigned char val) { I2C_Start(); if (!I2C_Write(0x90 )) { I2C_Stop(); return ; } I2C_Write(0x40 ); I2C_Write(val); I2C_Stop(); } void time0 () interrupt 1{ TH0 = T0RH; TL0 = T0RL; Key_Scan(); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef _ALLHEAD_H_ #define _ALLHEAD_H_ typedef unsigned char u8; typedef unsigned int u16;typedef unsigned long u32;#include <reg52.h> #include "key.h" #include "time.h" #include "I2C.h" #include "intrins.h" #endif