前言
这个板子是普中A2 STC89C52RC的板子,下面为开发板各功能模块
单片机最小系统 :(1)晶振电路(2)复位电路(3)电源电路(4)下载电路
GPIO概念 :GPIO是通用输入输出端口的简称,可以通过软件来控制其输入和输出。
开发板上使用的 51 单片机型号是 STC89C52RC,此芯片共有 40 引脚,芯片引脚图如下图所示:
51单片机引脚分类:
(1)电源引脚:引脚图中的VCC、GND都属于电源引脚。
(2)晶振引脚:引脚图中的XTAL1、XTAL2都属于晶振引脚。
(3)复位引脚:引脚图中的RST/VPD属于复位引脚,不做其他功能使用。
(4)下载引脚:51单片机的串口功能引脚(TXD、RXD)可以作为下载引脚使用。
(5)GPIO引脚:引脚图中带有Px.x等字样的均属于GPIO引脚。从引脚图可以看出,GPIO占用了芯片大部分的引脚,共达32个,分为了4组,P0、P1、P2、P3,每组为8个IO,而且在P3组中每个IO都具备额外功能,只要通过相应的寄存器设置即可配置对应的附加功能,同一时刻,每个引脚只能使用该引脚的一个功能
LED
LED简介
LED(light-emitting diode),即 发光二极管,俗称LED小灯,是一种由磷化镓(GaP)等半导体材料制成的、能直接将电能转变成光能的发光显示件。当LED内部有一定电流通过时,它就会发光,不同LED能发出不同颜色的光,常见的有红色、黄色等。
LED原理图
LED程序
控制LED闪烁
因为是正极流进来,想要点亮LED灯所以要给P2.0低电平,单片机初始状态管脚默认为高电平,即对应值为1。所以如果想要点亮LED灯,只需要让他的管脚电平为低电平即可,即对应值为0。
LED.c
1 2 3 4 5 6 7 8 9 10 11 #include "AllHead.h" void LED_flicker () { LED1 = 0 ; delay_10us(50000 ); LED1 = 1 ; delay_10us(50000 ); }
LED.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef __LED_H #define __LED_H #include "AllHead.h" sbit LED1 = P2^0 ; void LED_flicker () ;#endif
Delay.c
1 2 3 4 5 6 7 8 9 10 11 12 #include "AllHead.h" void delay_10us (u16 ten_us) { while (ten_us--); }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;#endif
main.c
1 2 3 4 5 6 7 8 9 #include "AllHead.h" void main ( ) { while (1 ) { LED_flicker(); } }
AllHead.h
将所有的头文件放到AllHead.h,这样子就比较方便,直接在各个模块下调用这个头文件即可
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "LED.h" #include "Delay.h" #endif
LED流水灯
~ 这个符号表示按位取反 (0变1 1变0)
LED.c
1 2 3 4 5 6 7 8 9 10 11 12 13 #include "AllHead.h" void LED_fall () { u8 i = 0 ; for (i = 0 ;i < 8 ;i++) { LED_PORT = ~(0x01 << i); delay_10us(50000 ); } }
LED.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef __LED_H #define __LED_H #include "AllHead.h" #define LED_PORT P2 void LED_fall () ;#endif
Delay.c
1 2 3 4 5 6 7 8 9 10 11 12 #include "AllHead.h" void delay_10us (u16 ten_us) { while (ten_us--); }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;#endif
main.c
1 2 3 4 5 6 7 8 9 #include "AllHead.h" void main ( ) { while (1 ) { LED_fall(); } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "LED.h" #include "Delay.h" #endif
用库函数写LED灯左右来回流水
还有一种流水灯的做法是用库函数:#include “intrins.h”
crol_和_cror都是库函数,每次移动后不会自动补0而是把前面移出去的补到后面去
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 #include "AllHead.h" void LED_ku_fall () { u8 i = 0 ; LED_PORT = ~0x01 ; Delay1ms(500 ); for (i = 0 ;i < 7 ;i++) { LED_PORT = _crol_(LED_PORT,1 ); Delay1ms(500 ); } for (i = 0 ;i < 7 ;i++) { LED_PORT = _cror_(LED_PORT,1 ); Delay1ms(500 ); } }
LED.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef __LED_H #define __LED_H #include "AllHead.h" #define LED_PORT P2 void LED_ku_fall () ;#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 #include "AllHead.h" void main ( ) { while (1 ) { LED_ku_fall(); } }
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "LED.h" #include "Delay.h" #include "intrins.h" #endif
蜂鸣器
蜂鸣器简介
蜂鸣器:是一种一体化结构的电子讯响器,采用直流电压供电,分为 压电式蜂鸣器 和 电磁式蜂鸣器(无源和有源), 51 开发板一般是 压电式 的蜂鸣器。
根据网上查的资料可以知道无源和有源的区别:
(1)有无震荡源
无源这里的“源”不是指电源,而是指震荡源。也就是说,有源蜂鸣器内部带震荡源,所以只要一通电就会叫。而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。
(2)价格不同
有源蜂鸣器往往比无源蜂鸣器贵,就是因为里面多个震荡电路。
怎么区分?
有绿色电路板的一种是无源蜂鸣器,没有电路板而用黑胶封闭的一种是有源蜂鸣器。
无源蜂鸣器通过脉冲频率才能发声。
蜂鸣器原理图
从图中可以看出,蜂鸣器控制管脚直接连接到 51 单片机的 P2.5 管脚上。图中并没有使用三极管进行电流放大,而是使用 ULN2003 芯片来驱动,当 P2.5 输入高电平,BEEP 则输出低电平;当 P2.5 输入低电平,BEEP 则输出高电平,类似一个非门
开发板上使用的是无源蜂鸣器,它需要一定频率的脉冲(高低电平)才会发声,因此需要让 P2.5 脚以一定频率不断输出高低电平信号才能控制蜂鸣器发出声音
蜂鸣器程序
蜂鸣器发出声音,一段时间后关闭
BEEP.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "AllHead.h" void BEEP_Contral () { u16 i = 2000 ; while (1 ) { while (i--) { BEEP = !BEEP; Delay1ms(1 ); } i = 0 ; BEEP = 0 ; } }
BEEP.h
1 2 3 4 5 6 7 8 9 10 #ifndef _BEEP_H_ #define _BEEP_H_ #include "AllHead.h" sbit BEEP = P2^5 ; void BEEP_Contral () ;#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 void main ( ) { while (1 ) { BEEP_Contral(); } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "BEEP.h" #endif
修改变量 i 的值可以改变蜂鸣器发声时间
若要改变音调可以修改延时时间,但要注意频率不能太大或者太小,若要改变音量,可以修改 BEEP 输出高电平时间
数码管
数码管简介
数码管是一种半导体发光器件,其基本单元是发光二极管。按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管
共阳数码管 是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极 COM 接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮
共阴数码管 是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴数码管在应用时应将公共极 COM 接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮
数码表
共阴数码管码表
共阳数码管码表
从上述共阳和共阴码表中不难发现,它们的数据正好是相互取反的值。比如共阴数码管数字 0 段码:0x3f,其二进制是:0011 1111,取反后为:1100 0000, 转换成 16 进制即为 0XC0。其他段码依此类推。该段码数据由来,是将 a 段作为最低位,b 段作为次低位,其他按顺序类推,dp 段为最高位,共 8 位,正好和 51 单片机的一组端口数一样,因此可以直接使用某一组端口控制数码管的段选数据口,比如 P0 口
原理图
数码管显示
静态显示 :多位数码管依然可以静态显示,但是显示时要么只显示一位数码管,要么多位同时显示相同内容。当多位数码管应用于某一系统时,它们的“位选”是可独立控制的,而“段选”是连接在一起的,我们可以通过位选信号控制哪几个数码管亮,而在同一时刻,位选选通的所有数码管上显示的数字始终都是一样的,因为它们的段选是连接在一起的,送入所有数码管的段选信号都是相同的,所以它们显示的数字必定一样,数码管的这种显示方法叫做静态显示。
动态显示 :就是利用减少段选线,分开位选线,利用位选线不同时选择通断,改变段选数据来实现的。比如在第一次选中第一位数码管时,给段选数据 0, 下一次位选中第二位数码管时显示 1。为了在显示 1 的时候,0 不会消失(当然实际上是消失了),必须在人肉眼观察不到的时间里再次点亮第一次点亮的 0。 而这时就需要记住,人的肉眼正常情况下只能分辨变化超过 24ms 间隔的运动。 也就是说,在下一次点亮 0 这个数字的时间差不得大于 24ms。这时就会发现, 数码管点亮是在向右或者向左一位一位点亮,形成了动态效果。如果把间隔时间改长就能直接展现这一现象。
74HC245芯片
74HC245 是一种三态输出、八路信号收发器,主要应用于大屏显示,以及其它的消费类电子产品中增加驱动。
(1)主要特性
①采用 CMOS 工艺
②宽电压工作范围:3.0V-5.0V
③双向三态输出
④八线双向收发器
⑤封装形式:SOP20、SOP20-2、TSSOP20、DIP20
(2)管脚功能定义
从上面的管脚功能定义说明及真值表可以知道该芯片使用方法很简单,给 OE 使能管脚低电平,DIR 管脚为高电平,传输方向是 A->B, DIR 管脚为低电平,传输方向是 B->A,至于输出高电平还是输出低电平取决于输入端的状态,如果输入为低电平,输出即为低;输入为高电平,输出即为高。如果 OE 使能管脚为高电平, 不论 DIR 管脚是高还是低,输出是高组态。 通常我们使用 74HC245 芯片用作驱动只会让其在一个方向输出,即 DIR 管脚高电平,传输方向是 A-->B
74HC138 芯片
74HC138D 是一种三通道输入、八通道输出译码器,主要应用于消费类电子产品
(1)主要特性
①采用CMOS 工艺
②低功耗
③工作电压:3.0V-5.0V
④封装形式:SOP16
(2)管脚功能定义
头上有一横表示在低电平时有效
真值:
L:表示低电平0
H:表示高电平1
X:表示无论是高电平还是低电平都不影响真值
从上面的管脚功能定义说明及真值表可以知道该芯片使用方法很简单,给 E1、E2 使能管脚低电平,E3 管脚为高电平,至于哪个管脚输出有效电平(低电平),要看 A0,A1,A2 输入管脚的电平状态。如果 A0,A1,A2 都为低电平,则 Y0 输出有效电平(低电平),其他管脚均输出高电平。如果 A0 为高电平,A1, A2 都为低电平,则 Y1 输出有效电平(低电平),其他管脚均输出高电平。如果 E1、E2 使能管脚任意一个为高电平或者 E3 为低电平,不论输入是什么,输出都为高电平。
方法:A0、A1、A2 输入就相当于 3 位 2 进制数,A0 是低位,A1 是次高位,A2 是高位。而 Y0-Y7 具体哪一个输出有效电平,就看输入二进制对应的十进制数值。比如输入是 101(A2,A1,A0),其对应的十进制数是 5,所以Y5输出有效电平(低电平)。
数码管程序
静态数码管实验
实验现象:下载程序后“数码管模块”最左边数码管显示数字 0
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "AllHead.h" #define SMG_A_DP_PORT P0 u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f ,0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; void main ( ) { SMG_A_DP_PORT = gsmg_code[0 ]; while (1 ) { } }
动态数码管实验
从左到右显示数字1到7
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 #include "AllHead.h" u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f ,0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; void smg_display () { u8 i = 0 ; for (i = 0 ;i < 8 ;i++) { switch (i) { case 0 : LSC = 1 ;LSB = 1 ;LSA = 1 ;break ; case 1 : LSC = 1 ;LSB = 1 ;LSA = 0 ;break ; case 2 : LSC = 1 ;LSB = 0 ;LSA = 1 ;break ; case 3 : LSC = 1 ;LSB = 0 ;LSA = 0 ;break ; case 4 : LSC = 0 ;LSB = 1 ;LSA = 1 ;break ; case 5 : LSC = 0 ;LSB = 1 ;LSA = 0 ;break ; case 6 : LSC = 0 ;LSB = 0 ;LSA = 1 ;break ; case 7 : LSC = 0 ;LSB = 0 ;LSA = 0 ;break ; } SMG_A_DP_PORT = gsmg_code[i]; Delay1ms(1 ); SMG_A_DP_PORT = 0x00 ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display () ;extern u8 gsmg_code[17 ];#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 #include "AllHead.h" void main ( ) { while (1 ) { smg_display(); } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "smg.h" #endif
动态数码管显示日期
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 #include "AllHead.h" u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f ,0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; void smg_display (u8 location,u8 number) { switch (location) { case 0 : LSC = 1 ;LSB = 1 ;LSA = 1 ;break ; case 1 : LSC = 1 ;LSB = 1 ;LSA = 0 ;break ; case 2 : LSC = 1 ;LSB = 0 ;LSA = 1 ;break ; case 3 : LSC = 1 ;LSB = 0 ;LSA = 0 ;break ; case 4 : LSC = 0 ;LSB = 1 ;LSA = 1 ;break ; case 5 : LSC = 0 ;LSB = 1 ;LSA = 0 ;break ; case 6 : LSC = 0 ;LSB = 0 ;LSA = 1 ;break ; case 7 : LSC = 0 ;LSB = 0 ;LSA = 0 ;break ; } SMG_A_DP_PORT = gsmg_code[number]; Delay1ms(1 ); SMG_A_DP_PORT = 0x00 ; }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 location,u8 number) ;extern u8 gsmg_code[17 ];#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "AllHead.h" void main ( ) { while (1 ) { smg_display(0 ,2 ); smg_display(1 ,0 ); smg_display(2 ,2 ); smg_display(3 ,3 ); smg_display(4 ,0 ); smg_display(5 ,7 ); smg_display(6 ,1 ); smg_display(7 ,3 ); } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "smg.h" #endif
按键
独立按键介绍
按键是一种电子开关,使用时轻轻按开关按钮就可使开关接通,当松开手时, 开关断开
“1,2”和“3,4”管脚之间距离短,初始不导通,“1,3”和“2,4”管脚之间距离长,初始值导通。
当按键按下时,距离短的会变为导通,距离长的会变为不导通 ,所以就可以利用按键这一特性来控制其他的事物。
例如管脚1接单片机的一个引脚,管脚2接地。当按键被按下时,就会给这个引脚一个 低电平 。如果不按,单片机的这个引脚默认的是 高电平 。
但是按键一般都会抖动,所以要进行消抖: 硬件消抖和软件消抖
硬件消抖是通过 充放电延时时间 来进行消抖,但成本高,一个按键就需要(一个电阻与一个电源),所以 一般选择软件消抖,软件消抖时间一般为 10ms 。
按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态, 如果按键按下,初始导通状态变为断开,初始断开状态变为导通。 通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,电压信号如下图所示
独立按键原理图
独立按键程序
独立按键控制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 #include "AllHead.h" u8 key_scan (u8 mode) { static u8 key = 1 ; if (mode) key = 1 ; if (key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0 )) { Delay1ms(10 ); key = 0 ; if (KEY1 == 0 ) return KEY1_PRESS; else if (KEY2 == 0 ) return KEY2_PRESS; else if (KEY3 == 0 ) return KEY3_PRESS; else if (KEY4 == 0 ) return KEY4_PRESS; } else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1 ) { key = 1 ; } return KEY_UNPRESS; }
Key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" sbit KEY1 = P3^1 ; sbit KEY2 = P3^0 ; sbit KEY3 = P3^2 ; sbit KEY4 = P3^3 ; #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 #define KEY_UNPRESS 0 u8 key_scan (u8 mode) ; #endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "AllHead.h" sbit LED1 = P2^0 ; void main ( ) { while (1 ) { key = key_scan(0 ); if (key == KEY1_PRESS) { LED1 = !LED1; } } }
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "LED.h" #include "Delay.h" #include "key.h" #endif
独立按键控制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 #include "AllHead.h" u8 key_scan (u8 mode) { static u8 key = 1 ; if (mode) key = 1 ; if (key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0 )) { Delay1ms(10 ); key = 0 ; if (KEY1 == 0 ) return KEY1_PRESS; else if (KEY2 == 0 ) return KEY2_PRESS; else if (KEY3 == 0 ) return KEY3_PRESS; else if (KEY4 == 0 ) return KEY4_PRESS; } else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1 ) { key = 1 ; } return KEY_UNPRESS; }
Key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" sbit KEY1 = P3^1 ; sbit KEY2 = P3^0 ; sbit KEY3 = P3^2 ; sbit KEY4 = P3^3 ; #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 #define KEY_UNPRESS 0 u8 key_scan (u8 mode) ; #endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (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 #include "AllHead.h" void main ( ) { u8 key = 0 ; u8 LEDNum = ~0x01 ; while (1 ) { key = key_scan(0 ); if (key == KEY1_PRESS) { LEDNum++; if (LEDNum >= 8 ) LEDNum = 0 ; LED_PORT = ~(0x01 << LEDNum); } if (key == KEY2_PRESS) { if (LEDNum == 0 ) LEDNum = 7 ; else LEDNum--; LED_PORT = ~(0x01 << LEDNum); } } }
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "LED.h" #include "Delay.h" #include "key.h" #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 #include "AllHead.h" u8 key_scan (u8 mode) { static u8 key = 1 ; if (mode) key = 1 ; if (key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0 )) { Delay1ms(10 ); key = 0 ; if (KEY1 == 0 ) return KEY1_PRESS; else if (KEY2 == 0 ) return KEY2_PRESS; else if (KEY3 == 0 ) return KEY3_PRESS; else if (KEY4 == 0 ) return KEY4_PRESS; } else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1 ) { key = 1 ; } return KEY_UNPRESS; }
Key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" sbit KEY1 = P3^1 ; sbit KEY2 = P3^0 ; sbit KEY3 = P3^2 ; sbit KEY4 = P3^3 ; #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 #define KEY_UNPRESS 0 u8 key_scan (u8 mode) ; #endif
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 #include "AllHead.h" u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f , 0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; void smg_display (u8 location,u8 number) { switch (location) { case 0 : LSC = 1 ;LSB = 1 ;LSA = 1 ;break ; case 1 : LSC = 1 ;LSB = 1 ;LSA = 0 ;break ; case 2 : LSC = 1 ;LSB = 0 ;LSA = 1 ;break ; case 3 : LSC = 1 ;LSB = 0 ;LSA = 0 ;break ; case 4 : LSC = 0 ;LSB = 1 ;LSA = 1 ;break ; case 5 : LSC = 0 ;LSB = 1 ;LSA = 0 ;break ; case 6 : LSC = 0 ;LSB = 0 ;LSA = 1 ;break ; case 7 : LSC = 0 ;LSB = 0 ;LSA = 0 ;break ; } SMG_A_DP_PORT = gsmg_code[number]; Delay1ms(1 ); SMG_A_DP_PORT = 0x00 ; }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 location,u8 number) ;extern u8 gsmg_code[17 ];#endif
BEEP.c
1 2 3 4 5 6 7 8 9 10 11 #include "AllHead.h" void BEEP_Time (u8 ms) { u8 i = 0 ; for (i = 0 ;i < ms; i++) { BEEP = !BEEP; Delay1ms(1 ); } }
BEEP.h
1 2 3 4 5 6 7 8 9 10 #ifndef _BEEP_H_ #define _BEEP_H_ #include "AllHead.h" sbit BEEP = P2^5 ; void BEEP_Time (u8 ms) ;#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "AllHead.h" u8 key = 0 ; void main ( ) { smg_display(0 ,0 ); while (1 ) { key = key_scan(0 ); if (key) { BEEP_Time(500 ); smg_display(0 ,key); } } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "BEEP.h" #include "smg.h" #include "key.h" #endif
矩阵按键
矩阵按键介绍
在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式 采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态。
矩阵按键原理图
矩阵按键的实现
行列描述法
先让一列为 低电平,其余几列全为高电平(此时我们确定了列数),然后立即轮流检测一次各行是否有低电平, 若检测到某一行为低电平(这时我们又确定了行数)则我们便可确认当前被按下的键是哪一行哪一列的,用同样方法轮流送各列一次低电平,再轮流检测一次各行是否变为 低电平 ,这样即可检测完所有的按键,当有键被按下时便可判断出按下的键是哪一个键。当然我们也可以将行线置低电平,扫描列是否有低电平。从而达到整个键盘的检测。
线翻转法
使所有行线为 低电平 时,检测所有列线是否有 低电平 ,如果有,就记录列线值;然后再翻转,使所有列线都为 低电平,检测所有行线的值由于有按键按下,行线的值也会有变化,记录行线的值。从而就可以检测到全部按键。
矩阵按键程序
行列扫描法_按下矩阵按键S1-S16 键,数码管显示 0-F
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 #include "AllHead.h" u8 key_matrix_ranks_scan (void ) { u8 key_value = 0 ; KEY_MATRIX_PORT = 0xf7 ; if (KEY_MATRIX_PORT != 0xf7 ) { Delay1ms(10 ); switch (KEY_MATRIX_PORT) { case 0x77 : key_value = 1 ; break ; case 0xb7 : key_value = 5 ; break ; case 0xd7 : key_value = 9 ; break ; case 0xe7 : key_value = 13 ; break ; } } while (KEY_MATRIX_PORT != 0xf7 ); KEY_MATRIX_PORT = 0xfb ; if (KEY_MATRIX_PORT != 0xfb ) { Delay1ms(10 ); switch (KEY_MATRIX_PORT) { case 0x7b : key_value = 2 ; break ; case 0xbb : key_value = 6 ; break ; case 0xdb : key_value = 10 ; break ; case 0xeb : key_value = 14 ; break ; } } while (KEY_MATRIX_PORT != 0xfb ); KEY_MATRIX_PORT = 0xfd ; if (KEY_MATRIX_PORT != 0xfd ) { Delay1ms(10 ); switch (KEY_MATRIX_PORT) { case 0x7d : key_value = 3 ; break ; case 0xbd : key_value = 7 ; break ; case 0xdd : key_value = 11 ; break ; case 0xed : key_value = 15 ; break ; } } while (KEY_MATRIX_PORT != 0xfd ); KEY_MATRIX_PORT = 0xfe ; if (KEY_MATRIX_PORT != 0xfe ) { Delay1ms(10 ); switch (KEY_MATRIX_PORT) { case 0x7e : key_value = 4 ; break ; case 0xbe : key_value = 8 ; break ; case 0xde : key_value = 12 ; break ; case 0xee : key_value = 16 ; break ; } } while (KEY_MATRIX_PORT != 0xfe ); return key_value; }
Key.h
1 2 3 4 5 6 7 8 9 10 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" #define KEY_MATRIX_PORT P1 u8 key_matrix_ranks_scan (void ) ; #endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "AllHead.h" void main ( ) { u8 key = 0 ; u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f ,0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; while (1 ) { key = key_matrix_ranks_scan(); if (key != 0 ) SMG_A_DP_PORT = gsmg_code[key - 1 ]; } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "key.h" #endif
线翻转扫描法_按下矩阵按键S1-S16 键,数码管显示 0-F
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 #include "AllHead.h" u8 key_matrix_flip_scan (void ) { static u8 key_value = 0 ; KEY_MATRIX_PORT = 0x0f ; if (KEY_MATRIX_PORT != 0x0f ) { Delay1ms(10 ); if (KEY_MATRIX_PORT != 0x0f ) { KEY_MATRIX_PORT = 0x0f ; switch (KEY_MATRIX_PORT) { case 0x07 : key_value = 1 ; break ; case 0x0b : key_value = 2 ; break ; case 0x0d : key_value = 3 ; break ; case 0x0e : key_value = 4 ; break ; } KEY_MATRIX_PORT = 0xf0 ; switch (KEY_MATRIX_PORT) { case 0x70 : key_value = key_value; break ; case 0xb0 : key_value = key_value + 4 ; break ; case 0xd0 : key_value = key_value + 8 ; break ; case 0xe0 : key_value = key_value + 12 ; break ; } while (KEY_MATRIX_PORT != 0xf0 ); } } else key_value = 0 ; return key_value; }
Key.h
1 2 3 4 5 6 7 8 9 10 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" #define KEY_MATRIX_PORT P1 u8 key_matrix_flip_scan (void ) ; #endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 #include "AllHead.h" void main ( ) { u8 key = 0 ; u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f ,0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; while (1 ) { key = key_matrix_flip_scan(); if (key != 0 ) SMG_A_DP_PORT = gsmg_code[key - 1 ]; } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "key.h" #endif
LED点阵
LED点阵介绍
LED 点阵是由发光二极管排列组成的显示器件
8*8 点阵共由 64 个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上,当对应的某一行置 1 电平,某一列置 0 电平,则相应的二极管就亮; 如要将第一个点点亮,则 1 脚接高电平 a 脚接低电平,则第一个点就亮了;如果 要将第一行点亮,则第 1 脚要接高电平,而(a、b、c、d、e、f、g、h )这些引脚接低电平,那么第一行就会点亮;如要将第一列点亮,则第 a 脚接低电平, 而(1、2、3、4、5、6、7、8)接高电平,那么第一列就会点亮。
LED点阵原理图
74HC595 芯片介绍
74HC595 是一个 8 位串行输入、并行输出的位移缓存器,其中并行输出为三态输出(即高电平、低电平和高阻抗)芯片管脚及功能说明如下:
芯片管脚功能
74HC595 是具有 8 位移位寄存器和一个存储器,三态输出功能。移位寄存器和存储器是单独的时钟。数据在 SCK 的上升沿输入,在 RCK 的上升沿进入到存储器中。如果两个时钟连在一起,则移位寄存器总是比存储器早一个脉冲。移位寄存器有一个串行输入(DS),和一个串行输出(Q7 非),和一个异步的低电平复位,存储寄存器有一个并行 8 位的,具有三态的总线输出,当 MR 为高电平,OE为低电平时,数据在 SHCP 上升沿进入移位寄存器,在 STCP 上升沿输出到并行端口。
注意:74HC595是 先传输字节的高位后传输低位, 所以需要 将字节低位移动到高位传输,在传输数据时,要注意 移位寄存器时钟和存储寄存器时钟的先后顺序,将要写入的数据先传输到74HC595寄存器中,即在准备好每位数据时要将 SRCLK进行一个上升沿变化,此时即可将数据传输到寄存器内, 待 循环8次即一个字节传输到寄存器中时,就可以来一个存储时钟上升沿,此时就可以将74HC595寄存器中的数据全部一次传输到595端口输出。【要注意清除寄存器缓存的数据】
取字模软件
双击打开该软件,首先选择“基本操作->新建图像”,设置图像的宽度和高度为8,点击确定后将在显示窗口出现一个8x8的白色格子,这个就类似于8x8LED 点阵,具体操作如下
可以看到上图 8*8 点阵区域非常小,我们可以将其放大,选择“模拟动画”, 后点击“放大格点”,
如下所示
然后可以在这个 8x8 白色格子里面点击,点击后即会在对应位置出现一个黑点,表示在 LED 点阵对应位置的 LED 灯点亮,未点击位置(白色)表示 LED 点阵对应位置的 LED 灯熄灭。 比如在 8x8LED 点阵上显示数字 0,那么可以在图中 8x8 白色框内通过点 击对应位置描出一个数字 0 的外形,如下所示:
然后设置取模数据的取模方式等内容,选择“参数设置”后点击“其他 选项”,具体操作如下:
然后点击“取模方式”,选择 C51 格式选项,然后在点阵生成区自动会 生成数字字符对应的数据
(如果是使用汇编编程,那么汇编对应的汉字数据可选择 A51 )
到这里我们就将数字0的数据生成了,然后将生成的数据复制到我们程序定义的数组中
LED点阵实验
IO 扩展(串转并)实验-74HC595
实验现象:下载程序后,8*8LED 点阵以一行循环滚动显示
注意事项:LED 点阵旁的 J24 黄色跳线帽短接到 GND 一端
从上图中可以看出,74HC595 需要用到的控制管脚SER、RCLK、SRCLK直接连接到 51 单片机的 P3.4-P3.6 IO 口上,输出端则是直接连接到 LED 点阵模块的行端口上,即为 LED 发光二极管的阳极,LED点阵的列则为发光二极管的阴极。 要想控制 LED 点阵,可以将单片机管脚按照 74HC595 芯片的通信时序要求来传输数据,这样即可控制 LED 点阵的行数据。根据 LED 发光二极管导通原理,当阳极为高电平,阴极为低电平则点亮,否则熄灭。因此通过单片机 P0 口可控制点阵列,74HC595 可控制点阵行。
Hc595.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 "AllHead.h" void hc595_write_data (u8 dat) { u8 i = 0 ; for (i = 0 ; i < 8 ; i++) { SER = dat >> 7 ; dat <<= 1 ; SRCLK = 0 ; delay_10us(1 ); SRCLK = 1 ; delay_10us(1 ); } rCLK = 0 ; delay_10us(1 ); rCLK = 1 ; }
Hc595.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _HC595_H_ #define _HC595_H_ #include "AllHead.h" sbit SER = P3^4 ; sbit rCLK = P3^5 ; sbit SRCLK = P3^6 ; #define LEDDZ_COL_PORT P0 void hc595_write_data (u8 dat) ;#endif
Delay.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 #include "AllHead.h" void delay_10us (u16 ten_us) { while (ten_us--); } void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include "AllHead.h" void main ( ) { u8 i = 0 ; u8 ghc595_buf[8 ] = {0x01 ,0x02 ,0x04 ,0x08 ,0x10 ,0x20 ,0x40 ,0x80 }; LEDDZ_COL_PORT = 0x00 ; while (1 ) { for (i = 0 ;i < 8 ;i++) { hc595_write_data(0x00 ); hc595_write_data(ghc595_buf[i]); Delay1ms(500 ); } } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "hc595.h" #endif
点亮一个点
实验现象:下载程序后,8*8LED 点阵点亮左上角第一个点
注意事项:LED 点阵旁的 J24 黄色跳线帽短接到 GND 一端
Hc595.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 "AllHead.h" void hc595_write_data (u8 dat) { u8 i = 0 ; for (i = 0 ; i < 8 ; i++) { SER = dat >> 7 ; dat <<= 1 ; SRCLK = 0 ; delay_10us(1 ); SRCLK = 1 ; delay_10us(1 ); } rCLK = 0 ; delay_10us(1 ); rCLK = 1 ; }
Hc595.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _HC595_H_ #define _HC595_H_ #include "AllHead.h" sbit SER = P3^4 ; sbit rCLK = P3^5 ; sbit SRCLK = P3^6 ; #define LEDDZ_COL_PORT P0 void hc595_write_data (u8 dat) ;#endif
Delay.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 #include "AllHead.h" void delay_10us (u16 ten_us) { while (ten_us--); } void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 #include "AllHead.h" void main ( ) { LEDDZ_COL_PORT = 0x7f ; ; while (1 ) { hc595_write_data(0x80 ); } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "hc595.h" #endif
LED点阵显示图像“0”
Hc595.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 "AllHead.h" void hc595_write_data (u8 dat) { u8 i = 0 ; for (i = 0 ; i < 8 ; i++) { SER = dat >> 7 ; dat <<= 1 ; SRCLK = 0 ; delay_10us(1 ); SRCLK = 1 ; delay_10us(1 ); } rCLK = 0 ; delay_10us(1 ); rCLK = 1 ; }
Hc595.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _HC595_H_ #define _HC595_H_ #include "AllHead.h" sbit SER = P3^4 ; sbit rCLK = P3^5 ; sbit SRCLK = P3^6 ; #define LEDDZ_COL_PORT P0 void hc595_write_data (u8 dat) ;#endif
Delay.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 #include "AllHead.h" void delay_10us (u16 ten_us) { while (ten_us--); } void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;void Delay1ms (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 #include "AllHead.h" void main ( ) { u8 i = 0 ; u8 gled_row[8 ] = {0x00 ,0x7C ,0x82 ,0x82 ,0x82 ,0x7C ,0x00 ,0x00 }; u8 gled_col[8 ] = {0x7f ,0xbf ,0xdf ,0xef ,0xf7 ,0xfb ,0xfd ,0xfe }; while (1 ) { for (i = 0 ;i < 8 ;i++) { LEDDZ_COL_PORT = gled_col[i]; hc595_write_data(gled_row[i]); Delay1ms(1 ); hc595_write_data(0x00 ); } } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "hc595.h" #endif
如果想要显示其他图案或者数字直接在取模软件生成行的数据复制就可以了
LED点阵显示动画
当我们了解了LED点阵屏显示图形后,就可以尝试用点阵屏来显示动画了,我们可以定义一个数组,把要显示的图形的数据存到数组里,然后逐个移动数据显示就变成了动画了
Hc595.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 #include "AllHead.h" void hc595_write_data (u8 dat) { u8 i = 0 ; for (i = 0 ; i < 8 ; i++) { SER = dat >> 7 ; dat <<= 1 ; SRCLK = 0 ; delay_10us(1 ); SRCLK = 1 ; delay_10us(1 ); } rCLK = 0 ; delay_10us(1 ); rCLK = 1 ; } void MatrixLED_ShowCol (u8 col,u8 dat) { hc595_write_data(dat); LEDDZ_COL_PORT = ~(0x80 >> col); Delay1ms(1 ); LEDDZ_COL_PORT = 0xff ; }
Hc595.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _HC595_H_ #define _HC595_H_ #include "AllHead.h" sbit SER = P3^4 ; sbit rCLK = P3^5 ; sbit SRCLK = P3^6 ; #define LEDDZ_COL_PORT P0 void hc595_write_data (u8 dat) ;void MatrixLED_ShowCol (u8 col,u8 dat) ;#endif
Delay.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 #include "AllHead.h" void delay_10us (u16 ten_us) { while (ten_us--); } void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;void Delay1ms (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 #include "AllHead.h" void main ( ) { u8 i = 0 ; u8 offset = 0 ; u8 count = 0 ; u8 gled_row[] = {0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0x00 ,0x7F ,0x08 ,0x08 ,0x08 ,0x7F ,0x00 ,0x1C , 0x2A ,0x2A ,0x2A ,0x18 ,0x00 ,0x00 ,0x7E ,0x01 , 0x02 ,0x00 ,0x7E ,0x01 ,0x02 ,0x00 ,0x1E ,0x21 , 0x21 ,0x1E ,0x00 ,0x00 ,0x7D ,0x00 ,0x00 ,0x00 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,}; while (1 ) { for (i = 0 ;i < 8 ;i++) { MatrixLED_ShowCol(i,gled_row[i + offset]); } count++; if (count > 10 ) { count = 0 ; offset++; if (offset > 40 ) offset = 0 ; } } }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "hc595.h" #endif
注意:做 LED 点阵实验时,一定要将 LED 点阵旁的 J24 黄色跳线帽短接到 GND 一端。如下所示:
16*16点阵
4片74HC595芯片,数据传入是 第一个字节最终传到第4片,第二个字节最终传到第3片,第三个字节最终传到第2片,第四个字节最终传到第1片上。(因为第一片8位满了后再传入的就会把前面的挤出去)
直流电机
直流电机介绍
直流电机是指 能将直流电能转换成机械能(直流电动机)或将机械能转换成直流电能(直流发电机的旋转电机)。它是能实现直流电能和机械能互相转换的电机。当它作电动机运行时是直流电动机,将电能转换为机械能;作发电机运行时是直流发电机,将机械能转换为电能。
直流电机的结构应由 定子和转子 两大部分组成。直流电机运行时静止不动的部分称为定子;运行时转动的部分称为转子。 直流电机没有正负之分 ,在两端加上直流电就能工作。需要知道直流电机的额定电压和额定功率,不能使之长时间超负荷运作。在交换接线后,可以形成正反转。
其内部相当于非门电路,即输入高输出为低,输入为低输出是高
外观实物图如下:
其内部结构如下图所示:
ULN2003 芯片介绍
该芯片是一个单片高电压、高电流的达林顿晶体管阵列集成电路。不仅可以用来驱动直流电机,还可用来驱动五线四相步进电机,比如 28BYJ-48 步进电机。
ULN2003 是一个单片高电压、高电流的达林顿晶体管阵列集成电路 。它是由 7 对 NPN 达林顿管组成的,它的高电压输出特性和阴极箝位二极管可以转换感应负载。单个达林顿对的集电极电流是 500mA。达林顿管并联可以承受更大的电流。 此电路主要应用于继电器驱动器,字锤驱动器,灯驱动器,显示驱动器(LED 气体放电),线路驱动器和逻辑缓冲器。ULN2003 的每对达林顿管都有一个 2.7k 串联电阻,可以直接和 TTL 或 5V CMOS 装置。
逻辑框图
从上图可以很容易理解该芯片的使用方法,其内部相当于非门电路,即输入高输出为低,输入为低输出是高,这里要注意:因为 ULN2003 的输出是集电极开路,ULN2003 要输出高电平,必须在输出口外接上拉电阻。这也就能解释在后面连接直流电机时为什么不能直接将 ULN2003 的 2 个输出口接电机线,而必须一根线接电源,另一个才接 ULN2003 输出口。
若使用该芯片驱动直流电机,只可实现单方向控制,电机一端接电源正极, 另一端接芯片的输出口。若想控制五线四相步进电机,则可将四路输出接到步进电机的四相上,电机另一条线接电正极。
直流电机实验
(1)硬件设计
实验现象:下载程序后,直流电机旋转 5秒 后停止
本实验使用到硬件资源如下:
(1)步进电机驱动模块
(2)直流电机
开发板上的步进电机驱动模块电路如下图所示:
从上图可知,ULN2003 的输入口与单片机的 P1.0-P1.3 连接,对应输出则是 OUT1-OUT4,而 J47 则是提供给外部连接电机的接口,可以支持直流电机、五线四相步进电机 28BYJ-48 连接。本实验使用的是直流电机,电机的一根线连接在 VCC 上,另一根连接在 OUT1 上,因此可通过单片机 P1.0 口输出高电平来控制电机旋转,输出低电源控制电机停止。
注意:单片机 P1.0 输出低电平时,ULN2003 的 OUT1 并不会输出高电平导致停止,而是因为集电极开路,导致电机无电流流入致使停止。
注意:直流电机的两根线要连接在 J47 端子的 O1 和 5V 上。如下所示:
(2)软件设计
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include "AllHead.h" sbit DC_Motor = P1^0 ; #define DC_MOTOR_RUN_TIME 5000 void main ( ) { DC_Motor = 1 ; Delay1ms(DC_MOTOR_RUN_TIME); DC_Motor = 0 ; while (1 ) { } }
AllHead.h
1 2 3 4 5 6 7 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #endif
步进电机
步进电机简介
步进电机是将电脉冲信号转变为角位移或线位移的开环控制元件。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,即给电机加一个脉冲信号,电机则转过一个步距角。这一线性关系的存在,加上步进电机只有周期性的误差而无累计误差等特点。使得在速度、位置等控制领域用步进电机来控制变的非常的简单。
下图为混合式步进电机组成图
步进电机工作原理
通常步进电机的转子为永磁体,当电流流过定子绕组时,定子绕组产生一矢量磁场。输入一个电脉冲,电动机转动一个角度前进一步。它输出的角位移与输入的脉冲 数成正比、转速与脉冲频率成正比。改变绕组通电的顺序,电机就会反转。所以可以控制脉冲数量、频率及电动机各相绕组的通电顺序来控制步进电机的转动。 具体看下图:
步进电机工作原理:当定子的矢量磁场旋转一个角度。转子也随着该磁场转步距角。每输入一个电脉冲 ,电动机转动一个角度前进一步。它输出的角位移与输入的脉冲数成正比、转速与脉冲频率成正比。改变绕组通电的顺序,电机就会反转 。所以可以控制脉冲数量、频率及电动机各相绕维组的通电顺序来控制步进电机的转动。
步进电机驱动原理
(1)双极性步进电机驱动原理
1 2 3 4 5 6 7 8 A 相通电,B 相不通电 A、B 相全部通电,且电流相同,产生相同磁性 B 相通电,A 断电 B 相通电,A 相通电,且电流相等,产生相同磁性 A 相通电,B 断电 A、B 相全部通电,且电流相同,产生相同磁性 B 相通电,A 断电 B 相通电,A 相通电,且电流相等,产生相同磁性
其中 1~4 步与 5~8 步的电流方向相反(电流相反,电磁的极性就相反)这样就产生了顺时针旋转,同理逆时针是将通电顺序反过来即可
(2)单极性步进电机驱动原理
单极性与双极性步进电机驱动类似,都可以分为整步与半步的驱动方式,不同的是,双极性的步进电机可以通过改变电流的方向来改变每相的磁场方向,但是单极性的就不可以了,它有一个公共端,这就直接决定了电流方向。
图中的通电顺序为:A->AB->B->BC->C->CD->D->DA, 转子每次只走半步 45 度,所以这也被称为半步驱动,与整步相比半步的旋转方式旋转起来更加的顺滑。
(3)细分驱动原理
对于细分驱动的原理,不分单双极性步进电机,下图以单极性为例:
在上图中均为双相激励;其中图(a)为 A 相电流很大,B 相的电流极其微弱,接近 0;图 © 为 A 相和 B 相的电流相同,电流决定磁场,所以说 A 相 和 B 相的磁场也是相同的,(a) 和(c)可以是极限特殊的情况,再看图(b) 和图(d)这两个是由于 A 相和 B 相的电流不同产生位置情况;由此可以得出改变定子的电流比例就可以使得转子在任意角度停住。细分的原理就是:通过改变定子的电流比例,改变转子在一个整步中的不同位置,可以将一个整步分成多个小步来运行 。
在上图中就是一个整步分成了 4 步来跑,从(a)~(d)是 A 相的电流逐渐减小,B 相电流逐渐增大的过程,如果驱动器的细分能力很强,可以将其分成 32 细分、64 细分等;这不仅提高了步进电机旋转的顺畅度而且提高了每步的精度。细分驱动具有转动顺畅、精度高、转矩大的特点,但控制复杂,一般需要专用芯片来实现。
步进电机技术指标
(1)静态技术指标
• 相数:产生不同对极 N、S 磁场的激磁线圈对数,也可以理解为步进电机中线圈的组数,其中两相步进电机步距角为 1.8°,三相的步进电机步距角为 1.5°,相数越多的步进电机,其步距角就越小。
• 拍数:完成一个磁场周期性变化所需脉冲数或导电状态用 n 表示,或指电机转过一个齿距角所需脉冲数,以四相电机为例,有四相四拍运行方式即 AB-BC-CD-DA-AB,四相八拍运行方式即 A-AB-B-BC-C-CD-D-DA-A。
• 步距角:一个脉冲信号所对应的电机转动的角度,可以简单理解为一个脉冲信号驱动的角度,电机上都有写,一般 42 步进电机的步距角为 1.8°
• 定位转矩:电机在不通电状态下,电机转子自身的锁定力矩(由磁场齿形 的谐波以及机械误差造成的)
• 静转矩:电机在额定静态电压作用下,电机不作旋转运动时,电机转轴的锁定力矩。此力矩是衡量电机体积的标准,与驱动电压及驱动电源等无关
(2)动态技术指标
• 步距角精度:步进电机转动一个步距角度的理论值与实际值的误差。用百分比表示:误差/步距角 *100%。
• 失步:电机运转时运转的步数,不等于理论上的步数。也可以叫做丢步, 一般都是因负载太大或者是频率过快。
• 失调角:转子齿轴线偏移定子齿轴线的角度,电机运转必存在失调角,由失调角产生的误差,采用细分驱动是不能解决的。
• 最大空载起动频率:在不加负载的情况下,能够直接起动的最大频率。
• 最大空载的运行频率:电机不带负载的最高转速频率。
• 运行转矩特性:电机的动态力矩取决于电机运行时的平均电流(而非静态电流),平均电流越大,电机输出力矩越大,即电机的频率特性越硬
• 电机正反转控制:通过改变通电顺序而改变电机的正反转
28BYJ-48 步进电机简介
28BYJ48 步进电机自带减速器,为四相无线步进电机,直径为 28mm,实物如下所示:
28BYJ48 电机内部结构等效图如下所示:
28BYJ48 步进电机旋转驱动方式如下表:
28BYJ48步进电机本来为低电平驱动,但是使用的这个开发板是用ULN2003芯片控制步进电机管脚,相当于非门,所以得高电平才能驱动28BYJ48步进电机
28BYJ48 步进电机主要参数如下所示:
在上图中 28BYJ48 步进电机主要参数中可以看到有一个减速比:1:64,步进角为 5.625/64 度,如果需要转动一圈,那么需要 360/5.625*644096 个脉冲信号
28BYJ48 步进电机实际上是:减速齿轮+步进电机组成
步进电机原理图
按键控制步进电机驱动实验
实验名称:步进电机实验
实验现象:下载程序后,当按下 KEY1 键可调节电电机旋转方向;当按下 KEY2 键,电机加速;当按下 KEY3 键,电机减速
step_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 #include "AllHead.h" void step_motor_28BYJ48_send_pluse (u8 step, u8 dir) { u8 temp = step; if (dir == 0 ) temp = 7 - step; switch (temp) { case 0 : IN4_A = 0 ;IN3_B = 0 ;IN2_C = 0 ;IN1_D = 1 ; break ; case 1 : IN4_A = 0 ;IN3_B = 0 ;IN2_C = 1 ;IN1_D = 1 ; break ; case 2 : IN4_A = 0 ;IN3_B = 0 ;IN2_C = 1 ;IN1_D = 0 ; break ; case 3 : IN4_A = 0 ;IN3_B = 1 ;IN2_C = 1 ;IN1_D = 0 ; break ; case 4 : IN4_A = 0 ;IN3_B = 1 ;IN2_C = 0 ;IN1_D = 0 ; break ; case 5 : IN4_A = 1 ;IN3_B = 1 ;IN2_C = 0 ;IN1_D = 0 ; break ; case 6 : IN4_A = 1 ;IN3_B = 0 ;IN2_C = 0 ;IN1_D = 0 ; break ; case 7 : IN4_A = 1 ;IN3_B = 0 ;IN2_C = 0 ;IN1_D = 1 ; break ; } }
step_motor.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef _STEP_MOTOR_H_ #define _STEP_MOTOR_H_ #include "AllHead.h" sbit IN1_D = P1^0 ; sbit IN2_C = P1^1 ; sbit IN3_B = P1^2 ; sbit IN4_A = P1^3 ; #define STEPMOTOR_MAXSPEED 1 #define STEPMOTOR_MINSPEED 5 void step_motor_28BYJ48_send_pluse (u8 step, u8 dir) ;#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 #include "AllHead.h" u8 key_scan (u8 mode) { static u8 key = 1 ; if (mode) key = 1 ; if (key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0 )) { Delay1ms(10 ); key = 0 ; if (KEY1 == 0 ) return KEY1_PRESS; else if (KEY2 == 0 ) return KEY2_PRESS; else if (KEY3 == 0 ) return KEY3_PRESS; else if (KEY4 == 0 ) return KEY4_PRESS; } else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1 ) { key = 1 ; } return KEY_UNPRESS; }
Key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" sbit KEY1 = P3^1 ; sbit KEY2 = P3^0 ; sbit KEY3 = P3^2 ; sbit KEY4 = P3^3 ; #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 #define KEY_UNPRESS 0 u8 key_scan (u8 mode) ; #endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (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 #include "AllHead.h" void main ( ) { u8 key = 0 ; u8 step = 0 ; u8 dir = 0 ; u8 speed = STEPMOTOR_MAXSPEED; while (1 ) { key = key_scan(0 ); if (key == KEY1_PRESS) dir = !dir; else if (key == KEY2_PRESS) { if (speed > STEPMOTOR_MAXSPEED) speed -= 1 ; } else if (key == KEY3_PRESS) { if (speed < STEPMOTOR_MINSPEED) speed += 1 ; } step_motor_28BYJ48_send_pluse(step++,dir); if (step == 8 ) step = 0 ; Delay1ms(speed); } }
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "key.h" #include "step_motor.h" #endif
注意:将步进电机红色线对接“步进电机模块” 输出端子 J47 的 5V 上,其它相序依次接入。
中断
中断概念
中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的, 中断功能的存在,很大程度上提高了单片机处理外部或内部事件的能力。对于单片机来讲,中断是指 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 溢出中断请求标志位。
中断优先级
同一优先级中的中断申请不止一个时,则有中断优先权排队问题。同一优先级的中断优先权排队,由中断系统硬件确定的自然优先级形成,其排列如所示:
中断号
中断响应条件
①中断源有中断请求;
②此中断源的中断允许位为 1;
③CPU 开中断(即 EA=1)。
以上三条同时满足时,CPU 才有可能响应中断。
在使用中断时我们需要做什么呢?
①你想使用的中断是哪个?选择相应的中断号;
②你所希望的触发条件是什么?
③你希望在中断之后干什么?
外部中断配置
以外部中断 0 为例,如下:
主程序中需要有以下代码
如果要配置的是外部中断 1,只需将 EX0 改为 EX1,IT0 改为 IT1,通常使用外部中断都是配置为下降沿触发,即 IT0=1
当触发中断后即会进入中断服务函数,外部中断 0 中断服务函数如下:
在中断函数中 exti0 是函数名,可自定义,但必须符合 C 语言标识符定义规则,interrupt 是一个关键字,表示 51 单片机中断。后面的 0 是中断号,外部中断 0 中断号为 0,如果是外部中断 1,则中断号为 2
程序–外部中断K3控制LED亮灭实验
EXTI.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include "AllHead.h" void EXTI0_Init (void ) { IT0 = 1 ; EX0 = 1 ; EA = 1 ; }
EXTI.h
1 2 3 4 5 6 7 8 #ifndef _EXTI_H_ #define _EXTI_H_ #include "AllHead.h" void EXTI0_Init (void ) ;#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (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 #include "AllHead.h" void main ( ) { EXTI0_Init(); while (1 ) { } } void EXTI0 () interrupt 0 { Delay1ms(10 ); if (KEY3 == 0 ) LED1 = !LED1; }
AllHead.h
1 2 3 4 5 6 7 8 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "EXTI.h" #endif
注意:由于红外接收传感器与 K3 共用 P3.2 口,因因此在做外部中断0实验时, 将红外接收传感器从开发板取下,防止干扰
定时器
CPU 时序
①振荡周期:为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期)。
②状态周期:2 个振荡周期为 1 个状态周期,用 S 表示。振荡周期又称 S 周期或时钟周期。
③机器周期: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 计数器的计数值。
51 单片机定时器/计数器内部结构图:
上图中的 T0 和 T1 引脚对应的是单片机 P3.4 和 P3.5 管脚
计数器的工作由两个特殊功能寄存器控制
TMOD 是定时/计数器的工作方式寄存器,确定工作方式和功能;
TCON 是控制寄存器,控制 T0、T1 的启动和停止及设置溢出标志
定时计数器寄存器
工作方式寄存器 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:工作方式设置位。定时/计数器有四种工作方式。
控制寄存器 TCON
TCON 的低 4 位用于控制外部中断。TCON 的高 4 位用于控制定时/计数器的启动和中断申请。
其格式如下:
TF1(TCON.7):T1 溢出中断请求标志位。T1 计数溢出时由硬件自动置 TF1 为 1。CPU 响应中断后 TF1 由硬件自动清 0。T1 工作时,CPU 可随时查询 TF1 的状态。所以,TF1 可用作查询测试的标志。TF1 也可以用软件置 1 或清 0,同硬件置 1 或清 0 的效果一样。
TR1(TCON.6):T1 运行控制位。TR1 置 1 时,T1 开始工作;TR1 置 0 时, T1 停止工作。TR1 由软件置 1 或清 0。所以,用软件可控制定时/计数器的启动与停止。
TF0(TCON.5):T0 溢出中断请求标志位,其功能与 TF1 类同。
TR0(TCON.4):T0 运行控制位,其功能与 TR1 类同。
定时/计数器的工作方式
(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 计数器
原理跟方式 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
定时器配置
在使用定时器时,应该如何配置使其工作?其步骤如下(各步骤顺序可任意):
①对 TMOD 赋值,以确定 T0 和 T1 的工作方式,如果使用定时器 0 即对 T0 配 置,如果使用定时器 1 即对 T1 配置。
②根据所要定时的时间计算初值,并将其写入 TH0、TL0 或 TH1、TL1。
③如果使用中断,则对 EA 赋值,开放定时器中断。
④使 TR0 或 TR1 置位,启动定时/计数器定时或计数
计算定时/计数器初值
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 或者使用小工具进行换算不用手动算。
开发板上使用的外部晶振不同,换算的初值是不一样的
小工具
也可以使用STC-ISP里面的定时器计算器
如果要实现很长时间的定时,比如定时 1 秒钟。可以通过初值设置定时 1ms,每当定时 1ms 结束后又重新赋初值,并且设定一个全局变量累计定时 1ms 的次数,当累计到 1000 次,表示已经定时 1 秒了。需要其他定时时间类似操作,这样我们就可以使用定时器来实现精确延时来替代之前的 delay 函数。
以定时器 0 为例介绍配置定时器工作方式 1、设定 1ms 初值,开启定时器计数功能以及总中断,如下
1 2 3 4 5 6 7 8 9 void time0_init (void ) { TMOD|=0X01 ; TH0=0XFC ; TL0=0X18 ; ET0=1 ; EA=1 ; TR0=1 ; }
程序–定时器0控制LED1秒闪烁
time.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include "AllHead.h" void Time0_Init (void ) { TMOD |= 0x01 ; TH0 = 0xFC ; TL0 = 0X66 ; ET0 = 1 ; EA = 1 ; TR0 = 1 ; }
time.h
1 2 3 4 5 6 7 8 #ifndef _EXTI_H_ #define _EXTI_H_ #include "AllHead.h" void Time0_Init (void ) ;#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 #include "AllHead.h" void main ( ) { Time0_Init(); while (1 ) { } } void Time0 () interrupt 1 { static u16 count = 0 ; TH0 = 0xFC ; TL0 = 0X66 ; count++; if (count == 1000 ) { count = 0 ; LED1 = !LED1; } }
AllHead.h
1 2 3 4 5 6 7 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "time.h" #endif
串口通信
串口通信相关术语
通信的方式可以分为多种,按照数据传送方式可分为串行通信和并行通信。按照通信的数据同步方式,可分为异同通信和同步通信。按照数据的传输方向又可分为单工、半双工和全双工通信。
1 2 3 4 5 6 全双工:通信双方可以在同一时刻互相传输数据 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线 单工:通信只能有一方发送到另一方,不能反向传输 异步:通信双方各自约定通信速率 同步:通信双方靠一根时钟线来约定通信速率 总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)
(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,那么每个码元可以表示四种状态,即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。由于很多常见的通信中一个码元都是表示两种状态,所以我们常常直接以 波特率来表示比特率。
单片机串口介绍
串口通信简介
串口通信(Serial Communication),是指外设和计算机间通过数据信号线、 地线等按位进行传输数据的一种通信方式,属于串行通信方式。串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。
(1)接口标准
串口通信的接口标准有很多,有 RS-232C、 RS-232、 RS-422A、 RS-485 等。常用的是 RS-232 和 RS-485。RS-232 其实是 RS-232C 的改进,原理是一样的。
RS-232C 接口规定使用 25 针连接器,简称 DB25,连接器的尺寸及每个插 针的排列位置都有明确的定义,如下图所示:
RS-232C 还有一种 9 针的非标准连接器接口,简称 DB9。串口通信使用的大多都是 DB9 接口。DB25 和 DB9 接头有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。9 针串口线的外观图如下图所示:
9 针串口和 25 针串口常用管脚的功能说明如下图所示:
在串口通信中,通常我们只使用 2、3、5 三个管脚,即 TXD、RXD、SGND。
RS-232C 是用正负电压来表示逻辑状态;与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反。而我们 51 单片机使用的就是 TTL 电平,所以要实现 51 单片机与计算机的串口通信,需要进行 TTL 与 RS-232C 电平转换,通常使用的电平转换芯片是 MAX232。
在串口通信中通常 PC 机的 DB9 为公头,单片机上使用的串口 DB9 为母头, 通过一根直通串口线进行相连。
串口通信中还需要注意的是,串口数据收发线要交叉连接,计算机的 TXD 要对应单片机的 RXD,计算机的 RXD 要对应单片机的 TXD,并且共 GND,如下图:
(2)通信协议
RS232 的通信协议比较简单,通常遵循 96-N-8-1 格式
“96”表示的是通信波特率为 9600
串口通信中通常使用的是 异步串口通信,即没有时钟线,所以两个设备要通信,必须要保持一致的波特率,当然,波特率常用值还有 4800、 115200 等。
“N”表示的是无校验位
由于串口通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有 奇校验(odd)、偶校验(even)、0 校验(space)、1 校验(mark)以及无校验(noparity)。
“8”表示的是数据位数为 8 位
当然数据位数还可以为 5、6、7 位长度
“1”表示的是 1 位停止位
串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由 一个逻辑 0的数据位表示,而数据包的停止信号可由0.5、1、1.5 或 2``` 个逻辑 1 的数据位表示,只要双方约定一致即可。
(3)串口内部结构
上图中右边的 TXD 和 RXD 为单片机 IO 口,TXD 对应的是 P3.1 管脚,RXD 对 应的是 P3.0 管脚。SBUF 是存放数据的,读取也是从这里读。
SBUF:串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器。
串口相关寄存器
串口控制寄存器 SCON
SM0 和 SM1 为工作方式选择位:
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,取消此中断申请。
电源控制寄存器 PCON
SMOD :波特率倍增位。在串口方式 1、方式 2、方式 3 时,波特率与 SMOD 有 关,当 SMOD=1 时,波特率提高一倍。复位时,SMOD=0。
串口工作方式
(1)方式 0
方式 0 时,串行口为同步移位寄存器的输入输出方式。主要用于扩展并行输 入或输出口。数据由 RXD(P3.0)引脚输入或输出,同步移位脉冲由 TXD(P3.1) 引脚输出。发送和接收均为 8 位数据,低位在先,高位在后。波特率固定为 fosc/12。
对应的输入输出时序图如下所示:
①方式 0 输出
②方式 0 输入
(2)方式 1
方式 1 是 10 位数据的异步通信口。TXD 为数据发送引脚,RXD 为数据接收引脚,传送一帧数据的格式如下所示。其中 1 位起始位,8 位数据位,1 位停止位。
对应的输入输出时序图如下所示:
①方式 1 输出
②方式 1 输入
用软件置 REN 为 1 时,接收器以所选择波特率的 16 倍速率采样 RXD 引脚电平,检测到 RXD 引脚输入电平发生负跳变时,则说明起始位有效,将其移入输入移位寄存器,并开始接收这一帧信息的其余位。接收过程中,数据从输入移位寄存器右边移入,起始位移至输入移位寄存器最左边时,控制电路进行最后一次移 位。当 RI=0,且 SM2=0(或接收到的停止位为 1)时,将接收到的 9 位数据的前 8 位数据装入接收 SBUF,第 9 位(停止位)进入 RB8,并置 RI=1,向 CPU 请求中断。
(3)方式2和方式3
方式 2 或方式 3 时为 11 位数据的异步通信口。TXD 为数据发送引脚,RXD 为 数据接收引脚。其数据格式如下所示:
对应的输入输出时序图如下所示:
①方式 2、方式 3 输出
发送开始时,先把起始位 0 输出到 TXD 引脚,然后发送移位寄存器的输出位 (D0)到 TXD 引脚。每一个移位脉冲都使输出移位寄存器的各位右移一位,并由 TXD 引脚输出。第一次移位时,停止位“1”移入输出移位寄存器的第 9 位上, 以后每次移位,左边都移入 0。当停止位移至输出位时,左边其余位全为 0,检测电路检测到这一条件时,使控制电路进行最后一次移位,并置 TI=1,向 CPU 请求中断。
②方式 2、方式 3 输入
接收时,数据从右边移入输入移位寄存器,在起始位 0 移到最左边时,控制电路进行最后一次移位。当 RI=0,且 SM2=0(或接收到的第 9 位数据为 1)时, 接收到的数据装入接收缓冲器 SBUF 和 RB8(接收数据的第 9 位),置 RI=1,向 CPU 请求中断。如果条件不满足,则数据丢失,且不置位 RI,继续搜索 RXD 引脚 的负跳变。
串口的使用方法
计算波特率
方式0的波特率 = f o s c / 12 \text{方式0的波特率} = fosc/12
方式 0 的波特率 = f osc /12
方式2的波特率 = ( 2 S M O D / 64 ) × f o s c \text{方式2的波特率} =(2^{SMOD}/64)\times fosc
方式 2 的波特率 = ( 2 SMO D /64 ) × f osc
方式1的波特率 = ( 2 S M O D / 32 ) × (T1 溢出率) \text{方式1的波特率} =(2^{SMOD}/32)\times\text{(T1 溢出率)}
方式 1 的波特率 = ( 2 SMO D /32 ) × (T1 溢出率 )
方式3的波特率 = ( 2 S M O D / 32 ) × (T1 溢出率) \text{方式3的波特率} =(2^{SMOD}/32)\times\text{(T1 溢出率)}
方式 3 的波特率 = ( 2 SMO D /32 ) × (T1 溢出率 )
其中 T1 溢出率 =
f o s c / ( 12 × ( 256 − ( T H 1 ) ) ) fosc /(12×(256 - (TH1)))
f osc / ( 12 × ( 256 − ( T H 1 )))
也可以用小工具自动生成波特率。在做串口通信实验时, 一定要确认外部晶振是否是 11.0592M。因为当使用12M 晶振时,波特率误差有 6.98%,会导致通信过程中出现乱码等错误信息
串口初始化步骤
如何使用串口,大家可以按照以下几个步骤配置
①确定 T1 的工作方式(TMOD 寄存器);
②确定串口工作方式(SCON 寄存器);
③计算 T1 的初值(设定波特率),装载 TH1、TL1;
④启动 T1(TCON 中的 TR1 位);
⑤如果使用中断,需开启串口中断控制位(IE 寄存器)。
例如:设置串口为工作方式 1、波特率为 9600、波特率加倍、使用中断。其配置程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void uart_init (u8 baud) { TMOD|= 0X20 ; SCON = 0X50 ; PCON = 0X80 ; TH1 = baud; TL1 = baud; ES = 1 ; EA = 1 ; TR1 = 1 ; } uart_init(0XFA );
串口通信原理图
串口通信实验
注意事项:使用黄色跳线帽将 CH340 旁的 P5 端子的 UTX 和 P30 短接,URX 和 P31 短接,出厂默认已短接好
串口助手发送数据给单片机,单片机原封不动转发给串口助手
Uart.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include "AllHead.h" void UART_Init (u8 baud) { TMOD |= 0X20 ; SCON = 0X50 ; PCON = 0X80 ; TH1 = baud; TH1 = baud; ES = 1 ; EA = 1 ; TR1 = 1 ; }
Uart.h
1 2 3 4 5 6 7 8 #ifndef _UART_H_ #define _UART_H_ #include "AllHead.h" void UART_Init (u8 baud) ;#endif
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include "AllHead.h" void main ( ) { UART_Init(0xFA ); while (1 ) { } } void uart () interrupt 4 { u8 rec_data; RI = 0 ; rec_data = SBUF; SBUF = rec_data; while (!TI); TI = 0 ; }
AllHead.h
1 2 3 4 5 6 7 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "uart.h" #endif
串口控制led灯
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 #include "AllHead.h" void UART_Init (u8 baud) { TMOD |= 0X20 ; SCON = 0X50 ; PCON = 0X80 ; TH1 = baud; TH1 = baud; ES = 1 ; EA = 1 ; TR1 = 1 ; } void UART_SendByte (u8 Byte) { SBUF = Byte; while (!TI); TI = 0 ; }
Uart.h
1 2 3 4 5 6 7 8 9 #ifndef _UART_H_ #define _UART_H_ #include "AllHead.h" void UART_Init (u8 baud) ;void UART_SendByte (u8 Byte) ;#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 #include "AllHead.h" #define LED_PORT P2 void main ( ) { UART_Init(0xFA ); while (1 ) { } } void uart () interrupt 4 { if (RI == 1 ) { LED_PORT = ~SBUF; UART_SendByte(SBUF); RI = 0 ; } }
AllHead.h
1 2 3 4 5 6 7 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "uart.h" #endif
PC端发送数据
现象
I2C-EEPROM
存储器介绍
I2C介绍
I2C是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单, 器件封装形式小,通信速率较高等优点。
I2C 总线只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。由于其管脚少,硬件实现简单,可扩展性强等特点,因此被广泛的使用在各大集成芯片内。
I2C 通信设备常用的连接方式如下图所示:
它的物理层有如下特点:
(1)它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在 一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2)一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA),一 条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5)多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
(6)具有三种传输模式:标准模式传输速率为 100kbit/s,快速模式为 400kbit/s,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7)连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
I2C 总线常用的一些术语
主机:启动数据传送并产生时钟信号的设备;
从机:被主机寻址的器件;
多主机:同时有多于一个主机尝试控制总线但不破坏传输;
主模式:用 I2CNDAT 支持自动字节计数的模式; 位 I2CRM,I2CSTT,I2CSTP 控制数据的接收和发送;
从模式:发送和接收操作都是由 I2C 模块自动控制的;
仲裁:是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并使传输不被破坏的过程;
同步:两个或多个器件同步时钟信号的过程;
发送器:发送数据到总线的器件;
接收器:从总线接收数据的器件。
I2C 协议
I2C 的协议定义了通信的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。
(1)数据有效性规定
I2C 总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。每次数据传输都以字节为单位,每次传输的字节数不受限制。如下图:
(2)起始和停止信号
SCL 线为高电平期间,SDA 线由高电平向低电平的变化表示起始信号;SCL 线为高电平期间,SDA 线由低电平向高电平的变化表示终止信号。起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态。如下图:
起始条件 :SCL高电平期间,SDA从高电平切换到低电平
终止条件 :SCL高电平期间,SDA从低电平切换到高电平
(3)应答响应
每当发送器件传输完一个字节的数据后,后面必须紧跟一个校验位,这个校验位是接收端通过控制 SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成,数据传送可以继续进行。这个校验位其实就是数据或地址传输过程中的响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号即特定的低电平脉冲, 发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号即特定的高电平脉冲,发送方接收到该信号后会产生一个停止信号,结束信号传输。应答响应时序图如下:
每一个字节必须保证 8 位长度。数据传送时,先传送最高位(MSB),每 一个被传送的字节后面都必须跟随一位应答位(即一帧共有 9 位)。这些信号中,起始信号是必需的,结束信号和应答信号都可以不要。
发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
(4)总线的寻址方式
I2C 总线寻址按照从机地址位数可分为两种,一种是 7 位,另一种是 10 位。采用 7 位的寻址字节(寻址字节是起始信号后的第一个字节)的位定义如下:
D7~D1 位组成从机的地址。D0 位是数据传送方向位,为“ 0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的 7 位寻址位有 4 位是固定位,3 位是可编程位,这时仅能寻址 8 个同样的器件,即可以有 8 个同样的器件接入到该 I2C 总线系统中。
(5)数据传输
I2C 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。在起始信号后必须传送一个从机的地址(7 位),第 8 位是数据的传送方向位(R/W),用“ 0”表示主机发送(写)数据(W),“ 1”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
AT24C02的固定地址为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)。
AT24C02 介绍(EEPROM)
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
存储介质:E2PROM
通讯接口:I2C
总线容量:256字节
AT24C01/02/04/08/16…是一个 1K/2K/4K/8K/16K 位串行 CMOS,内部含有128/256/512/1024/2048 个 8 位字节,AT24C01 有一个 8 字节页写缓冲器, AT24C02/04/08/16 有一个 16 字节页写缓冲器。该器件通过 I2C 总线接口进行操作,它有一个专门的写保护功能。我们开发板上使用的是 AT24C02(EEPROM) 芯片,此芯片具有 I2C 通信接口,芯片内保存的数据在掉电情况下都不丢失, 所以通常用于存放一些比较重要的数据等。AT24C02 芯片管脚及外观图如下图所示:
芯片管脚说明如下图所示:
AT24C02 器件地址为7 位,高 4 位固定为 1010,低 3 位由 A0/A1/A2 信号线的电平决定。 因为传输地址或数据是以字节为单位传送的,当传送地址时, 器件地址占 7 位,还有最后一位(最低位 R/W)用来选择读写方向,它与地址无关。其格式如下:
开发板已经将芯片的 A0/A1/A2 连接到 GND,所以器件地址为 1010000,即 0x50(未计算最低位)。如果要对芯片进行写操作时,R/W 即为 0, 写器件地址即为 0XA0;如果要对芯片进行读操作时,R/W 即为 1,此时读器件地址为 0XA1。
I2C 总线时序如下图所示:
EEPROM原理图
I2C-EEPROM实验
下载程序后,数码管右 4 位显示 0,按 K1 键将将数据写入到 EEPROM 内保存,按 K2 键读取 EEPROM 内保存的数据,按 K3 键显示数据加 1,按 K4 键显示数据清零,最大能写入的数据是 255
第一种写法
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 #include "AllHead.h" u8 key_scan (u8 mode) { static u8 key = 1 ; if (mode) key = 1 ; if (key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0 )) { Delay1ms(10 ); key = 0 ; if (KEY1 == 0 ) return KEY1_PRESS; else if (KEY2 == 0 ) return KEY2_PRESS; else if (KEY3 == 0 ) return KEY3_PRESS; else if (KEY4 == 0 ) return KEY4_PRESS; } else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1 ) { key = 1 ; } return KEY_UNPRESS; }
Key.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" sbit KEY1 = P3^1 ; sbit KEY2 = P3^0 ; sbit KEY3 = P3^2 ; sbit KEY4 = P3^3 ; #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 #define KEY_UNPRESS 0 u8 key_scan (u8 mode) ; #endif
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 #include "AllHead.h" u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f , 0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; void smg_display (u8 location,u8 dat[]) { u8 i = 0 ; u8 pos_temp = location - 1 ; for (i = pos_temp;i < 8 ;i++) { switch (i) { case 0 : LSC = 1 ;LSB = 1 ;LSA = 1 ;break ; case 1 : LSC = 1 ;LSB = 1 ;LSA = 0 ;break ; case 2 : LSC = 1 ;LSB = 0 ;LSA = 1 ;break ; case 3 : LSC = 1 ;LSB = 0 ;LSA = 0 ;break ; case 4 : LSC = 0 ;LSB = 1 ;LSA = 1 ;break ; case 5 : LSC = 0 ;LSB = 1 ;LSA = 0 ;break ; case 6 : LSC = 0 ;LSB = 0 ;LSA = 1 ;break ; case 7 : LSC = 0 ;LSB = 0 ;LSA = 0 ;break ; } SMG_A_DP_PORT = gsmg_code[dat[i-pos_temp]]; Delay1ms(1 ); SMG_A_DP_PORT = 0x00 ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 location,u8 dat[]) ;extern u8 gsmg_code[17 ];#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
IIC.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" void iic_start (void ) { IIC_SCL = 1 ; IIC_SDA = 1 ; delay_10us(1 ); IIC_SDA = 0 ; delay_10us(1 ); IIC_SCL = 0 ; } void iic_stop (void ) { IIC_SCL = 1 ; IIC_SDA = 0 ; delay_10us(1 ); IIC_SDA = 1 ; delay_10us(1 ); } void iic_ack (void ) { IIC_SCL = 0 ; IIC_SDA = 0 ; delay_10us(1 ); IIC_SCL = 1 ; delay_10us(1 ); IIC_SCL = 0 ; } void iic_nack (void ) { IIC_SCL = 0 ; IIC_SDA = 1 ; delay_10us(1 ); IIC_SCL = 1 ; delay_10us(1 ); IIC_SCL = 0 ; } u8 iic_wait_ack (void ) { u8 time_temp = 0 ; IIC_SCL = 1 ; delay_10us(1 ); while (IIC_SDA) { time_temp++; if (time_temp > 100 ) { iic_stop(); return 1 ; } } IIC_SCL = 0 ; return 0 ; } void iic_write_byte (u8 dat) { u8 i = 0 ; IIC_SCL = 0 ; for (i = 0 ;i < 8 ; i++) { if ((dat & 0x80 ) > 0 ) IIC_SDA = 1 ; else IIC_SDA = 0 ; dat <<= 1 ; delay_10us(1 ); IIC_SCL = 1 ; delay_10us(1 ); IIC_SCL = 0 ; delay_10us(1 ); } } u8 iic_read_byte (u8 ack) { u8 i = 0 ; u8 receive = 0 ; for (i = 0 ; i < 8 ; i++) { IIC_SCL = 0 ; delay_10us(1 ); IIC_SCL = 1 ; delay_10us(1 ); receive <<= 1 ; if (IIC_SDA) receive++; delay_10us(1 ); } if (!ack) iic_nack(); else iic_ack(); return receive; }
IIC.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef _IIC_H_ #define _IIC_H_ #include "AllHead.h" sbit IIC_SCL = P2^1 ; sbit IIC_SDA = P2^0 ; void iic_start (void ) ;void iic_stop (void ) ;void iic_ack (void ) ;void iic_nack (void ) ;u8 iic_wait_ack (void ) ; void iic_write_byte (u8 dat) ;u8 iic_read_byte (u8 ack) ; #endif
EEPROM.c(对应IIC第一种第一种写法)
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 "AllHead.h" void EEPROM_write_one_byte (u8 addr,u8 dat) { iic_start(); iic_write_byte(0xA0 ); iic_wait_ack(); iic_write_byte(addr); iic_wait_ack(); iic_write_byte(dat); iic_wait_ack(); iic_stop(); Delay1ms(10 ); } u8 EEPROM_read_one_byte (u8 addr) { u8 receive = 0 ; iic_start(); iic_write_byte(0xA0 ); iic_wait_ack(); iic_write_byte(addr); iic_wait_ack(); iic_start(); iic_write_byte(0xA1 ); iic_wait_ack(); receive = iic_read_byte(addr); iic_stop(); return receive; }
EEPROM.h
1 2 3 4 5 6 7 8 9 #ifndef _EEPROM_H_ #define _EEPROM_H_ #include "AllHead.h" void EEPROM_write_one_byte (u8 addr,u8 dat) ;u8 EEPROM_read_one_byte (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 27 28 29 30 31 32 33 34 35 36 #include "AllHead.h" #define EEPROM_ADDRESS 0 void main ( ) { u8 key = 0 ; u8 save_value = 0 ; u8 save_buf[3 ]; while (1 ) { key = key_scan(0 ); if (key == KEY1_PRESS) { EEPROM_write_one_byte(EEPROM_ADDRESS,save_value); } else if (key == KEY2_PRESS) { save_value = EEPROM_read_one_byte(EEPROM_ADDRESS); } else if (key == KEY3_PRESS) { save_value++; if (save_value == 255 ) save_value = 255 ; } else if (key == KEY4_PRESS) { save_value = 0 ; } save_buf[0 ] = save_value / 100 ; save_buf[1 ] = save_value % 100 / 10 ; save_buf[2 ] = save_value % 100 % 10 ; smg_display(6 ,save_buf); } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "smg.h" #include "key.h" #include "EXTI.h" #include "iic.h" #include "EEPROM.h" #endif
第二种写法,区别在IIC那里的写法不同,第二种写法开发板会比较灵敏一点
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 #include "AllHead.h" u8 key_scan (u8 mode) { static u8 key = 1 ; if (mode) key = 1 ; if (key == 1 && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0 )) { Delay1ms(10 ); key = 0 ; if (KEY1 == 0 ) return KEY1_PRESS; else if (KEY2 == 0 ) return KEY2_PRESS; else if (KEY3 == 0 ) return KEY3_PRESS; else if (KEY4 == 0 ) return KEY4_PRESS; } else if (KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1 ) { key = 1 ; } return KEY_UNPRESS; }
Key.h
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 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" sbit KEY1 = P3^1 ; sbit KEY2 = P3^0 ; sbit KEY3 = P3^2 ; sbit KEY4 = P3^3 ; #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 #define KEY_MATRIX_PORT P1 #define KEY_UNPRESS 0 u8 key_scan (u8 mode) ; #endif
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 #include "AllHead.h" u8 gsmg_code[17 ] = {0x3f ,0x06 ,0x5b ,0x4f ,0x66 ,0x6d ,0x7d ,0x07 ,0x7f , 0x6f ,0x77 ,0x7c ,0x39 ,0x5e ,0x79 ,0x71 }; void smg_display (u8 location,u8 dat[]) { u8 i = 0 ; u8 pos_temp = location - 1 ; for (i = pos_temp;i < 8 ;i++) { switch (i) { case 0 : LSC = 1 ;LSB = 1 ;LSA = 1 ;break ; case 1 : LSC = 1 ;LSB = 1 ;LSA = 0 ;break ; case 2 : LSC = 1 ;LSB = 0 ;LSA = 1 ;break ; case 3 : LSC = 1 ;LSB = 0 ;LSA = 0 ;break ; case 4 : LSC = 0 ;LSB = 1 ;LSA = 1 ;break ; case 5 : LSC = 0 ;LSB = 1 ;LSA = 0 ;break ; case 6 : LSC = 0 ;LSB = 0 ;LSA = 1 ;break ; case 7 : LSC = 0 ;LSB = 0 ;LSA = 0 ;break ; } SMG_A_DP_PORT = gsmg_code[dat[i-pos_temp]]; Delay1ms(1 ); SMG_A_DP_PORT = 0x00 ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 location,u8 dat[]) ;extern u8 gsmg_code[17 ];#endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (unsigned int xms) ;#endif
IIC.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" void I2C_Start (void ) { I2C_SDA=1 ; I2C_SCL=1 ; I2C_SDA=0 ; I2C_SCL=0 ; } void I2C_Stop (void ) { I2C_SDA=0 ; I2C_SCL=1 ; I2C_SDA=1 ; } void I2C_SendByte (u8 Byte) { u8 i; for (i=0 ;i<8 ;i++) { I2C_SDA=Byte&(0x80 >>i); I2C_SCL=1 ; I2C_SCL=0 ; } } u8 I2C_ReceiveByte (void ) { u8 i,Byte=0x00 ; I2C_SDA=1 ; for (i=0 ;i<8 ;i++) { I2C_SCL=1 ; if (I2C_SDA) { Byte|=(0x80 >>i); } I2C_SCL=0 ; } return Byte; } void I2C_SendAck (u8 AckBit) { I2C_SDA=AckBit; I2C_SCL=1 ; I2C_SCL=0 ; } u8 I2C_ReceiveAck (void ) { u8 AckBit; I2C_SDA=1 ; I2C_SCL=1 ; AckBit=I2C_SDA; I2C_SCL=0 ; return AckBit; }
IIC.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _IIC_H_ #define _IIC_H_ #include "AllHead.h" sbit I2C_SCL = P2^1 ; sbit I2C_SDA = P2^0 ; void I2C_Start (void ) ;void I2C_Stop (void ) ;void I2C_SendByte (u8 Byte) ;u8 I2C_ReceiveByte (void ) ; void I2C_SendAck (u8 AckBit) ;u8 I2C_ReceiveAck (void ) ; #endif
EEPROM.c(对应IIC第二种写法)
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 #include "AllHead.h" void EEPROM_WriteByte (u8 addr,u8 Data) { I2C_Start(); I2C_SendByte(0xA0 ); I2C_ReceiveAck(); I2C_SendByte(addr); I2C_ReceiveAck(); I2C_SendByte(Data); I2C_ReceiveAck(); I2C_Stop(); } u8 EEPROM_ReadByte (u8 addr) { u8 Data; I2C_Start(); I2C_SendByte(0xA0 ); I2C_ReceiveAck(); I2C_SendByte(addr); I2C_ReceiveAck(); I2C_Start(); I2C_SendByte(0xA1 ); I2C_ReceiveAck(); Data=I2C_ReceiveByte(); I2C_SendAck(1 ); I2C_Stop(); return Data; }
EEPROM.h
1 2 3 4 5 6 7 8 9 #ifndef _EEPROM_H_ #define _EEPROM_H_ #include "AllHead.h" void EEPROM_WriteByte (u8 addr,u8 Data) ;u8 EEPROM_ReadByte (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 27 28 29 30 31 32 33 34 35 36 #include "AllHead.h" #define EEPROM_ADDRESS 0 void main ( ) { u8 key = 0 ; u8 save_value = 0 ; u8 save_buf[3 ]; while (1 ) { key = key_scan(0 ); if (key == KEY1_PRESS) { EEPROM_WriteByte(EEPROM_ADDRESS,save_value); } else if (key == KEY2_PRESS) { save_value = EEPROM_ReadByte(EEPROM_ADDRESS); } else if (key == KEY3_PRESS) { save_value++; if (save_value == 255 ) save_value = 255 ; } else if (key == KEY4_PRESS) { save_value = 0 ; } save_buf[0 ] = save_value / 100 ; save_buf[1 ] = save_value % 100 / 10 ; save_buf[2 ] = save_value % 100 % 10 ; smg_display(6 ,save_buf); } }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "smg.h" #include "key.h" #include "EXTI.h" #include "iic.h" #include "EEPROM.h" #endif
按键1控制秒表启动与暂停,按键2使秒表清0,按键3将秒表数据存储到EEPROM,按键4读取EEPROM中存储的数据
这个是一种新的思路,把按键和数码管的驱动函数放到定时器中断服务函数这里来,让定时器可以实现多个模块进行计时,实现更高效率的计时
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 #include "AllHead.h" u8 key_ketNumber = 0 ; u8 key (void ) { u8 temp; temp = key_ketNumber; key_ketNumber = 0 ; return temp; } u8 Key_GetState (void ) { u8 KeyNumber = 0 ; if (KEY1 == 0 ) KeyNumber = KEY1_PRESS; if (KEY2 == 0 ) KeyNumber = KEY2_PRESS; if (KEY3 == 0 ) KeyNumber = KEY3_PRESS; if (KEY4 == 0 ) KeyNumber = KEY4_PRESS; return KeyNumber; } void Key_loop (void ) { static u8 NowState,LastState; LastState = NowState; NowState = Key_GetState(); if (LastState == KEY1_PRESS && NowState == KEY_UNPRESS) key_ketNumber = KEY1_PRESS; if (LastState == KEY2_PRESS && NowState == KEY_UNPRESS) key_ketNumber = KEY2_PRESS; if (LastState == KEY3_PRESS && NowState == KEY_UNPRESS) key_ketNumber = KEY3_PRESS; if (LastState == KEY4_PRESS && NowState == KEY_UNPRESS) key_ketNumber = KEY4_PRESS; }
Key.h
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 #ifndef _KEY_H_ #define _KEY_H_ #include "AllHead.h" sbit KEY1 = P3^1 ; sbit KEY2 = P3^0 ; sbit KEY3 = P3^2 ; sbit KEY4 = P3^3 ; #define KEY1_PRESS 1 #define KEY2_PRESS 2 #define KEY3_PRESS 3 #define KEY4_PRESS 4 #define KEY_UNPRESS 0 u8 Key_GetState (void ) ; u8 key (void ) ; void Key_loop (void ) ;#endif
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 #include "AllHead.h" u8 smg_Buf[9 ] = {0 ,10 ,10 ,10 ,10 ,10 ,10 ,10 ,10 }; u8 gsmg_code[]={0x3F ,0x06 ,0x5B ,0x4F ,0x66 ,0x6D ,0x7D ,0x07 ,0x7F ,0x6F ,0x00 ,0x40 }; void smg_SetBuf (u8 location,u8 number) { smg_Buf[location] = number; } void smg_display (u8 location,u8 number) { SMG_A_DP_PORT = 0x00 ; switch (location) { case 1 : LSC = 1 ;LSB = 1 ;LSA = 1 ;break ; case 2 : LSC = 1 ;LSB = 1 ;LSA = 0 ;break ; case 3 : LSC = 1 ;LSB = 0 ;LSA = 1 ;break ; case 4 : LSC = 1 ;LSB = 0 ;LSA = 0 ;break ; case 5 : LSC = 0 ;LSB = 1 ;LSA = 1 ;break ; case 6 : LSC = 0 ;LSB = 1 ;LSA = 0 ;break ; case 7 : LSC = 0 ;LSB = 0 ;LSA = 1 ;break ; case 8 : LSC = 0 ;LSB = 0 ;LSA = 0 ;break ; } SMG_A_DP_PORT = gsmg_code[number]; } void smg_loop (void ) { static u8 i = 1 ; smg_display(i,smg_Buf[i]); i++; if (i > 8 ) i = 1 ; }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 location,u8 number) ;void smg_loop (void ) ;void smg_SetBuf (u8 location,u8 number) ;#endif
EXTI.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include "AllHead.h" void Time0_Init (void ) { TMOD |= 0x01 ; TH0 = 0xFC ; TL0 = 0X66 ; ET0 = 1 ; EA = 1 ; TR0 = 1 ; }
EXTI.h
1 2 3 4 5 6 7 8 #ifndef _EXTI_H_ #define _EXTI_H_ #include "AllHead.h" void Time0_Init (void ) ;#endif
IIC.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" void I2C_Start (void ) { I2C_SDA=1 ; I2C_SCL=1 ; I2C_SDA=0 ; I2C_SCL=0 ; } void I2C_Stop (void ) { I2C_SDA=0 ; I2C_SCL=1 ; I2C_SDA=1 ; } void I2C_SendByte (u8 Byte) { u8 i; for (i=0 ;i<8 ;i++) { I2C_SDA=Byte&(0x80 >>i); I2C_SCL=1 ; I2C_SCL=0 ; } } u8 I2C_ReceiveByte (void ) { u8 i,Byte=0x00 ; I2C_SDA=1 ; for (i=0 ;i<8 ;i++) { I2C_SCL=1 ; if (I2C_SDA) { Byte|=(0x80 >>i); } I2C_SCL=0 ; } return Byte; } void I2C_SendAck (u8 AckBit) { I2C_SDA=AckBit; I2C_SCL=1 ; I2C_SCL=0 ; } u8 I2C_ReceiveAck (void ) { u8 AckBit; I2C_SDA=1 ; I2C_SCL=1 ; AckBit=I2C_SDA; I2C_SCL=0 ; return AckBit; }
IIC.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef _IIC_H_ #define _IIC_H_ #include "AllHead.h" sbit I2C_SCL = P2^1 ; sbit I2C_SDA = P2^0 ; void I2C_Start (void ) ;void I2C_Stop (void ) ;void I2C_SendByte (u8 Byte) ;u8 I2C_ReceiveByte (void ) ; void I2C_SendAck (u8 AckBit) ;u8 I2C_ReceiveAck (void ) ; #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 #include "AllHead.h" void EEPROM_WriteByte (u8 addr,u8 Data) { I2C_Start(); I2C_SendByte(0xA0 ); I2C_ReceiveAck(); I2C_SendByte(addr); I2C_ReceiveAck(); I2C_SendByte(Data); I2C_ReceiveAck(); I2C_Stop(); } u8 EEPROM_ReadByte (u8 addr) { u8 Data; I2C_Start(); I2C_SendByte(0xA0 ); I2C_ReceiveAck(); I2C_SendByte(addr); I2C_ReceiveAck(); I2C_Start(); I2C_SendByte(0xA1 ); I2C_ReceiveAck(); Data=I2C_ReceiveByte(); I2C_SendAck(1 ); I2C_Stop(); return Data; }
EEPROM.h
1 2 3 4 5 6 7 8 9 #ifndef _EEPROM_H_ #define _EEPROM_H_ #include "AllHead.h" void EEPROM_WriteByte (u8 addr,u8 Data) ;u8 EEPROM_ReadByte (u8 addr) ; #endif
Delay.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 "AllHead.h" void Delay1ms (unsigned int xms) { unsigned char i, j; while (xms) { i = 2 ; j = 239 ; do { while (--j); } while (--i); xms--; } }
Delay.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void Delay1ms (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 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 #include "AllHead.h" u8 key_Number = 0 ; u8 Min,Sec,MinSec; u8 RunFlag; void main ( ) { Time0_Init(); while (1 ) { key_Number = key(); if (key_Number == KEY1_PRESS) { RunFlag = !RunFlag; } if (key_Number == KEY2_PRESS) { Min = 0 ; Sec = 0 ; MinSec = 0 ; } if (key_Number == KEY3_PRESS) { EEPROM_WriteByte(0 ,Min); Delay1ms(5 ); EEPROM_WriteByte(1 ,Sec); Delay1ms(5 ); EEPROM_WriteByte(2 ,MinSec); Delay1ms(5 ); } if (key_Number == KEY4_PRESS) { Min = EEPROM_ReadByte(0 ); Sec = EEPROM_ReadByte(1 ); MinSec = EEPROM_ReadByte(2 ); } smg_SetBuf(1 ,Min/10 ); smg_SetBuf(2 ,Min%10 ); smg_SetBuf(3 ,11 ); smg_SetBuf(4 ,Sec/10 ); smg_SetBuf(5 ,Sec%10 ); smg_SetBuf(6 ,11 ); smg_SetBuf(7 ,MinSec/10 ); smg_SetBuf(8 ,MinSec%10 ); } } void Sec_loop (void ) { if (RunFlag) { MinSec++; if (MinSec >= 100 ) { MinSec = 0 ; Sec++; if (Sec >= 60 ) { Sec = 0 ; Min++; if (Min >= 60 ) { Min = 0 ; } } } } } void Time0 () interrupt 1 { static u16 count1 = 0 ,count2 = 0 ,count3 = 0 ; TH0 = 0xFC ; TL0 = 0X66 ; count1++; if (count1 == 20 ) { count1 = 0 ; Key_loop(); } count2++; if (count2 == 2 ) { count2 = 0 ; smg_loop(); } count3++; if (count3 == 10 ) { count3 = 0 ; Sec_loop(); }
AllHead.h
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "smg.h" #include "key.h" #include "EXTI.h" #include "iic.h" #include "EEPROM.h" #endif
DS18B20-温度传感器
DS18B20 介绍
DS18B20 是一种“ 一线总线(单总线)”接口的温度传感器。它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。
DS18B20 温度传感器的特点:
1、适应电压范围更宽,电压范围:3.0~5.5V,在寄生电源方式下可由数据线供电。
2、独特的单线接口方式,DS18B20 在与微处理器连接时仅需要一条口线即可实现微处理器与 DS18B20 的双向通讯。
3、DS18B20 支持多点组网功能,多个 DS18B20 可以并联在唯一的三线上,实现组网多点测温。
4、DS18B20 在使用中不需要任何外围元件,全部传感元件及转换电路集成在形如一只三极管的集成电路内。
5、温度范围 -55℃~+125℃,在-10~+85℃时精度为±0.5℃
6、可编程的分辨率为 9~12 位,对应的可分辨温度分别为 0.5℃、0.25℃、0.125℃ 和 0.0625℃,可实现高精度测温。
7、在 9 位分辨率时最多在 93.75ms 内把温度转换为数字,12 位分辨率时最多在 750ms 内把温度值转换为数字,速度更快。
8、测量结果直接输出数字温度信号,以"一根总线"串行传送给 CPU,同时可传送 CRC 校验码,具有极强的抗干扰纠错能力。
9、负压特性:电源极性接反时,芯片不会因发热而烧毁,但不能正常工作。
DS18B20 外观实物如下图所示:
从 DS18B20 外观图可以看到,当我们正对传感器切面(传感器型号字符那一面)时,传感器的管脚顺序是从左到右排列。管脚 1 为 GND,管脚 2 为数据DQ,管脚 3 为 VDD。如果把传感器插反,那么电源将短路,传感器就会发烫,很容易损坏,所以一定要注意传感器方向,通常在开发板上都会标出传感器的凸起出,所以只需要把传感器凸起的方向对着开发板凸起方向插入即可。
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)→ 连续读出两个字节数据(即温度) → 结束
DS18B20 模块电路原理图
传感器接口的单总线管脚接至单片机 P3.7 IO 口上
DS18B20 实验
插上 DS18B20 温度传感器,数码管显示检测的温度值
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 void smg_display (u8 dat[], u8 location) { u8 i = 0 ; u8 location_temp = location - 1 ; for (i = location_temp; i < 8 ; i++) { switch (i) { case 0 : LSC = 1 ; LSB = 1 ; LSA = 1 ; break ; case 1 : LSC = 1 ; LSB = 1 ; LSA = 0 ; break ; case 2 : LSC = 1 ; LSB = 0 ; LSA = 1 ; break ; case 3 : LSC = 1 ; LSB = 0 ; LSA = 0 ; break ; case 4 : LSC = 0 ; LSB = 1 ; LSA = 1 ; break ; case 5 : LSC = 0 ; LSB = 1 ; LSA = 0 ; break ; case 6 : LSC = 0 ; LSB = 0 ; LSA = 1 ; break ; case 7 : LSC = 0 ; LSB = 0 ; LSA = 0 ; break ; } SMG_A_DP_PORT = dat[i - location_temp]; delay_10us(100 ); SMG_A_DP_PORT = 0x00 ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 dat[], u8 location) ;extern u8 gsmg_code[17 ]; #endif
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 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 #include "AllHead.h" void ds18b20_reset (void ) { DS18B20_PORT = 0 ; delay_10us(75 ); DS18B20_PORT = 1 ; delay_10us(2 ); } u8 ds18b20_check (void ) { u8 time_temp = 0 ; while (DS18B20_PORT && time_temp < 20 ) { time_temp++; delay_10us(1 ); } if (time_temp >= 20 ) return 1 ; else time_temp = 0 ; while ((!DS18B20_PORT) && time_temp < 20 ) { time_temp++; delay_10us(1 ); } if (time_temp >= 20 ) return 1 ; else return 0 ; } u8 ds18b20_init (void ) { ds18b20_reset(); return ds18b20_check(); } void ds18b20_write_byte (u8 dat) { u8 i = 0 ; u8 temp = 0 ; for (i = 0 ; i < 8 ; i++) { temp = dat & 0x01 ; dat >>= 1 ; if (temp) { DS18B20_PORT = 0 ; _nop_(); _nop_(); DS18B20_PORT = 1 ; delay_10us(6 ); } else { DS18B20_PORT = 0 ; delay_10us(6 ); DS18B20_PORT = 1 ; _nop_(); _nop_(); } } } u8 ds18b20_read_bit (void ) { u8 dat = 0 ; DS18B20_PORT = 0 ; _nop_(); _nop_(); DS18B20_PORT = 1 ; _nop_(); _nop_(); if (DS18B20_PORT) dat = 1 ; else dat = 0 ; delay_10us(5 ); return dat; } u8 ds18b20_read_byte (void ) { u8 i = 0 ; u8 dat = 0 ; u8 temp = 0 ; for (i = 0 ; i < 8 ; i++) { temp = ds18b20_read_bit(); dat = (temp << 7 ) | (dat >> 1 ); } return dat; } void ds18b20_start (void ) { ds18b20_reset(); ds18b20_check(); ds18b20_write_byte(0xCC ); ds18b20_write_byte(0x44 ); } u8 ds18b20_read_temperture (void ) { u8 dath = 0 ; u8 datl = 0 ; u16 value = 0 ; float temp; ds18b20_start(); ds18b20_reset(); ds18b20_check(); ds18b20_write_byte(0xCC ); ds18b20_write_byte(0xBE ); datl = ds18b20_read_byte(); dath = ds18b20_read_byte(); value = (dath << 8 ) + datl; if (value && 0xF800 == 0xf800 ) { value = (~value) + 1 ; temp = value * (-0.0625 ); } else temp = value * 0.0625 ; return temp; }
ds18b20.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef _DS18B20_H #define _DS18B20_H #include "AllHead.h" sbit DS18B20_PORT = P3^7 ; void ds18b20_reset (void ) ;u8 ds18b20_check (void ) ; u8 ds18b20_init (void ) ; void ds18b20_write_byte (u8 dat) ;u8 ds18b20_read_bit (void ) ; void ds18b20_write_byte (u8 dat) ;void ds18b20_start (void ) ;u8 ds18b20_read_temperture (void ) ; #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 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;#endif
main.c
主函数代码很简单,在 while循环中间隔一定时间读取温度数据,并将温度值保留小数点后一位,然后将读取的温度数据转换为数码管可显示的段码,最后调用数码管显示函数
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 #include "AllHead.h" void main () { u8 i = 0 ; int temp_value; u8 temp_buf[5 ]; ds18b20_init(); while (1 ) { i++; if (i % 50 == 0 ) temp_value = ds18b20_read_temperture() * 10 ; if (temp_value < 0 ) { temp_value = -temp_value; temp_buf[0 ] = 0x40 ; } else { temp_buf[0 ] = 0x00 ; } temp_buf[1 ] = gsmg_code[temp_value / 1000 ]; temp_buf[2 ] = gsmg_code[temp_value % 1000 / 100 ]; temp_buf[3 ] = gsmg_code[temp_value % 1000 % 100 / 10 ] | 0x80 ; temp_buf[4 ] = gsmg_code[temp_value % 1000 % 100 % 10 ]; smg_display(temp_buf, 4 ); } }
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "intrins.h" #include "smg.h" #include "ds18b20.h" #endif
注意:一定要注意温度传感器的方向,在接口处我们已经用丝印画了一个凸起,所以只需要将温度传感器对应插入即可
DS1302 时钟
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;
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 时停止。
小时寄存器 :时寄存器。最高位为 12/24 小时的格式选择位,该位为 1 时表示 12 小时格式。当设置为 12 小时显示格式时,第 5 位的高电平表示下午(PM);而当设置为 24 小时格式时,第 5 位为具体的时间数据。
写保护寄存器 :当该寄存器最高位 WP 为 1 时,DS1302只读不写,所以要在往 DS1302 写数据之前确保 WP 为 0。
慢充电寄存器(涓细电流充电)寄存器 :当 DS1302 掉电时,可以马上调用外部电源保护时间数据。该寄存器就是配置备用电源的充电选项的。其中高四位(4 个 TCS)只有在 1010 的情况下才能使用充电选项;低四位的情况与 DS1302 内部电路有关。
什么是BCD 码
BCD 码是通过 4 位二进制码来表示 1 位十进制中的 0~9 这 10 个数码
如下所示:
从 DS1302 中读取出来的时钟数据均为 BCD 码格式,需转换为我们习惯的 10 进制
DS1302 的读写时序
在控制指令字输入后的下一个 SCLK 时钟的上升沿时,数据被写入 DS1302,数据输入从低位(位 0)开始。同样,在紧跟 8 位的控制指令字后的下一个 SCLK脉冲的下降沿读出 DS1302 的数据,读出数据时从低位 0 位到高位 7。其时序图如下所示:
上图就是 DS1302 的三个时序:复位时序,单字节写时序,单字节读时序;
CE(RST ):复位时序,即在 RST 引脚产生一个正脉冲,在整个读写器件,RST 要保持高电平,一次字节读写完毕之后,要注意把 RST 返回低电平准备下次读写周期;
单字节读时序 :注意读之前还是要先对寄存器写命令,前八个为控制寄存器地址,从最低位开始写;可以看到,写数据是在 SCLK 的上升沿实现,而读数据在 SCLK 的下降沿实现。所以,在单字节读时序中,写命令的第八个上升沿结束后紧接着的第八个下降沿就将要读寄存器的第一位数据读到数据线上了!这个就是 DS1302 操作中最特别的地方。当然读出来的数据也是最低位开始。
单字节写时序 :两个字节的数据配合 16 个上升沿将数据写入即可。
程序注意事项 :
★要记得在操作 DS1302 之前关闭写保护(即确保 WP 为 0);
★注意用延时来降低单片机的速度以配合器件时序;
★DS1302 读出来的数据是 BCD 码形式,要转换成我们习惯的 10 进制;
★读取字节之前,将 IO 设置为输入口,读取完之后,要将其改回输出口;
★在写程序的时候,建议实现开辟数组(内存空间)来集中放置 DS1302 的一系列数据,方便以后扩展键盘输入。
DS1302时钟原理图
DS1302 芯片的控制管脚接至单片机 P3.4-P3.6 上,在芯片的X1、X2 管脚处外接了一个32.768KHZ 晶振,为时钟运行提供一个稳定的时钟频率,C2 和 C3 为旁路电容,目的是消除晶振起振时产生的电感干扰。如果需要备用电源可将外部备用电源接入第 8 脚 VCC1。
DS1302时钟实验程序
数码管上显示电子时钟时分秒,格式为“XX-XX-XX”
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 void smg_display (u8 dat[], u8 location) { u8 i = 0 ; u8 location_temp = location - 1 ; for (i = location_temp; i < 8 ;i++) { switch (i) { case 0 : LSC = 1 ;LSB = 1 ;LSA = 1 ;break ; case 1 : LSC = 1 ;LSB = 1 ;LSA = 0 ;break ; case 2 : LSC = 1 ;LSB = 0 ;LSA = 1 ;break ; case 3 : LSC = 1 ;LSB = 0 ;LSA = 0 ;break ; case 4 : LSC = 0 ;LSB = 1 ;LSA = 1 ;break ; case 5 : LSC = 0 ;LSB = 1 ;LSA = 0 ;break ; case 6 : LSC = 0 ;LSB = 0 ;LSA = 1 ;break ; case 7 : LSC = 0 ;LSB = 0 ;LSA = 0 ;break ; } SMG_A_DP_PORT = dat[i - location_temp]; delay_10us(100 ); SMG_A_DP_PORT=0x00 ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 dat[], u8 location) ;extern u8 gsmg_code[17 ]; #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 128 129 #include "AllHead.h" u8 gWRITE_addr[7 ] = {0x80 ,0x82 ,0x84 ,0x86 ,0x88 ,0x8a ,0x8c }; u8 gRREAD_addr[7 ] = {0x81 ,0x83 ,0x85 ,0x87 ,0x89 ,0x8b ,0x8d }; u8 gDS1302_TIME[7 ] = {0x01 ,0x34 ,0x18 ,0x08 ,0x08 ,0x02 ,0x23 }; void ds1302_write_byte (u8 addr,u8 dat) { u8 i = 0 ; DS1302_CE = 0 ; _nop_(); DS1302_SCLK = 0 ; _nop_(); DS1302_CE = 1 ; _nop_(); for (i = 0 ;i < 8 ;i++) { DS1302_IO = addr & 0x01 ; addr >>= 1 ; DS1302_SCLK = 1 ; _nop_(); DS1302_SCLK = 0 ; _nop_(); } for (i = 0 ;i < 8 ;i++) { DS1302_IO = dat & 0x01 ; dat >>= 1 ; DS1302_SCLK = 1 ; _nop_(); DS1302_SCLK = 0 ; _nop_(); } DS1302_CE = 0 ; _nop_(); } u8 ds1302_read_byte (u8 addr) { u8 i = 0 ; u8 temp = 0 ; u8 dat = 0 ; DS1302_CE = 0 ; _nop_(); DS1302_SCLK = 0 ; _nop_(); DS1302_CE = 1 ; _nop_(); for (i = 0 ;i < 8 ;i++) { DS1302_IO = addr & 0x01 ; addr >>= 1 ; DS1302_SCLK = 1 ; _nop_(); DS1302_SCLK = 0 ; _nop_(); } for (i = 0 ;i < 8 ;i++) { temp = DS1302_IO; dat = (temp << 7 ) | (dat >> 1 ); DS1302_SCLK = 1 ; _nop_(); DS1302_SCLK = 0 ; _nop_(); } DS1302_CE = 0 ; _nop_(); DS1302_SCLK = 1 ; _nop_(); DS1302_IO = 0 ; _nop_(); DS1302_IO = 1 ; _nop_(); return dat; } void ds1302_init (void ) { u8 i = 0 ; ds1302_write_byte(0x8E ,0X00 ); for (i = 0 ;i < 7 ;i++) { ds1302_write_byte(gWRITE_addr[i],gDS1302_TIME[i]); } ds1302_write_byte(0x8E ,0X80 ); } void ds1302_read_time (void ) { u8 i = 0 ; for (i = 0 ;i < 7 ;i++) { gDS1302_TIME[i] = ds1302_read_byte(gRREAD_addr[i]); } }
ds1302.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef _DS1302_H_ #define _DS1302_H_ #include "AllHead.h" sbit DS1302_CE = P3^5 ; sbit DS1302_IO = P3^4 ; sbit DS1302_SCLK = P3^6 ; 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 gDS1302_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 #include "AllHead.h" void main ( ) { u8 time_buf[8 ]; ds1302_init(); while (1 ) { ds1302_read_time(); time_buf[0 ] = gsmg_code[gDS1302_TIME[2 ] / 16 ]; time_buf[1 ] = gsmg_code[gDS1302_TIME[2 ] % 16 ]; time_buf[2 ] = 0x40 ; time_buf[3 ] = gsmg_code[gDS1302_TIME[1 ] / 16 ]; time_buf[4 ] = gsmg_code[gDS1302_TIME[1 ] % 16 ]; time_buf[5 ] = 0x40 ; time_buf[6 ] = gsmg_code[gDS1302_TIME[0 ] / 16 ]; time_buf[7 ] = gsmg_code[gDS1302_TIME[0 ] % 16 ]; smg_display(time_buf,1 ); } }
主函数代码先初始化 DS1302 并设定好初始时间,进入 while 循环,读取 DS1302 时钟数据存储至全局变量数组gDS1302_TIME 中,最后将读取的数据转换为数码管可显示的段码数据并调用数码管显示函数显示时间。
在处理 DS1302 读取的数据时,取高低位是使用除16和取余16,并非之前的除 10 和取余 10。这是因为写入进 DS1302 时是 BCD 码,读取出的数据也是 BCD 码,而 BCD 码即是 4 位表示一个十进制数,类似于一个字节的十六进制数据的高 4 位和低 4 位一样,所以这里是除 16 和取余 16
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "intrins.h" #include "smg.h" #include "ds1302.h" #endif
红外遥控
红外遥控介绍
(1) 红外线简介
人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、蓝、紫。其中红光的波长范围为 0.62~0.76μm;紫光的波长范围为 0.38~0.46μm。比紫光波长还短的光叫紫外线,比红光波长还长的光叫红外线。红外线遥控就是利用波长为 0.76~1.5μm 之间的近红外线来传送控制信号的。
(2) 红外遥控的原理
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。
红外遥控通信系统一般由红外发射装置和红外接收设备两大部分组成。
1) 红外发射装置
红外发射装置,也就是通常我们说的红外遥控器是由键盘电路、红外编码电路、电源电路和红外发射电路组成。红外发射电路的主要元件为红外发光二极管。它实际上是一只特殊的发光二极管;由于其内部材料不同于普通发光二极管,因而在其两端施加一定电压时,它便发出的是红外线而不是可见光。目前大量的使用的红外发光二极管发出的红外线波长为 940nm 左右,外形与普通发光二极管相同。红外发光二极管有透明的,还有不透明的。红外遥控器和红外发光二极管如下图所示:
通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为 38kHz,这是由发射端所使用的 455kHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取 12,所以455kHz÷12≈37.9kHz≈38kHz。也有一些遥控系统采用 36kHz、 40 kHz、 56 kHz等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信号(二进制脉冲码)调制在 38KHz 的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。
二进制脉冲码的形式有多种,其中最为常用的是 NEC Protocol 的 PWM码(脉冲宽度调制)和 Philips RC-5 Protocol 的PPM 码脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率。开发板配套的红外遥控器使用的是 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 高电平组成),如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次数。
2) 红外接收设备
红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥控接收器的主要作用是将遥控发射器发来的红外光信好转换成电信号,再放大、限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器。成品红外接收头的封装大致有两种:一种采用铁皮屏蔽;一种是塑料封装。均有三只引脚,即电源正( VDD)、电源负(GND)和数据输出(VOUT)。其外观实物图如下图所示:
正对接收头的凸起处看,从左至右,管脚依次是1:VOUT,2:GND,3:VDD。由于红外接收头在没有脉冲的时候为高电平,当收到脉冲的时候为低电平,所以可以通过外部中断的下降沿触发中断,在中断内通过计算高电平时间来判断接收到的数据是 0 还是 1。
红外接收模块电路
从上图中可知,红外接收头的输出管脚接至单片机 P3.2 管脚上,为了保证红外接收头输出管脚默认为高电平,需外接一个 10K 上拉电阻。
红外遥控实验
数码管上显示红外解码遥控器键值
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 void smg_display (u8 dat[], u8 location) { u8 i = 0 ; u8 location_temp = location - 1 ; for (i = location_temp; i < 8 ; i++) { switch (i) { case 0 : LSC = 1 ; LSB = 1 ; LSA = 1 ; break ; case 1 : LSC = 1 ; LSB = 1 ; LSA = 0 ; break ; case 2 : LSC = 1 ; LSB = 0 ; LSA = 1 ; break ; case 3 : LSC = 1 ; LSB = 0 ; LSA = 0 ; break ; case 4 : LSC = 0 ; LSB = 1 ; LSA = 1 ; break ; case 5 : LSC = 0 ; LSB = 1 ; LSA = 0 ; break ; case 6 : LSC = 0 ; LSB = 0 ; LSA = 1 ; break ; case 7 : LSC = 0 ; LSB = 0 ; LSA = 0 ; break ; } SMG_A_DP_PORT = dat[i - location_temp]; delay_10us(100 ); SMG_A_DP_PORT = 0x00 ; } }
smg.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef _SMG_H_ #define _SMG_H_ #include "AllHead.h" #define SMG_A_DP_PORT P0 sbit LSA = P2^2 ; sbit LSB = P2^3 ; sbit LSC = P2^4 ; void smg_display (u8 dat[], u8 location) ;extern u8 gsmg_code[17 ]; #endif
ired.c
使用外部中断 0 来解码红外遥控数据,所以需初始化配置外部中断0。
初始化外部中断后,中断就已经开启了,当 P32 引脚来一个下降沿,就会触发一次中断,在中断内我们可以计算高电平时间,通过高电平时间判断是否进入引导码及数据 0 和 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 #include "AllHead.h" u8 gired_data[4 ]; void ired_init (void ) { IT0 = 1 ; EX0 = 1 ; EA = 1 ; IRED = 1 ; } void ired () interrupt 0 { u16 time_cnt = 0 ; u8 i = 0 , j = 0 ; u8 ired_high_time = 0 ; if (IRED == 0 ) { time_cnt = 1000 ; while ((!IRED) && (time_cnt)) { delay_10us(1 ); time_cnt--; if (time_cnt == 0 ) return ; } if (IRED) { time_cnt = 500 ; while ((IRED) && (time_cnt)) { delay_10us(1 ); time_cnt--; if (time_cnt == 0 ) return ; } for (i = 0 ; i < 4 ; i++) { for (j = 0 ; j < 8 ; j++) { time_cnt = 600 ; while ((!IRED) && (time_cnt)) { delay_10us(1 ); time_cnt--; if (time_cnt == 0 ) return ; } while (IRED) { 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 11 12 13 14 15 #ifndef _IRED_H_ #define _IRED_H_ #include "AllHead.h" sbit IRED = P3^2 ; void ired_init (void ) ;extern u8 gired_data[4 ];#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 10 11 #ifndef _DELAY_H #define _DELAY_H #include "AllHead.h" typedef unsigned char u8; typedef unsigned int u16; void delay_10us (u16 ten_us) ;#endif
main.c
主函数代码首先初始化红外,进入while 循环将红外解码数据转换为数码管段码数据,然后在数码管上显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "AllHead.h" void main ( ) { u8 ired_buf[3 ]; ired_init(); while (1 ) { ired_buf[0 ] = gsmg_code[gired_data[2 ] / 16 ]; ired_buf[1 ] = gsmg_code[gired_data[2 ] % 16 ]; ired_buf[2 ] = 0x76 ; smg_display(ired_buf, 6 ); } }
AllHead.h
1 2 3 4 5 6 7 8 9 #ifndef _ALLHEAD_H #define _ALLHEAD_H #include <reg52.h> #include "Delay.h" #include "smg.h" #include "ired.h" #endif
注意:红外接收头的凸起位置需对到 PCB 板丝印凸出位置,否则功能无法正常实现。