黄石建设信息网站,福步外贸论坛怎样注册,石龙网站设计,青岛做网站的公司哪家好嵌入式入门教学汇总#xff1a; 嵌入式入门教学——C51#xff08;上#xff09;嵌入式入门教学——C51#xff08;中#xff09;嵌入式入门教学——C51#xff08;下#xff09;
目录
七、矩阵键盘
八、定时器和中断
九、串口通信
十、LED点阵屏
十一、DS1302实…嵌入式入门教学汇总 嵌入式入门教学——C51上嵌入式入门教学——C51中嵌入式入门教学——C51下
目录
七、矩阵键盘
八、定时器和中断
九、串口通信
十、LED点阵屏
十一、DS1302实时时钟
十二、蜂鸣器 七、矩阵键盘
在键盘中按键数量较多时为了减少I/0口的用通常将按键排列成矩阵形式。采用逐行或逐列的“扫描”就可以读出任何位置按键的状态。
1、扫描
数码管扫描输出扫描 原理显示第1位 - 显示第2位 - 显示第3位 - ...然后快速循环这个过程最终实现所有数码管同时显示的效果动态显示。矩阵键盘扫描输入扫描 原理读取第1行(列) - 读取第2行(列) - 读取第3行(列) - ...然后快速循环这个过程最终实现所有按键同时检测的效果。以上两种扫描方式的共性节省I/O口。
2、矩阵按键原理图
I/O口如何知道哪个按键被按下 按行扫描 将P17、P16、P15、P14依次循环为0(看作接地)其余为1。如果此时P17为0当S1按下P13为0当S2按下P12为0。【注】按行扫描时蜂鸣器会响是由于引脚冲突造成的。按列扫描 将P13、P12、P11、P10依次循环为0(看作接地)其余为1。如果此时P13为0当S1按下P17为0当S5按下P16为0。
3、矩阵键盘键码显示LCD1602显示
内容按下矩阵键盘的按键在LCD1602上显示对应的键码值。新建工程在工程目录下新建Functions、Objects、Listings文件夹。将LCD1602的驱动代码和延时函数的代码复制到Functions中。代码上面有设置.hex文件和.lst文件存放位置。新建main.c文件将Delay.c和LCD1602.c添加到工程中并且设置引入路径。编写矩阵键盘模块新建MatrixKey.c和MatrixKey.h用来存放矩阵键盘模块。编写MatrixKey.h文件。 #ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__
unsigned char MatrixKey();
#endif 【小技巧】快速生成头文件的模板 添加后双击即可使用。编写MatrixKey.c文件。 #include REGX52.H
#include Delay.h
/*** brief 矩阵键盘读取按键键码* param 无* retval KeyNumber 按下按键的键码值如果按键按下不放程序会停留在此函数松手的瞬间返回键码没有按键按下时返回零。*/
unsigned char MatrixKey(){unsigned char KeyNumber0;// 按列扫描// 第一列P10xFF;P1_30;if(P1_70){ Delay(20);while(P1_70);Delay(20);KeyNumber1;}if(P1_60){ Delay(20);while(P1_60);Delay(20);KeyNumber5;}if(P1_50){ Delay(20);while(P1_50);Delay(20);KeyNumber9;}if(P1_40){ Delay(20);while(P1_40);Delay(20);KeyNumber13;}// 第二列P10xFF;P1_20;if(P1_70){ Delay(20);while(P1_70);Delay(20);KeyNumber2;}if(P1_60){ Delay(20);while(P1_60);Delay(20);KeyNumber6;}if(P1_50){ Delay(20);while(P1_50);Delay(20);KeyNumber10;}if(P1_40){ Delay(20);while(P1_40);Delay(20);KeyNumber14;}// 第三列P10xFF;P1_10;if(P1_70){ Delay(20);while(P1_70);Delay(20);KeyNumber3;}if(P1_60){ Delay(20);while(P1_60);Delay(20);KeyNumber7;}if(P1_50){ Delay(20);while(P1_50);Delay(20);KeyNumber11;}if(P1_40){ Delay(20);while(P1_40);Delay(20);KeyNumber15;}// 第四列P10xFF;P1_00;if(P1_70){ Delay(20);while(P1_70);Delay(20);KeyNumber4;}if(P1_60){ Delay(20);while(P1_60);Delay(20);KeyNumber8;}if(P1_50){ Delay(20);while(P1_50);Delay(20);KeyNumber12;}if(P1_40){ Delay(20);while(P1_40);Delay(20);KeyNumber16;}return KeyNumber;
} 编写main.c文件。 #include REGX52.H
#include Delay.h
#include LCD1602.h
#include MatrixKey.h
unsigned char KeyNum;
void main(){LCD_Init();LCD_ShowString(1,1,Which One?);while(1){KeyNumMatrixKey();if(KeyNum){ // 不加ifKeyNum会马上刷新为0LCD_ShowNum(2,1,KeyNum,2);}}
} 编译下载程序到单片机按键后LCD1602显示对应键码值。
4、矩阵键盘密码锁LCD1602显示
内容s1~s10为1~9和0s11为确定s12为取消。输入密码正确显示TRUE输入密码错误显示ERR。复制上一个工程的文件夹并修改名称快速创建将其中的MatrixKey.c和MatrixKey.h放入Functions中。打开工程将原先的MatrixKey.c和MatrixKey.h从工程中删除重新添加MatrixKey.c文件并设置引入路径。将矩阵键盘模块化修改main.c文件。 #include REGX52.H
#include Delay.h
#include LCD1602.h
#include MatrixKey.h
unsigned char KeyNum;
unsigned int Password,Count;
void main(){LCD_Init();LCD_ShowString(1,1,Password:);while(1){KeyNumMatrixKey();if(KeyNum){ // 不加ifKeyNum会马上刷新为0if(KeyNum10){ // 如果是s1~s10按下输入密码if(Count4){ // 限制密码位数Password*10; // 密码左移一位PasswordKeyNum%10; // 将10转化为0获得一位密码Count; // 记录密码位数}}LCD_ShowNum(2,1,Password,4); // 更新显示}if(KeyNum11){ // s11按下确认if(Password1234){ // 判断密码LCD_ShowString(1,11,TRUE); // 密码正确Password0; // 密码清零Count0; // 计次清零LCD_ShowNum(2,1,Password,4); // 更新显示}else{LCD_ShowString(1,11,ERR); // 密码错误Password0; // 密码清零Count0; // 计次清零LCD_ShowNum(2,1,Password,4); // 更新显示}}if(KeyNum12){ // s12按下取消Password0; // 密码清零Count0; // 计次清零LCD_ShowNum(2,1,Password,4); // 更新显示}}
} 编译下载程序到单片机调试显示正常。
八、定时器和中断
1、定时器
定时器在单片机内部就像一个小闹钟一样根据时钟的输出信号每隔“一秒”计数单元的数值就增加一当计数单元数值增加到“设定的闹钟提醒时间”时计数单元就会向中断系统发出中断申请产生“响铃提醒”使程序跳转到中断服务函数中执行。定时器作用 用于计时系统可实现软件计时或者使程序每隔一固定时间完成一项操作。替代长时间的Delay提高CPU的运行效率和处理速度。定时器个数3个TO、T1、T2TO和T1与传统的51单片机兼容T2是此型号单片机增加的资源不同的单片机可能有不同数量的定时器。
1.1、定时器工作模式
STC89C52的TO和T1均有四种工作模式 模式013位定时器/计数器模式116位定时器/计数器 (常用)模式28位自动重装模式模式3两个8位计数器模式1框图 由SYSclk(系统时钟)或T0 Pin(外部脉冲)提供脉冲。SYSclk系统时钟即晶振周期本开发板上的晶振为12MHZ。TL0和TH0两个8位范围是0~65535用于计数每隔1us加一直到溢出时产生中断总共定时时间为65535us。 例如需要计时1ms只需要给其赋值64535还有1000到65535刚好为1ms。
1.2、定时器相关寄存器
寄存器是连接软硬件的媒介在单片机中寄存器就是一段特殊的RAM存储器一方面寄存器可以存储和读取数据另一方面每一个寄存器背后都连接了一根导线控制着电路的连接方式寄存器相当于一个复杂机器的“操作按钮”。TCON是控制寄存器控制T0、T1的启动和停止及设置溢出标志即控制定时器启动和中断申请。具体每一位的作用看参考手册 TF1定时器/计数器T1溢出标志。TR1定时器T1的运行控制位。TF0定时器/计数器T0溢出中断标志。TR0定时器T0的运行控制位。IE1外部中断1请求源标志。IT1外部中断1触发方式控制位。IE0外部中断0请求源标志。IT0外部中断0触发方式控制位。TMOD是定时/计数器的工作方式寄存器确定工作方式和功能。具体每一位的作用看参考手册 GATE定时器开关。C/T选择定时器。M1、M0定时器/计数器模式选择。
2、中断
CPU在处理某一事件A时发生了另一事件B请求CPU迅速去处理中断发生CPU暂时中断当前的工作转去处理事件B中断响应和中断服务待CPU将事件B处理完毕后再回到原来事件A被中断的地方继续处理事件A中断返回这一过程称为中断。引起CPU中断的根源称为中断源。被中断的地方称为断点。中断功能的部件称为中断系统。
2.1、中断系统的结构
STC89C52中断资源 中断源个数8个外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、外部中断2、外部中断3中断优先级个数4个中断号中断结构部分 各中断源响应优先级 0 外部中断01 定时/计数器02 外部中断1 3 定时/计数器1 4 串行口中断优先级的三个原则 CPU同时接收到几个中断时首先响应优先级别最高的中断请求。正在进行的中断过程不能被新的同级或低优先级的中断请求所中断。正在进行的低优先级中断服务能被高优先级中断请求所中断。中断系统内部设有两个用户不能寻址的优先级状态触发器。 其中一个置1表示正在响应高优先级的中断它将阻断后来所有的中断请求。另一个置1表示正在响应低优先级中断它将阻断后来所有的低优先级中断请求。中断响应的条件 中断源有中断请求。此中断源的中断允许位为1。CPU开中断即EA1。
2.2、中断寄存器
IE是中断允许寄存器控制中断的开启。 EACPU的总中断允许控制位。ET2定时/计数器T2的溢出中断允许位。ES串行口1中断允许位。ET1定时/计数器T1的溢出中断允许位。EX1外部中断1中断允许位。ET0T0的溢出中断允许位。EX0外部中断0中断允许位。IPH是中断优先级控制寄存器高。具体每一位的作用看参考手册 IP是中断优先级控制寄存器低。 PT0HPT0定时器0中断优先级控制位。其他的看参考手册 当PT0H0且PT00时定时器0中断为最低优先级中断(优先级0)当PT0H0且PT01时定时器0中断为较低优先级中断(优先级1)当PT0H1且PT00时定时器0中断为较高优先级中断(优先级2)当PT0H1且PT01时定时器0中断为最高优先级中断(优先级3)【注】不可位寻址的寄存器只能整体赋值可位寻址的寄存器可以按位赋值。
3、独立按键控制LED流水灯状态
内容按下独立键盘的按键改变LED流水灯的流转方向。新建工程在工程目录下新建Objects、Listings文件夹并设置.hex文件和.lst文件存放位置。
3.1、定时器0初始化
3.1.1、手写
使用定时器0需要对其进行初始化包括选择定时器和其工作模式。对TMOD寄存器操作选择定时器和其工作模式。不可按位赋值 高4位控制定时器1不使用故赋全0即TMOD0000 0001。对TCON寄存器操作控制寄存器开启。可按位赋值 TF0;TR01;配置中断 ET01; EA1; PT00;编写代码 //定时器0初始化
void Timer0Init(){//TMOD0x01; // 0000 0001 这样赋值在同时使用定时器1和定时器0时可能会产生冲突TMOD0xF0; // 把TMOD低四位清零高四位不变TMOD|TMOD|0x01; // 把TMOD最低位置1高四位不变TF00; // 中断溢出标志位TR01; // 开启定时器// 初始化计数器TH064535/256; // 取高位TL064535%2561; // 取低位// 配置中断ET01; // 允许中断EA1; // 允许总中断PT00; // 中断优先级
}
3.1.2、自动生成
也可以使用STC-ISP生成初始化函数。建议使用但是没有配置中断需要另外添加。 void Timer0Init(void) //1毫秒12.000MHz
{TMOD 0xF0; //设置定时器模式TMOD | 0x01; //设置定时器模式TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值TF0 0; //清除TF0标志TR0 1; //定时器0开始计时// 配置中断ET01; // 允许中断EA1; // 允许总中断PT00; // 中断优先级
}
3.1.3、定时器模块化
在工程目录下新建Functions文件夹将Timer0.c和Timer0.h放入其中。将Timer.c加入工程中并设置其引入路径。Timer0.c #include REGX52.H
/*** brief 定时器0初始化1毫秒12.000MHz* param 无* retval 无*/
void Timer0Init(void) //1毫秒12.000MHz
{TMOD 0xF0; //设置定时器模式TMOD | 0x01; //设置定时器模式TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值TF0 0; //清除TF0标志TR0 1; //定时器0开始计时// 配置中断ET01;EA1; PT00;
}
/* 定时器中断函数模板
void Timer0_Routine() interrupt 1{static unsigned int T0Count;TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值T0Count;if(T0Count1000){T0Count0;P2_0~P2_0;}
}
*/ Timer0.h #ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0Init(void);
#endif
3.2、独立按键模块化
将延时函数模块和独立按键模块放入Functions文件夹。将Delay.c和Key.c加入工程中并设置其引入路径。Key.c #include REGX52.H
#include Delay.h
/*** brief 获取独立按键键码* param 无* retval 按下按键的键码范围0~4无按键按下时返回0*/
unsigned char Key(){unsigned char KeyNumber0;if(P3_10){Delay(20);while(P3_10);Delay(20);KeyNumber1;}if(P3_00){Delay(20);while(P3_00);Delay(20);KeyNumber2;}if(P3_20){Delay(20);while(P3_20);Delay(20);KeyNumber3;}if(P3_30){Delay(20);while(P3_30);Delay(20);KeyNumber4;}return KeyNumber;
} Key.h #ifndef __KEY_H__
#define __KEY_H__
unsigned char Key();
#endif3.3、编写main.c文件
定时器0每隔1微秒会执行中断函数而中断函数中每隔500次会执行真正的操作也就是每隔0.5ms会移动一次LED。 #include REGX52.H
#include Timer0.h
#include Key.h
#include intrins.h // 引入_crol_()和_cror_()循环移位函数
unsigned char KeyNum,LEDMode;
void main(){Timer0Init(); // 定时器0初始化P20xFE; // 初始化LEDwhile(1){ // 每当定时器溢出就跳转执行中断函数KeyNumKey();if(KeyNum){if(KeyNum1){LEDMode;if(LEDMode2) LEDMode0; // LEDMode只在0和1变化}}}
}
// 中断函数
void Timer0_Routine() interrupt 1{static unsigned int T0Count;TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值T0Count;if(T0Count500){ // 每隔0.5ms让LED移动T0Count0;if(LEDMode0)P2_crol_(P2,1); // P2左移1位else P2_cror_(P2,1); // P2右移1位}
}
4、定时器时钟LCD1602显示
新建工程在工程目录下新建Functions、Objects、Listings文件夹将延时函数、定时器和LCD1602模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中将.lst文件存放到Listings中。新建main.c文件添加Delay.c、LCD1602.c、Timer0.c到工程中并设置其引入路径。编写main.c文件。 #include REGX52.H
#include Delay.h
#include LCD1602.h
#include Timer0.h
unsigned char Sec55,Min59,Hour23;
void main(){LCD_Init();Timer0Init();LCD_ShowString(1,1,Clock:);LCD_ShowString(2,1, : :);while(1){LCD_ShowNum(2,1,Hour,2); // 小时LCD_ShowNum(2,4,Min,2); // 分钟LCD_ShowNum(2,7,Sec,2); // 秒}
}
// 中断函数
void Timer0_Routine() interrupt 1{static unsigned int T0Count;TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值T0Count;if(T0Count1000){T0Count0;Sec;if(Sec60){Sec0;Min;if(Min60){Min0;Hour;if(Hour24) Hour0;}}}
}
九、串口通信
1、串口
串口是一种应用十分广泛的通讯接口可以实现两个设备的互相通信。例如单片机与单片机、单片机与电脑、单片机与名式各样的模块互相通信。51单片机内部自带UART (Universal Asynchronous ReceiverTransmitter通用异步收发器)可实现单片机的串口通信。
1.1、硬件电路
简单双向串口通信有两根通信线发送端TXD和接收端RXD。TXD与RXD要交叉连接。当只需单向的数据传输时可以直接一根通信线。当电平标准不一致时需要加电平转换芯片。
1.2、电平标准
电平标准是数据1和数据0的表达方式是传输线缆中人为规定的电压串口常用的电平标准有如下三种 TTL电平5V表示10V表示0RS232电平-3~-15V表示13~15V表示0RS485电平两线压差2~6V表示1-2~-6V表示0差分信号
1.3、相关术语
全双工通信双方可以在同一时刻互相传输数据。半双工通信双方可以互相传输数据但必须分时复用一根数据线。单工通信只能有一方发送到另一方不能反向传输。异步通信双方各自约定通信速率。同步通信双方靠一根时钟线来约定通信速率。总线连接各个设备的数据传输线路 (类似于一条马路把路边各连接起来使住户可以相互交流。
1.4、UATR通用异步收发器
STC89C52有1个UART有四种工作模式 模式0同步移位寄存器模式18位UART波特率可变 (常用)模式29位UART波特率固定模式39位UART波特率可变
1.5、USB原理图使用USB进行串口通信
1.6、串口的参数及时序图
波特率串口通信的速率发送和接收各数据位的间隔时间。检验位用于数据验证。停止位用于数据帧间隔。8位数据格式 9位数据格式多一位校验位
1.7、串口相关寄存器
SCON是串口控制寄存器。具体每一位的作用看参考手册 SM0检测帧错误或指定工作模式。SM00SM11时工作在模式1。SM2允许模式2或模式3多机通信。REN接收使能。T1发送中断请求标志位。R1接收中断请求标志位。PCON是电源控制寄存器。 SMOD波特率选择。SMOD0帧错误检测有效控制位。
1.8、串口模式图
2、串口向电脑发送数据
内容单片机通过串口每秒向计算机发送一个逐步加一的数字。新建工程在工程目录下新建Functions、Objects、Listings文件夹将延时函数模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中将.lst文件存放到Listings中。新建main.c文件添加Delay.c到工程中并设置其引入路径。
2.1、串口初始化
配置SCON让串口在模式1下工作。 由于模式1下只允许使用定时器1所以需要对定时器1初始化。 配置PCON需要计算波特率可以使用STC-ISP中的波特率计算器自动生成串口初始化代码再稍作修改。TL1和TH1决定了波特率结合上述串口模式图理解 // 串口初始化
void UART_Init(){ // 4800bps12.000MHzPCON | 0x80;SCON 0x40;// 配置定时器1只允许使用定时器1TMOD 0x0F; //设置定时器模式TMOD | 0x20; //设置定时器模式TL1 0xF3; //设置定时初值TH1 0xF3; //设置定时初值ET1 0; //禁止定时器1中断TR1 1; //启动定时器1
} 2.2、串口模块化
UART.c #include REGX52.H
/*** brief 串口初始化4800bps12.000MHz* param 无* retval 无*/
void UART_Init(){PCON | 0x80;SCON 0x50;// 配置定时器1只允许使用定时器1TMOD 0x0F; //设置定时器模式TMOD | 0x20; //设置定时器模式TL1 0xF3; //设置定时初值TH1 0xF3; //设置定时初值ET1 0; //禁止定时器1中断TR1 1; //启动定时器1
}
/*** brief 串口发送一个字节数据* param Byte 要发送的一个字节数据* retval 无*/
void UART_SendByte(unsigned char Byte){SBUFByte; // 串口缓冲寄存器while(TI0); // 检测是否完成。机械控制为1即TI为1时发送完成。TI0; // 软件复位
} UART.h #ifndef __UART_H__
#define __UART_H__
void UART_Init();
void UART_SendByte(unsigned char Byte);
#endif 将 UART.c和UART.h放入Functions中在工程中添加UART.c文件并设置其引入路径。
2.3、编写main.c文件 #include REGX52.H
#include Delay.h
#include UART.h
unsigned char Sec;
void main(){UART_Init();while(1){UART_SendByte(Sec);Sec;Delay(1000);}
} 编译下载到单片机。打开STC-ISP的串口助手设置波特率等打开串口。按复位键每隔一秒会收到加一的数字。
3、 电脑通过串口控制LED
内容计算机通过串口向单片机发送数据让单片机亮起对于的LED灯并且单片机向计算机发送接收到的数据。将上一个工程文件夹复制一份并修改名称。这次需要接收数据所以需要重新修改串口初始化函数。只需要在UART.c中修改SCON的REN位即SCON0x50。因为要使用到中断所以还要对中断进行配置。修改后的串口初始化函数 void UART_Init(){PCON | 0x80;SCON 0x50;// 配置定时器1只允许使用定时器1TMOD 0x0F; //设置定时器模式TMOD | 0x20; //设置定时器模式TL1 0xF3; //设置定时初值TH1 0xF3; //设置定时初值ET1 0; //禁止定时器1中断TR1 1; //启动定时器1// 配置中断EA1; // 启动所有中断ES1// 启动串口中断
} 在main.c文件中添加串口中断函数。 #include REGX52.H
#include Delay.h
#include UART.h
unsigned char Sec;
void main(){UART_Init();while(1){}
}
void UART_Rountine() interrupt 4{ // 当单片机串口接收到数据时该中断函数会执行if(RI1){ // 接收中断请求标志位将发送和接收区分开P2~SBUF;UART_SendByte(SBUF); // 将单片机收到的数据发送到电脑显示RI0; // 软件复位}
} 编译下载程序到单片机使用串口助手发送数据到单片机十六进制单片机亮起对于的LED灯并且向计算机发送接收到的数据。
十、LED点阵屏
LED点阵屏由若干个独立的LED组成LED以矩阵的形式排列以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合如汽车报站器、广告屏以及公告牌等。LED点阵屏分类 按颜色单色、双色、全彩。按像素8*8、16*16等 (大规模的LED点阵通常由很多个小点阵拼接而成)
1、显示原理
LED点阵屏的结构类似于数码管只不过是数码管把每一列的像素以“8”字型排列而已。LED点阵屏与数码管一样有共阴和共阳两种接法不同的接法对应的电路结构不同。LED点阵屏需要进行逐行或逐列扫描才能使所有LED同时显示。开发板对应引脚关系
2、LED点阵屏原理图
使用了74HC595扩展引脚使用3个输入控制8个输出。 3、74HC595
74HC595是串行输入并行输出的移位寄存器可用3根线输入串行数据8根线输出并行数据多片级联后可输出16位、24位、32位等常用于IO口扩展。结合上面原理图理解OE输出使能上面加根横线代表低电平有效所以需要将OE和GND短接74HC595才能工作。下图接的VCC要改 SRCLR串行清零端。接了VCC表示不清空。P35(RCLK)寄存器时钟。获得8位数据后将8位数据同时输出P36(SRCLK)串行时钟。将获得的数据下移一位P34(SER)串行数据。一次只输入一位数据再经过8位的输入就能刷新数据。 4、LED点阵屏显示图形
内容控制LED点阵屏显示一个笑脸。新建工程在工程目录下新建Functions、Objects、Listings文件夹将延时函数模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中将.lst文件存放到Listings中。新建main.c文件添加Delay.c到工程中并设置其引入路径。
4.1、C51的sfr和sbit
sfr特殊功能寄存器声明 例sfr P0 0x80; // 声明P0口寄存器物理地址为0x80sbit特殊位声明 例sbit P0_1 0x81; 或 sbit P0_1 P0^1; // 声明P0寄存器的第1位
4.2、编写main.c文件
74HC595控制行显示P0寄存器控制列显示。使用动态显示原理循环扫描要显示的每一列。需要消影 #include REGX52.H
#include Delay.h
sbit RCKP3^5; // RCLK寄存器时钟
sbit SCKP3^6; // SRCLK串行时钟
sbit SERP3^4; // SER串行数据
#define MATRIX_LED_PORT P0
void MatrixLED_Init(){ // 初始化74HC595SCK0;RCK0;
}
void _74HC595_WriteByte(unsigned char Byte){ // 控制行unsigned char i;for(i0;i8;i){ // 将8位数据放入移位寄存器SERByte(0x80i); // 非0赋1SCK1; // 移位SCK0;}RCK1; // 将8位数据输出RCK0;
}
void MatrixLED_ShowColumn(unsigned char Column,unsigned char Line){_74HC595_WriteByte(Line); // 第几行亮MATRIX_LED_PORT~(0x80Column); // 第几列亮0选中Delay(1); // 消影MATRIX_LED_PORT0xFF;
}
void main(){MatrixLED_Init();while(1){MatrixLED_ShowColumn(0,0x3C);MatrixLED_ShowColumn(1,0x42);MatrixLED_ShowColumn(2,0xA9);MatrixLED_ShowColumn(3,0x85);MatrixLED_ShowColumn(4,0x85);MatrixLED_ShowColumn(5,0xA9);MatrixLED_ShowColumn(6,0x42);MatrixLED_ShowColumn(7,0x3C);}
} 编译下载程序到单片机显示如下。
4.3、将LED点阵屏显示模块化
MatrixLED.c #include REGX52.H
#include Delay.h
sbit RCKP3^5; // RCLK寄存器时钟
sbit SCKP3^6; // SRCLK串行时钟
sbit SERP3^4; // SER串行数据
#define MATRIX_LED_PORT P0
/*** brief LED点阵屏初始化* param 无* retval 无*/
void MatrixLED_Init(){ // 初始化74HC595SCK0;RCK0;
}
/*** brief 74HC595写入一个字节* param 要写入的字节* retval 无*/
void _74HC595_WriteByte(unsigned char Byte){ // 控制行unsigned char i;for(i0;i8;i){ // 将8位数据放入移位寄存器SERByte(0x80i); // 非0赋1SCK1; // 移位SCK0;}RCK1; // 将8位数据输出RCK0;
}
/*** brief LED点阵屏显示一列数据* param Column 要选择的列范围0~70在最左边* param Line 选择列显示的数据高位在上1为亮0为灭* retval 无*/
void MatrixLED_ShowColumn(unsigned char Column,unsigned char Line){_74HC595_WriteByte(Line); // 第几行亮MATRIX_LED_PORT~(0x80Column); // 第几列亮0选中Delay(1); // 消影MATRIX_LED_PORT0xFF;
} MatrixLED.h #ifndef __MATRIX_LED_H__
#define __MATRIX_LED_H__
void MatrixLED_Init();
void MatrixLED_ShowColumn(unsigned char Column,unsigned char Line);
#endif
5、LED点阵屏显示动画
内容LED点阵屏流动显示字符串。复制上一个工程文件夹将LED点阵屏模块放入Functions中添加MatrixLED.c到工程中并设置其引入路径。使用字模提取工具获得动画位置网上有 点击新建建立一个高度为8长度自定义的画框。点击放大在画框中点出需要显示的动画。点击C51格式将生成的字符串新建为数组。编写main.c文件。 #include REGX52.H
#include Delay.h
#include MatrixLED.h
unsigned char code Animation[]{ // code将数组放入flash中0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 为了让动画流畅开头让动画流水出现0xFF,0x08,0x08,0x08,0xFF,0x00,0x0E,0x15,0x15,0x0C,0x00,0x7E,0x01,0x01,0x02,0x00,0x7E,0x01,0x01,0x02,0x00,0x0E,0x11,0x11,0x0E,0x00,0x7D,0x00,0x7D,0x00,0x7D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 显示完空的8列再显示新的才不会扰乱前面的显示
};
void main(){unsigned char i,offset,Count;MatrixLED_Init();while(1){for(i0;i8;i){MatrixLED_ShowColumn(i,Animation[ioffset]);}Count;if(Count10){ // 一帧扫描十遍不能用延时函数,会显得动画不流畅Count0;offset;if(offset40) offset0;}}
} 编译下载程序到单片机显示如下。
十一、DS1302实时时钟
DS1302是由低功耗实时时钟芯片它可以对年、月、日、周、时、分、秒进行计时且具有闰年补偿等多种功能。工作原理设置好初始时间DS1302就会自动计时只需把其中寄存器的值读出即可。
1、硬件电路
【注】VCC1(备用电池)的作用是保证时钟在断电后依然能够计时。但是本开发板中并未接VCC1所以没有断电后继续计时的功能。内部结构框图
2、DS1302原理图
SCLK串行时钟用来控制IO口每一位的输入/读取。IO每次输入/读取一位数据。CE芯片使能。
3、DS1302相关寄存器
【注】寄存器中的数据是以BCD码进行存储的。所以输出需要转换为十进制输入需要转换为BCD码 BCD码是用4位二进制数来表示1位十进制数。例0001 0011表示131000 0101表示850001 1010不合法(第二位超出了) 在十六进制中的体现0x13表示130x85表示850x1A不合法BCD码转十进制DECBCD/16*10BCD%162位BCD十进制转BCD码BCDDEC/10*16DEC%102位BCD命令字启动每一次数据传输控制输入/读取哪个寄存器的值上图前两列即为命令字 第7位固定为1。第6位0操作时钟1操作RAM。第1~5位要操作的地址。第0位0写1读。
4、DS1302时序图
读先输入要读取寄存器的位置命令字再读取数据。写先输入要写入寄存器的位置命令字再写入数据。【注】利用时序(上升/下降沿)控制位输入/读出。因为串行输入是一次输入一位所以应该先从低位开始输入/读出。 5、DS1302时钟LCD1602显示
内容使用DS1302控制时钟并在LCD1602显示屏上显示。新建工程在工程目录下新建Functions、Objects、Listings文件夹将LCD1602模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中将.lst文件存放到Listings中。新建main.c文件添加LCD1602.c到工程中并设置其引入路径。
5.1、DS1302模块化
DS1302.c #include REGX52.H
sbit DS1302_SCLKP3^6; // 串行时钟
sbit DS1302_IOP3^4; // 数据输入/输出
sbit DS1302_CEP3^5; // 芯片使能
// 写入地址
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8A
#define DS1302_YEAR 0x8C
#define DS1302_WP 0x8E
char DS1302_TIME[]{23,8,11,13,59,55,5};
/*** brief 初始化DS1302时钟* param 无* retval 无*/
void DS1302_Init(void){DS1302_CE0;DS1302_SCLK0;
}
/*** brief 向DS1302中写入一个字节数据* param Command 要写入的命令* param Data 要写入的数据* retval 无*/
void DS1302_WriteByte(unsigned char Command,Data){unsigned char i;DS1302_CE1;// 输入控制位for(i0;i8;i){DS1302_IOCommand(0x01i);DS1302_SCLK1; // 时序一个上升下降沿读取IO口的一位DS1302_SCLK0;}// 输入数据for(i0;i8;i){DS1302_IOData(0x01i);DS1302_SCLK1; // 时序DS1302_SCLK0;}DS1302_CE0;
}
/*** brief 从DS1302读出一个字节数据* param Command 要写入的命令* retval Data 读出的一个字节数据*/
unsigned char DS1302_ReadByte(unsigned char Command){unsigned char i,Data0x00;Command|0x01; // 将写的地址改为读的地址DS1302_CE1;// 输入控制位for(i0;i8;i){DS1302_IOCommand(0x01i);DS1302_SCLK0; // 时序DS1302_SCLK1;}// 读取数据for(i0;i8;i){DS1302_SCLK1;DS1302_SCLK0;if(DS1302_IO){ Data|(0x01i);} // 将对应位置1}DS1302_CE0;DS1302_IO0;return Data;
}
/*** brief 使用数组设置时间* param 无* retval 无*/
void DS1302_SetTime(void){DS1302_WriteByte(DS1302_WP,0x00); // 关闭芯片写保护DS1302_WriteByte(DS1302_YEAR,DS1302_TIME[0]/10*16DS1302_TIME[0]%10); // 十进制转BCDDS1302_WriteByte(DS1302_MONTH,DS1302_TIME[1]/10*16DS1302_TIME[1]%10);DS1302_WriteByte(DS1302_DATE,DS1302_TIME[2]/10*16DS1302_TIME[2]%10);DS1302_WriteByte(DS1302_HOUR,DS1302_TIME[3]/10*16DS1302_TIME[3]%10);DS1302_WriteByte(DS1302_MINUTE,DS1302_TIME[4]/10*16DS1302_TIME[4]%10);DS1302_WriteByte(DS1302_SECOND,DS1302_TIME[5]/10*16DS1302_TIME[5]%10);DS1302_WriteByte(DS1302_DAY,DS1302_TIME[6]/10*16DS1302_TIME[6]%10);DS1302_WriteByte(DS1302_WP,0x80); // 打开芯片写保护
}
/*** brief 读取时间到数组* param 无* retval 无*/
void DS1302_ReadTime(void){unsigned char Temp;TempDS1302_ReadByte(DS1302_YEAR);DS1302_TIME[0]Temp/16*10Temp%16; // BCD转十进制TempDS1302_ReadByte(DS1302_MONTH);DS1302_TIME[1]Temp/16*10Temp%16;TempDS1302_ReadByte(DS1302_DATE);DS1302_TIME[2]Temp/16*10Temp%16;TempDS1302_ReadByte(DS1302_HOUR);DS1302_TIME[3]Temp/16*10Temp%16;TempDS1302_ReadByte(DS1302_MINUTE);DS1302_TIME[4]Temp/16*10Temp%16;TempDS1302_ReadByte(DS1302_SECOND);DS1302_TIME[5]Temp/16*10Temp%16;TempDS1302_ReadByte(DS1302_DAY);DS1302_TIME[6]Temp/16*10Temp%16;
} DS1302.h #ifndef __DS1302_H__
#define __DS1302_H__
void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_ReadTime(void);
void DS1302_SetTime(void);
extern char DS1302_TIME[];
#endif
将编写好的DS1302模块放入Funcitons中并设置其引入路径。
5.2、编写main.c文件 #include REGX52.H
#include LCD1602.h
#include DS1302.h
void main(){LCD_Init();DS1302_Init();LCD_ShowString(1,1, / / ( ));LCD_ShowString(2,1, : :);DS1302_SetTime();while(1){DS1302_ReadTime();LCD_ShowNum(1,1,DS1302_TIME[0],2);LCD_ShowNum(1,4,DS1302_TIME[1],2);LCD_ShowNum(1,7,DS1302_TIME[2],2);LCD_ShowNum(1,10,DS1302_TIME[6],1); // 星期LCD_ShowNum(2,1,DS1302_TIME[3],2);LCD_ShowNum(2,4,DS1302_TIME[4],2);LCD_ShowNum(2,7,DS1302_TIME[5],2);}
} 编译下载程序到单片机显示如下。
6、DS1302调节时钟LCD1602显示
内容使用DS1302控制时钟在LCD1602显示屏上显示并且可以通过独立按键设置时间。按下按键1打开设置按下按键2切换设置对象按下按键3增大数值按下按键4减小数值再次按下按键1保存设置。复制上一个工程文件夹将延时函数、独立按键和定时器0模块放入Functions文件夹中。添加Delay.c、Key.c和Timer0.c到工程中并设置其引入路径。编写main.c文件 #include REGX52.H
#include LCD1602.h
#include DS1302.h
#include Key.h
#include Timer0.h
unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;
/*** brief 显示时间* param 无* retval 无*/
void TimeShow(void){DS1302_ReadTime();LCD_ShowNum(1,1,DS1302_TIME[0],2);LCD_ShowNum(1,4,DS1302_TIME[1],2);LCD_ShowNum(1,7,DS1302_TIME[2],2);LCD_ShowNum(1,10,DS1302_TIME[6],1); // 星期LCD_ShowNum(2,1,DS1302_TIME[3],2);LCD_ShowNum(2,4,DS1302_TIME[4],2);LCD_ShowNum(2,7,DS1302_TIME[5],2);
}
/*** brief 调节时间* param 无* retval 无*/
void TimeSet(void){if(KeyNum2){ // 按键按键2选择要更新的位置TimeSetSelect;TimeSetSelect%7; // 越界清零}else if(KeyNum3){ // 按键按键3数字加一DS1302_TIME[TimeSetSelect];// 上界判断if(DS1302_TIME[0]99) DS1302_TIME[0]0; // 年if(DS1302_TIME[1]12) DS1302_TIME[1]1; // 月// 日if(DS1302_TIME[1]1 || DS1302_TIME[1]3 || DS1302_TIME[1]5 || DS1302_TIME[1]7 ||DS1302_TIME[1]8 || DS1302_TIME[1]10 || DS1302_TIME[1]12){if(DS1302_TIME[2]31) DS1302_TIME[2]1;}else if(DS1302_TIME[1]4 || DS1302_TIME[1]6 || DS1302_TIME[1]9 || DS1302_TIME[1]11){if(DS1302_TIME[2]30) DS1302_TIME[2]1;}else if(DS1302_TIME[1]2){if(DS1302_TIME[0]%40 DS1302_TIME[0]%100!0){ // 闰年if(DS1302_TIME[2]29) DS1302_TIME[2]1;}else{if(DS1302_TIME[2]28) DS1302_TIME[2]1; // 平年}}if(DS1302_TIME[3]23) DS1302_TIME[3]0; // 时if(DS1302_TIME[4]59) DS1302_TIME[4]0; // 分if(DS1302_TIME[5]59) DS1302_TIME[5]0; // 秒if(DS1302_TIME[6]7) DS1302_TIME[6]1; // 星期}else if(KeyNum4){ // 按键按键4数字减一DS1302_TIME[TimeSetSelect]--;// 下界判断if(DS1302_TIME[0]0) DS1302_TIME[0]99; // 年if(DS1302_TIME[1]1) DS1302_TIME[1]12; // 月// 日if(DS1302_TIME[1]1 || DS1302_TIME[1]3 || DS1302_TIME[1]5 || DS1302_TIME[1]7 ||DS1302_TIME[1]8 || DS1302_TIME[1]10 || DS1302_TIME[1]12){if(DS1302_TIME[2]1) DS1302_TIME[2]31;if(DS1302_TIME[2]31) DS1302_TIME[2]1;}else if(DS1302_TIME[1]4 || DS1302_TIME[1]6 || DS1302_TIME[1]9 || DS1302_TIME[1]11){if(DS1302_TIME[2]1) DS1302_TIME[2]30;if(DS1302_TIME[2]30) DS1302_TIME[2]1;}else if(DS1302_TIME[1]2){if(DS1302_TIME[0]%40 DS1302_TIME[0]%100!0){ // 闰年if(DS1302_TIME[2]1) DS1302_TIME[2]29;if(DS1302_TIME[2]29) DS1302_TIME[2]1;}else{if(DS1302_TIME[2]1) DS1302_TIME[2]28; // 平年if(DS1302_TIME[2]28) DS1302_TIME[2]1;}}if(DS1302_TIME[3]0) DS1302_TIME[3]23; // 时if(DS1302_TIME[4]0) DS1302_TIME[4]59; // 分if(DS1302_TIME[5]0) DS1302_TIME[5]59; // 秒if(DS1302_TIME[6]1) DS1302_TIME[6]7; // 星期}// 更新显示if(TimeSetSelect0 TimeSetFlashFlag1) LCD_ShowString(1,1, );else LCD_ShowNum(1,1,DS1302_TIME[0],2);if(TimeSetSelect1 TimeSetFlashFlag1) LCD_ShowString(1,4, );else LCD_ShowNum(1,4,DS1302_TIME[1],2);if(TimeSetSelect2 TimeSetFlashFlag1) LCD_ShowString(1,7, );else LCD_ShowNum(1,7,DS1302_TIME[2],2);if(TimeSetSelect3 TimeSetFlashFlag1) LCD_ShowString(2,1, );else LCD_ShowNum(2,1,DS1302_TIME[3],2);if(TimeSetSelect4 TimeSetFlashFlag1) LCD_ShowString(2,4, );else LCD_ShowNum(2,4,DS1302_TIME[4],2);if(TimeSetSelect5 TimeSetFlashFlag1) LCD_ShowString(2,7, );else LCD_ShowNum(2,7,DS1302_TIME[5],2);if(TimeSetSelect6 TimeSetFlashFlag1) LCD_ShowString(1,10, );else LCD_ShowNum(1,10,DS1302_TIME[6],1);LCD_ShowNum(2,10,TimeSetSelect,1);
}
void main(){LCD_Init();DS1302_Init();Timer0Init();LCD_ShowString(1,1, / / ( ));LCD_ShowString(2,1, : :);DS1302_SetTime();while(1){KeyNumKey(); // 获取键码值if(KeyNum1){ // 按下按键1修改/保存时间 if(MODE0) {MODE1;TimeSetSelect0;}else if(MODE1) {MODE0;DS1302_SetTime();}}switch(MODE){case 0:TimeShow();break;case 1:TimeSet();break;}}
}
// 定时器0
void Timer0_Routine() interrupt 1{static unsigned int T0Count;TL0 0x18; //设置定时初值TH0 0xFC; //设置定时初值T0Count;if(T0Count500){ // 5msT0Count0;TimeSetFlashFlag!TimeSetFlashFlag;}
}
十二、蜂鸣器
蜂鸣器是一种将电信号转换为声音信号的器件常用来产生设备的按键音、报警音等提示信号。蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器。 有源蜂鸣器内部自带振荡源将正负极接上直流电压即可持续发声频率固定。无源蜂鸣器内部不带振荡源需要控制器提供振荡脉冲才可发声调整提供振荡脉冲的频率可发出不同频率的声音。本开发板使用的是无源蜂鸣器需要不断翻转电平蜂鸣器才会发声。例如 for(i0;i500;i){Buzzer!Buzzer; // 蜂鸣器控制引脚Delay(1); // 每隔一毫秒翻转一次周期为2毫秒频率为500HZ
} // 以500HZ的频率响500ms
1、蜂鸣器原理图
因为单片机的IO口不能直接驱动蜂鸣器所以通过ULN2003D辅助控制。
2、蜂鸣器播放提示音
内容按下独立按键数码管显示对应的按键键码蜂鸣器在松开按键时发声。新建工程在工程目录下新建Functions、Objects、Listings文件夹将延时函数、独立按键和数码管模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中将.lst文件存放到Listings中。新建main.c文件添加Delay.c、Key.c和Nixie.c到工程中并设置其引入路径。编写蜂鸣器模块。 Buzzer.c #include REGX52.H
#include INTRINS.H
// 蜂鸣器端口
sbit BuzzerP2^5;
/*** brief 蜂鸣器私有延时函数延时500微秒* param 无* retval 无*/
void Buzzer_Delay500us() //12.000MHz
{unsigned char i;_nop_(); // 延时一个机器周期i 247;while (--i);
}
/*** brief 让蜂鸣器以1000HZ的频率发声* param ms 发声时长* retval 无*/
void Buzzer_Time(unsigned int ms){unsigned int i;for(i0;ims*2;i){Buzzer!Buzzer;Buzzer_Delay500us(); // 每隔500微秒翻转一次周期为1毫秒频率为1000HZ}
} Buzzer.h #ifndef __BUZZER_H__
#define __BUZZER_H__
void Buzzer_Time(unsigned int ms);
#endif 将蜂鸣器模块放入Functions文件夹中并设置其引入路径。编写main.c文件。 #include REGX52.H
#include Delay.h
#include Key.h
#include Nixie.h
#include Buzzer.h
unsigned char KeyNum;
void main(){Nixie_Static(1,0);while(1){KeyNumKey();if(KeyNum){Buzzer_Time(100); // 蜂鸣器发声100msNixie_Static(1,KeyNum); // 显示对应按键的键码}}
} 按下独立按键松开时蜂鸣器以1000HZ频率发声100毫秒。
3、蜂鸣器播放音乐
内容蜂鸣器播放小星星。新建工程在工程目录下新建Functions、Objects、Listings文件夹将延时函数、定时器0模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中将.lst文件存放到Listings中。新建main.c文件添加Delay.c和Timer0.c到工程中并设置其引入路径。乐谱
编写main.c文件夹 #include REGX52.H
#include Delay.h
#include Timer0.h
sbit BuzzerP2^5;
#define SPEED 500 //十六分音符时长
unsigned int FreqTable[]{ // 低中高音0, // 休止符63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283
};
unsigned char Music[]{13,4,13,4,20,4,20,4,22,4,22,4,20,8,0,2,18,4,18,4,17,4,17,4,15,4,15,4,13,8,0xFF, // 终止标志
};
unsigned char FreqSelect0,MusicSelect0;
void main(){Timer0Init();while(1){if(Music[MusicSelect]!0xFF){FreqSelectMusic[MusicSelect];MusicSelect;Delay(SPEED/4*Music[MusicSelect]);MusicSelect;// 不让音符连在一起TR00;Delay(5);TR01;}else{TR00;while(1);}}
}
void Timer0_Routine() interrupt 1{ // 1ms执行一次500HZif(FreqTable[FreqSelect]){TL0 FreqTable[FreqSelect]%256; //设置定时初值TH0 FreqTable[FreqSelect]/256; //设置定时初值Buzzer!Buzzer;}
}