文章目录
- led灯
- led点亮
- led闪烁
- 流水灯
- 独立按键
- 按键点灯
- 按键消抖
- 按键实现二进制流水灯
- 按键实现流水灯
- 数码管
- 静态数码管显示
- 动态数码管显示
- 矩阵键盘
- 定时器/中断
- 串口通信
- led点阵屏
- DS1302实时时钟
- 蜂鸣器
- AT24C02
- DS18B20
- LCD1602
- 直流电机驱动
- AD/DA
- 红外遥控
led灯
创建项目:选择Legacy Device …和AT89C52
生成hex文件
led点亮
原理图:
给一个低电平即可点亮对应的led灯
#include <STC89C5xRC.H>void main()
{P2 = 0xFE;//1111 1110 点亮D1P2 = 0xFC;//1111 1100 点亮D1和D2
}
或者
#include <REGX52.H>sbit D1=P2^0;
sbit D2=P2^1;
sbit D3=P2^2;
sbit D4=P2^3;
sbit D5=P2^4;
sbit D6=P2^5;
sbit D7=P2^6;
sbit D8=P2^7;
int main(){
D1=0;while(1){}
}
sfr与sbit的含义
sfr P0=0x80;//把地址 0x80 处的寄存器定义为 P0
sbit P0_1=P0^1;//取第一位定义为 P0_1
led闪烁
delay函数使用工具自带的软件延时计算机生成
#include <REGX52.H>
#include <INTRINS.H>
void Delay500ms() //@12.000MHz
{unsigned char i, j, k;_nop_();i = 4;j = 205;k = 187;do{do{while (--k);} while (--j);} while (--i);
}void main(){
while(1){
P2=0xFE;//D1亮
Delay500ms();
P2=0xFF;//D1灭
Delay500ms();
}}
优化delay函数
#include <INTRINS.H>
void Delay1ms(unsigned int xms) //@12.000MHz
{while(xms--){//延时1msunsigned char i, j;i = 2;j = 239;do{while (--j);} while (--i);}
}
流水灯
void Delay500ms() //@12.000MHz
{unsigned char i, j, k;_nop_();i = 4;j = 205;k = 187;do{do{while (--k);} while (--j);} while (--i);
}int main(){
unsigned char D=D1;
while(1){unsigned char i;for(i=0;i<8;i++){P2=~(0x01<<i);//通过位运算符Delay500ms();}
}}
独立按键
原理图
按键点灯
#include <REGX52.H>void main(){while(1){
if(P3_1==0){//P3寄存器的bit1等于0,说明P31按键通了
P2_0=0;//d0亮
}
else{
P2_0=1;
}
}
}
按键消抖
#include <REGX52.H>void Delay(unsigned int xms) //@12.000MHz
{unsigned char i, j;while(xms--){i = 12;j = 169;do{while (--j);} while (--i);
}
}void main(){while(1){
if(P3_1==0){//按键按下Delay(20);while(P3_1==0);//等待按键松开Delay(20);P2_0=~P2_0;
}
}
}
按键实现二进制流水灯
#include <REGX52.H>void Delay(unsigned int xms) //@12.000MHz
{unsigned char i, j;while(xms--){i = 12;j = 169;do{while (--j);} while (--i);
}
}
void main(){
unsigned char ledNum=0;//0~255
while(1){
if(P3_1==0){//按键按下Delay(20);while(P3_1==0);//等待按键松开Delay(20);ledNum++;//0000 0001 =>0000 0010 =>0000 0011P2=~ledNum;//1111 1110 => 1111 1101 => 1111 1100
}
}
}
0000 0001 = 0000 0001 << 0
0000 0010 = 0000 0001 << 1
0000 0100 = 0000 0001 << 2
0000 1000 = 0000 0001 << 3
按键实现流水灯
#include <REGX52.H>void Delay(unsigned int xms) //@12.000MHz
{unsigned char i, j;while(xms--){i = 12;j = 169;do{while (--j);} while (--i);
}
}//使用移位进行优化
void main(){
unsigned char ledNum=0;
P2=0xFE;
while(1){
if(P3_1==0){//按键按下Delay(20);while(P3_1==0);//等待按键松开Delay(20);ledNum++;if(ledNum>=8){ledNum=0;}P2=~(0x01<<ledNum);}}}
数码管
原理图:
P00~P07 代表控制当前数码管的 a~g 显示形式,
图形6:abcdefg dp=1011 1110 对应的P0=0111 1101=0x7D(顺序是P07,P06…)
图形0:dp g f e d c b a=0011 1111 =0x3F 所以P0=0x3F;
74译码器使用3位 bit 输入表示8种状态,调整 LED1~8 哪一个输出低电平,代表要启动8个数码管的哪一个的公共端
比如P2^4=1, P2^3=1, P2^2=0, 输入就是110,取反后就是001,就是LED1
静态数码管显示
#include <REGX52.H>void main(){//使第一个灯亮,led8=111P2_4=1;P2_3=1;P2_2=1;//显示图形为6 abcdefg=0100 0001 P0=0x7D;while(1);}
使用函数和数组进行优化
#include <REGX52.H>//数码管显示的数字 0-9
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};void NiXie(unsigned char Location,unsigned char Number){switch (Location){case 1:P2_4=1;P2_3=1;P2_2=1;break; case 2:P2_4=1;P2_3=1;P2_2=0;break;case 3:P2_4=1;P2_3=0;P2_2=1;break;case 4:P2_4=1;P2_3=0;P2_2=0;break;case 5:P2_4=0;P2_3=1;P2_2=1;break;case 6:P2_4=0;P2_3=1;P2_2=0;break;case 7:P2_4=0;P2_3=0;P2_2=1;break;case 8:P2_4=0;P2_3=0;P2_2=0;break;}P0=NixieTable[Number];
}void main(){//第8个led灯,显示图形为6
NiXie(8,6);
while(1);}
动态数码管显示
#include <REGX52.H>void Delay(unsigned int xms) //@12.000MHz
{unsigned char i, j;while(xms--){i = 12;j = 169;do{while (--j);} while (--i);
}
}unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void NiXie(unsigned char Location,unsigned char Number){switch (Location){case 1:P2_4=1;P2_3=1;P2_2=1;break; case 2:P2_4=1;P2_3=1;P2_2=0;break;case 3:P2_4=1;P2_3=0;P2_2=1;break;case 4:P2_4=1;P2_3=0;P2_2=0;break;case 5:P2_4=0;P2_3=1;P2_2=1;break;case 6:P2_4=0;P2_3=1;P2_2=0;break;case 7:P2_4=0;P2_3=0;P2_2=1;break;case 8:P2_4=0;P2_3=0;P2_2=0;break;}P0=NixieTable[Number];//消影Delay(1);P0=0x00;//什么都不显示
}void main(){while(1){NiXie(1,1);//第一个数码管显示1
NiXie(2,2);//第二个数码管显示2
NiXie(3,3);//第三个数码管显示3}}
矩阵键盘
原理图:
为了减少 IO 口的占用,用4个 IO 口代表行,4个 IO 口代表列。
类似动态数码管快速扫描实现几乎同时点亮的效果,矩阵键盘也是快速扫描。
MatrixKey.h
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__unsigned char MatrixKey();#endif
MatrixKey.c
这里使用按列扫描按键
#include <REGX52.H>
#include "Delay.h"
#include "MatrixKey.h"unsigned char MatrixKey(){
unsigned char KeyNum=0;
//按列扫描按键P1=0xFF;P1_3=0;//第一列if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNum=1;}if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNum=5;}if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNum=9;}if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNum=13;}P1=0xFF; P1_2=0;//第二列if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNum=2;}if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNum=6;}if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNum=10;}if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNum=14;}P1=0xFF; P1_1=0;//第三列if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNum=3;}if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNum=7;}if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNum=11;}if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNum=15;}P1=0xFF; P1_0=0;//第三列if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNum=4;}if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNum=8;}if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNum=12;}if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNum=16;}return KeyNum;}
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
void main(){
LCD_Init();LCD_ShowString(1,1,"MatrixKey");
while(1){
KeyNum=MatrixKey();
if(KeyNum){//添加判断,否则会一直显示0
LCD_ShowNum(2,1,KeyNum,2);
}
}
}
矩阵键盘密码锁
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;//单位按键值
unsigned int Password;//4位密码
unsigned char count;//4位数字
void main(){
LCD_Init();LCD_ShowString(1,1,"Password");
while(1){
KeyNum=MatrixKey();
if(KeyNum){//添加判断,否则会一直显示0
if(KeyNum<=10){if(count<4){Password*=10;
Password+=KeyNum%10;//将10变成0count++;}}
else if(KeyNum==11){//s11确认按钮=>验证密码
if(Password==1234){
LCD_ShowString(1,10,"SUCCESS");}
else{LCD_ShowString(1,12,"ERROR");Password=0;count=0;
}
}
else if(KeyNum==12){Password=0;
}
LCD_ShowNum(2,1,Password,4);
}
}}
定时器/中断
定时器的作用:
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间就完成一项操作
(2)替代长时间的Delay,提供CPU的运行效率和处理速度
STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器
GATE 用于开启定时器。当 GATE 打开, TR=1(timer reset)且INT1/INT0 为高时,定时器开始工作。
C/T就是打开定时器的计时还是时钟功能。
M0M1 用于选定时钟的4个模式。比如16 位就是01.
控制P2_0的灯,每隔1s闪烁
#include <REGX52.H>void Timer0_Init(){
//TMOD=0x01;TMOD=TMOD&0xF0;//1111 0000 把TMOD的低四位清零,高四位保持不变
TMOD=TMOD|0x01;//把TMOD的其他位清零,最后一位保持不变
TF0=0;
TR0=1;
TH0=64535/256;
TL0=64535%256;
ET0=1;
EA=1;
PT0=0;
}unsigned int T0Count;
void Timer0_Routine() interrupt 1
{
TH0=64535/256;//赋初值
TL0=64535%256;//赋初值
T0Count++;if(T0Count>=1000){//每隔1s执行一次T0Count=0;P2_0=~P2_0;
}}void main(){
Timer0_Init();
while(1);
return;
}
使用软件生成的Timer0Init():
STC-ISP 上也有生成定时器函数。不过 AUXR 设置定时器时钟那一步是针对最新版本可以调整单片机定时器使能而添加的,我们的单片机加上会报错,需要删掉。
另外需要手动添加 ET EA PT
void Timer0Init(void) //1毫秒@11.0592MHz
{TMOD &= 0xF0; //设置定时器模式TL0 = 0xCD; //设置定时初值TH0 = 0xD4; //设置定时初值TF0 = 0; //清除TF0标志TR0 = 1; //定时器0开始计时//需要自己手动配置ET0=1;//启动定时器中断 Enable Timer 1
EA=1;//开启全局中断
PT0=0;//设置工作模式
}
unsigned int T0Count;
void Timer0_Routine() interrupt 1
{TL0 = 0xCD; //设置定时初值TH0 = 0xD4; //设置定时初值T0Count++;if(T0Count>=1000){//1ms *1000=1sT0Count=0;P2_0=~P2_0;}}void main()
{Timer0Init();while(1){}
}
中断函数:
在函数形参括号后加修饰符 interrupt m,系统编译时把对应函数转化为中断函数,自动加上程序头段和尾段,并按 51系统中断的处理方式自动把它安排在程序存储器中的相应位置。
在该修饰符中,m 的取值为 0~31,对应的中断情况如下:
0——外部中断 0
1——定时/计数器 T0
2——外部中断 1
3——定时/计数器 T1
4——串行口中断
5——定时/计数器 T2
其它值预留。
案例:按键K1控制流水灯方向
key.h
#ifndef __KEY_H__
#define __KEY_H__unsigned char Key();#endif
key.c
#include <REGX52.H>
#include "Delay.h"/*** @brief 获取独立按键键码* @param 无* @retval 按下按键的键码,范围:0~4,无按键按下时返回值为0*/
unsigned char Key()
{unsigned char KeyNumber=0;if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}return KeyNumber;
}
Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__void Timer0Init(void);#endif
Timer0.c
#include <REGX52.H>/*** @brief 定时器0初始化,1毫秒@12.000MHz* @param 无* @retval 无*/
void Timer0Init(void)
{TMOD &= 0xF0; //设置定时器模式TMOD |= 0x01; //设置定时器模式TL0 = 0x18; //设置定时初值TH0 = 0xFC; //设置定时初值TF0 = 0; //清除TF0标志TR0 = 1; //定时器0开始计时ET0=1;EA=1;PT0=0;
}/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{static unsigned int T0Count;TL0 = 0x18; //设置定时初值TH0 = 0xFC; //设置定时初值T0Count++;if(T0Count>=1000){T0Count=0;}
}
*/
main.c
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>//_crol_unsigned char KeyNum,LEDMode;void main()
{P2=0xFE;Timer0Init();while(1){KeyNum=Key(); //获取独立按键键码if(KeyNum) //如果按键按下{if(KeyNum==1) //如果K1按键按下{LEDMode++; //模式切换if(LEDMode>=2)LEDMode=0;}}}
}void Timer0_Routine() interrupt 1
{static unsigned int T0Count;TL0 = 0x18; //设置定时初值TH0 = 0xFC; //设置定时初值T0Count++; //T0Count计次,对中断频率进行分频if(T0Count>=500)//分频500次,500ms{T0Count=0;if(LEDMode==0) //模式判断P2=_crol_(P2,1); //LED输出 左移1位if(LEDMode==1)P2=_cror_(P2,1); //右移1位}
}
案例:定时器时钟
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"unsigned char Sec=55,Min=59,Hour=23;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(T0Count>=1000) //定时器分频,1s{T0Count=0;Sec++; //1秒到,Sec自增if(Sec>=60){Sec=0; //60秒到,Sec清0,Min自增Min++;if(Min>=60){Min=0; //60分钟到,Min清0,Hour自增Hour++;if(Hour>=24){Hour=0; //24小时到,Hour清0}}}}
}
串口通信
STC89C52系列单片机的串行口设有两个控制寄存器:串行控制寄存器SCON和波特率选择特殊功能寄存器PCON。
配置SCON
SBUF:物理上是接收和发送两个寄存器,实际上共用相同的地址,只是使用时有不同的意义。我们只需要把数据放入其中就行,发送原理暂不用弄明白。
SCON:串口控制寄存器。控制电路。包含:
SM0,SM1:设置工作方式。比如我们采用8位 UART,就赋值01.
SM2:与工作方式1无关。
REN:是否允许串行接收状态。1允许接收。
TB8 RB8:接收到的第9位数据,与工作方式1无关。
TI RI:发送接收中断请求标志位。代表发送完了。硬件赋1,需要用软件复位。
赋值的话只有 SM0 SM1=01,和 REN 需要注意,其他的初始值都=0。
PCON:电源管理。包含:
SMOD:可见支路图,用于设置波特率是否加倍。
SMOD1:纠错或协助 SM0 设置工作方式。
IE:打开中断。
可以直接使用波特率计算器生成初始化代码
串口初始化代码:
void UART_Init()
{SCON=0x40;PCON |= 0x80;TMOD &= 0x0F; //设置定时器模式TMOD |= 0x20; //设置定时器模式TL1 = 0xF3; //设定定时初值TH1 = 0xF3; //设定定时器重装值ET1 = 0; //禁止定时器1中断TR1 = 1; //启动定时器1
}
串口向电脑发送数据
#include <REGX52.H>/*** @brief 串口初始化,4800bps@12.000MHz* @param 无* @retval 无*/
void UART_Init()
{SCON=0x40;PCON |= 0x80;TMOD &= 0x0F; //设置定时器模式TMOD |= 0x20; //设置定时器模式TL1 = 0xF3; //设定定时初值TH1 = 0xF3; //设定定时器重装值ET1 = 0; //禁止定时器1中断TR1 = 1; //启动定时器1
}/*** @brief 串口发送一个字节数据* @param Byte 要发送的一个字节数据* @retval 无*/
void UART_SendByte(unsigned char Byte)
{SBUF=Byte;while(TI==0);//TI=1代表发送完了TI=0;
}int main(){
UART_Init();
UART_SendByte(0x11);//发送11
while(1){}
}
电脑接收信息:
修改SCON,添加EA和ES
void UART_Init()
{SCON=0x50;//允许接收数据PCON |= 0x80;TMOD &= 0x0F; //设置定时器模式TMOD |= 0x20; //设置定时器模式TL1 = 0xF3; //设定定时初值TH1 = 0xF3; //设定定时器重装值ET1 = 0; //禁止定时器1中断TR1 = 1; //启动定时器1EA=1;//启动所有中断ES=1;//启动串口中断
}
int main(){
UART_Init();
while(1){}
}void UART_Routine() interrupt 4 // 4表示通讯串口中断
{
if(RI==1){//接收数据P2=~SBUF;//通过串口发送的数据控制led灯UART_SendByte(SBUF);RI=0;//接收标志位清零}
}
led点阵屏
由图可知,OE 低电平有效,因此 LED 点阵旁的跳线帽一定要接到 OE-GND 一端。
SRCLK:移位寄存器,数据先传输到移位寄存器中。移位寄存器上升沿时移位,再接收下一次数据。
RCLK:存储寄存器。存储寄存器上升沿时把寄存器中所有数据都通过端口输出。
相当于手枪,每次 SRCLK 上升时我们填入一枚子弹,RCLK 上升时把弹夹塞入
传入数据如列P是0100 0000,行D是0000 0001,则代表最后一行第二列的点会被点亮。
#include <REGX52.H>
#include "Delay.h"sbit RCK=P3^5;//P3的第五位 RCLK
sbit SCK=P3^6;//SRCLK
sbit SER=P3^4;//SERvoid _74HC595_WriteByte(unsigned char Byte){
unsigned char i;for(i=0;i<8;i++){SER=Byte&(0x80>>i);SCK=1;SCK=0;}RCK=1;RCK=0;
}void MatrixLED_ShowColumn(unsigned char Column,Data){
_74HC595_WriteByte(Data);//给行通电P0=~(0x80>>Column);//从左到一右,第0列 P0=1000 0000 第1列P0=0100 0000... 低电平确定那一列可以被点亮Delay(1);P0=0xFF;//清除
}int main(){
//初始化 给低电平
SCK=0;RCK=0;
while(1){
//点亮爱心
MatrixLED_ShowColumn(0,0x30);
MatrixLED_ShowColumn(1,0x78);
MatrixLED_ShowColumn(2,0x7C);
MatrixLED_ShowColumn(3,0x3E);
MatrixLED_ShowColumn(4,0x3E);
MatrixLED_ShowColumn(5,0x7C);
MatrixLED_ShowColumn(6,0x78);
MatrixLED_ShowColumn(7,0x30);}
}
改进并添加偏移动画和逐帧动画
#include <REGX52.H>
#include "Delay.h"sbit RCK=P3^5;//P3的第五位 RCLK
sbit SCK=P3^6;//SRCLK
sbit SER=P3^4;//SER//添加code,将数据放在flash里面 把每一列要显示的数据放到数组中
unsigned char code Animation[]={
0x00,0x04,0x6B,0x96,0x97,0x6A,0x0c,0x00,
0x00,0x04,0x6B,0x96,0x97,0x6C,0x18,0x00,
0x00,0x6E,0x97,0x97,0x6E,0x00,0x00,0x00,};
void _74HC595_WriteByte(unsigned char Byte){
unsigned char i;for(i=0;i<8;i++){SER=Byte&(0x80>>i);SCK=1;SCK=0;}RCK=1;RCK=0;
}void MatrixLED_ShowColumn(unsigned char Column,Data){
_74HC595_WriteByte(Data);P0=~(0x80>>Column);//从左到一右,第0列 P0=1000 0000 第1列P0=0100 0000...Delay(1);P0=0xFF;//清除
}int main(){unsigned char i,offset=0,count=0;
//初始化 给低电平SCK=0;RCK=0;while(1){//一次显示8列for(i=0;i<8;i++){MatrixLED_ShowColumn(i,Animation[i+offset]);}count++;//count用于计时if(count>15){count=0;//offset+=1; //偏移动画offset+=8;//逐帧动画if(offset>16){offset=0;}}}
}
DS1302实时时钟
时序定义:
操作流程就是将数据写入 DS1302 的寄存器来设置当前时间格式,然后 DS1302 时钟运作后我们再将寄存器中数据读出。
寄存器地址:读地址=写入地址 | 0x01
DS1302使用的是BCB码,因此需要转换成10进制
时钟案例:
引入LCD1602.h和LCD1602.c
DS1302.h
#ifndef __DS1302_H__
#define __DS1302_H__
extern unsigned char DS1302_Time[];//外部可以调用void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime(void);#endif
DS1302.c
#include <REGX52.H>//引脚定义
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^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 //写保护//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={19,11,16,12,59,55,6};/*** @brief DS1302初始化* @param 无* @retval 无*/
void DS1302_Init(void)
{DS1302_CE=0;DS1302_SCLK=0;
}/*** @brief DS1302写一个字节* @param Command 命令字/地址* @param Data 要写入的数据* @retval 无*/
void DS1302_WriteByte(unsigned char Command,Data)
{unsigned char i;DS1302_CE=1;for(i=0;i<8;i++){DS1302_IO=Command&(0x01<<i);DS1302_SCLK=1;DS1302_SCLK=0;}for(i=0;i<8;i++){DS1302_IO=Data&(0x01<<i);DS1302_SCLK=1;DS1302_SCLK=0;}DS1302_CE=0;
}/*** @brief DS1302读一个字节* @param Command 命令字/地址* @retval 读出的数据*/
unsigned char DS1302_ReadByte(unsigned char Command)
{unsigned char i,Data=0x00;//Data一定要初始化Command|=0x01; //将写指令转换为读指令 eg:会将Command=0x80变为0x81DS1302_CE=1;for(i=0;i<8;i++){//这里为什么和 write 是相反的?因为我们注意到 read 是先上升沿读入8位,再切换为下降沿读入8位。、//如果还是先1后0,读入第8位时不仅会把上升沿读掉,下降沿也会读掉,导致错过第九位。//需要查询最小执行时间。不过这里执行时间都大于最小时间了。DS1302_IO=Command&(0x01<<i);DS1302_SCLK=0;DS1302_SCLK=1;}for(i=0;i<8;i++){DS1302_SCLK=1;DS1302_SCLK=0;if(DS1302_IO){Data|=(0x01<<i);}}DS1302_CE=0;DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错return Data;
}void DS1302_SetTime(void){DS1302_WriteByte(DS1302_WP,0x00);//关闭写保护
DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);//10进制转DCB码
DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
DS1302_WriteByte(DS1302_WP,0x80);
}void DS1302_ReadTime(void)
{unsigned char Temp;Temp=DS1302_ReadByte(DS1302_YEAR);DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取Temp=DS1302_ReadByte(DS1302_MONTH);DS1302_Time[1]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_DATE);DS1302_Time[2]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_HOUR);DS1302_Time[3]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_MINUTE);DS1302_Time[4]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_SECOND);DS1302_Time[5]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_DAY);DS1302_Time[6]=Temp/16*10+Temp%16;
}
main.c
#include <REGX52.H>
#include "DS1302.h"
#include "LCD1602.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(2,1,DS1302_Time[3],2);//显示时LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒}
}
蜂鸣器
原理图:
三极管的作用是:不用单片机自己直接驱动单片机
响起来很简单:不断反转 P1^5 口(新版普中为P2^5)
#include <REGX52.H>
#include "Key.h"
#include "Delay.h"
unsigned char KeyNum;
unsigned char i;sbit Buzzer=P2^5;
int main(){
Nixie(1,0);
while(1){
KeyNum=Key();//按键触发蜂鸣器if(KeyNum){for(i=0;i<100;i++){Buzzer=~Buzzer;Delay(1);}//100ms}
}
}
使用方法:TH=重装载值/256,TL=重装载值%256.
蜂鸣器演奏天空之城
添加定时器Timer0.h
main.c
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"//蜂鸣器端口定义
sbit Buzzer=P2^5;//播放速度,值为四分音符的时长(ms)
#define SPEED 500//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P 0
#define L1 1
#define L1_ 2
#define L2 3
#define L2_ 4
#define L3 5
#define L4 6
#define L4_ 7
#define L5 8
#define L5_ 9
#define L6 10
#define L6_ 11
#define L7 12
#define M1 13
#define M1_ 14
#define M2 15
#define M2_ 16
#define M3 17
#define M4 18
#define M4_ 19
#define M5 20
#define M5_ 21
#define M6 22
#define M6_ 23
#define M7 24
#define H1 25
#define H1_ 26
#define H2 27
#define H2_ 28
#define H3 29
#define H4 30
#define H4_ 31
#define H5 32
#define H5_ 33
#define H6 34
#define H6_ 35
#define H7 36//索引与频率对照表
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 code Music[]={//音符,时值,//1P, 4,P, 4,P, 4,M6, 2,M7, 2,H1, 4+2,M7, 2,H1, 4,H3, 4,M7, 4+4+4,M3, 2,M3, 2,//2M6, 4+2,M5, 2,M6, 4,H1, 4,M5, 4+4+4,M3, 4,M4, 4+2,M3, 2,M4, 4,H1, 4,//3M3, 4+4,P, 2,H1, 2,H1, 2,H1, 2,M7, 4+2,M4_,2,M4_,4,M7, 4,M7, 8,P, 4,M6, 2,M7, 2,//4H1, 4+2,M7, 2,H1, 4,H3, 4,M7, 4+4+4,M3, 2,M3, 2,M6, 4+2,M5, 2,M6, 4,H1, 4,//5M5, 4+4+4,M2, 2,M3, 2,M4, 4,H1, 2,M7, 2+2,H1, 2+4,H2, 2,H2, 2,H3, 2,H1, 2+4+4,//6H1, 2,M7, 2,M6, 2,M6, 2,M7, 4,M5_,4,M6, 4+4+4,H1, 2,H2, 2,H3, 4+2,H2, 2,H3, 4,H5, 4,//7H2, 4+4+4,M5, 2,M5, 2,H1, 4+2,M7, 2,H1, 4,H3, 4,H3, 4+4+4+4,//8M6, 2,M7, 2,H1, 4,M7, 4,H2, 2,H2, 2,H1, 4+2,M5, 2+4+4,H4, 4,H3, 4,H3, 4,H1, 4,//9H3, 4+4+4,H3, 4,H6, 4+4,H5, 4,H5, 4,H3, 2,H2, 2,H1, 4+4,P, 2,H1, 2,//10H2, 4,H1, 2,H2, 2,H2, 4,H5, 4,H3, 4+4+4,H3, 4,H6, 4+4,H5, 4+4,//11H3, 2,H2, 2,H1, 4+4,P, 2,H1, 2,H2, 4,H1, 2,H2, 2+4,M7, 4,M6, 4+4+4,P, 4,0xFF //终止标志
};unsigned char FreqSelect,MusicSelect;void main()
{Timer0Init();while(1){if(Music[MusicSelect]!=0xFF) //如果不是停止标志位{FreqSelect=Music[MusicSelect]; //选择音符对应的频率MusicSelect++;Delay(SPEED/4*Music[MusicSelect]); //选择音符对应的时值MusicSelect++;TR0=0;Delay(5); //音符间短暂停顿TR0=1;}else //如果是停止标志位{TR0=0;while(1);}}
}void Timer0_Routine() interrupt 1
{if(FreqTable[FreqSelect]) //如果不是休止符{/*取对应频率值的重装载值到定时器*/TL0 = FreqTable[FreqSelect]%256; //设置定时初值TH0 = FreqTable[FreqSelect]/256; //设置定时初值Buzzer=!Buzzer; //翻转蜂鸣器IO口}
}
AT24C02
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
原理图:
开发板原理图:
时序结构:
起始与终止条件
注意:起始条件是先将SDA置0,再将SCL置0
终止条件是现将SCL置1,再将SDA置1
发送一个字节与接受一个字节数据
发送应答与接受应答:
发送一帧数据的全过程:
I2C.h
#ifndef __I2C_H__
#define __I2C_H__void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);#endif
I2C.c
#include "I2C.h"
#include <REGX52.H>sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;/*
开始*/
void I2C_Start(void){I2C_SCL=1;
I2C_SDA=1;
I2C_SDA=0;
I2C_SCL=0;}/*
结束
*/
void I2C_Stop(void){
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;}
//发送8位数据
void I2C_SendByte(unsigned char Byte){
unsigned char i;for(i=0;i<8;i++){I2C_SDA=Byte&(0x80>>i);I2C_SCL=1;I2C_SCL=0;}
}//接受8位数据
unsigned char I2C_ReceiveByte(void){
unsigned char i,Byte=0x00;
I2C_SDA=1;//释放主机对SDA的控制
for(i=0;i<8;i++){
I2C_SCL=1;
if(I2C_SDA){ Byte|=0x80>>i;}
I2C_SCL=0;
}return Byte;}//发送确认信息
void I2C_SendAck(unsigned char AckBit){
I2C_SDA=AckBit;I2C_SCL=1;I2C_SCL=0;}
//接受确认信息
unsigned char I2C_ReceiveAck(void){
unsigned char AckBit;
I2C_SDA=1;//释放SDA
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;}
AT24C02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__void AT24C02_Write(unsigned char WordAddress,Data);
unsigned char AT24C02_Read(unsigned char WordAddress);
#endif
AT24C02.c
#include "AT24C02.h"
#include "I2C.h"#define AT24C02_ADDRESS 0xA0 //AT24C02的Address/*
向AT24C02中写数据 字节写
*/
void AT24C02_Write(unsigned char WordAddress,Data){
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);//Slave Address +W0
I2C_ReceiveAck();
I2C_SendByte(WordAddress);//WORD ADDRESS
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}/*
向AT24C02中读数据 随机读
*/
unsigned char AT24C02_Read(unsigned char WordAddress){
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);//Slave Address+W0
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);//Slave Address+R1
I2C_ReceiveAck();
Data=I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Data;}
main.c(向EEPROM中保存16位的数据)
按k1对number进行+1,k2对number进行-1,k3对number写入EEPROM,k4从EEPROM读取number
#include <REGX52.H>
#include "AT24C02.h"
#include "Delay.h"
#include "LCD1602.h"
#include "Key.h"
unsigned int Num=0;//unsigned int的表示范围才是0~65535。共16位
unsigned char KeyNum=0;
int main(){
unsigned char Data;
LCD_Init(); while(1){KeyNum=Key();
if(KeyNum==1){
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2){
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3){
AT24C02_Write(0,Num%256);//EEPROM内部结构,每个存储单元可以存储一个字节(8位) 写入低八位
Delay(5);AT24C02_Write(1,Num/256); //写入高八位
Delay(5);
LCD_ShowString(2,1,"Write OK");
Delay(1000);
LCD_ShowString(2,1," ");}
if(KeyNum==4){
Num=AT24C02_Read(0);//读取低8位
Num|=AT24C02_Read(1)<<8;//读取高8位
LCD_ShowNum(1,1,Num,5);
}}
}
定时器扫描按键
引入Timer0.h和Nixie.h(数码管)
#include <REGX52.H>
#include "AT24C02.h"
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
#include "Nixie.h"unsigned char KeyNum=0;
unsigned char Temp=0;
int main(){Timer0_Init();while(1){KeyNum=Key();if(KeyNum){
Temp=KeyNum;}NiXie(1,Temp);
}
}void Timer0_Routine() interrupt 1
{static unsigned int T0Count;TL0 = 0x18; //设置定时初值TH0 = 0xFC; //设置定时初值T0Count++;if(T0Count>=20)//20ms调用一次{T0Count=0;Key_Loop();//获取按键值并存放在全局变量中}
}
Key.h
#ifndef __KEY_H__
#define __KEY_H__unsigned char Key(void);
void Key_Loop(void);
#endif
Key.c
#include <REGX52.H>
#include "Delay.h"
unsigned char Key_KeyNumber;
//获取按键状态,无消抖检测
unsigned char Key_GetState()
{unsigned char KeyNumber=0;if(P3_1==0){KeyNumber=1;}if(P3_0==0){KeyNumber=2;}if(P3_2==0){KeyNumber=3;}if(P3_3==0){KeyNumber=4;}return KeyNumber;
}//每隔20ms调用一次
void Key_Loop(void){
static unsigned char NowState,LastState;
LastState=NowState;
NowState=Key_GetState();if(LastState==1&&NowState==0){//表示k1按下后已松手,所以不需要消抖Key_KeyNumber=1;}if(LastState==2&&NowState==0){//表示k2按下后已松手Key_KeyNumber=2;}if(LastState==3&&NowState==0){//表示k3按下后已松手Key_KeyNumber=3;}if(LastState==4&&NowState==0){//表示k4按下后已松手Key_KeyNumber=4;}
}//获取按键值
unsigned char Key(void){
unsigned char Temp=0;Temp=Key_KeyNumber;Key_KeyNumber=0;
return Temp;
}
同理:定时器扫描数码管
Nixie.h
#ifndef __NIXIE_H__
#define __NIXIE_H__
#include "Delay.h"
void Nixie_Loop(void);
void Nixie_SetBuf(unsigned char Location,Number);
#endif
Nixie.c
#include "NiXie.h"
#include <REGX52.H>
//数码管显示缓存区
unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};//从1-8一共8个数码管unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00};
//数码管显示数字
void Nixie_Scan(unsigned char Location,unsigned char Number){P0=0x00;switch (Location){case 1:P2_4=1;P2_3=1;P2_2=1;break; case 2:P2_4=1;P2_3=1;P2_2=0;break;case 3:P2_4=1;P2_3=0;P2_2=1;break;case 4:P2_4=1;P2_3=0;P2_2=0;break;case 5:P2_4=0;P2_3=1;P2_2=1;break;case 6:P2_4=0;P2_3=1;P2_2=0;break;case 7:P2_4=0;P2_3=0;P2_2=1;break;case 8:P2_4=0;P2_3=0;P2_2=0;break;}P0=NixieTable[Number];
}//每2ms执行一次
void Nixie_Loop(void){//从第一个数码管开始扫描
static unsigned char i=1;
Nixie_Scan(i,Nixie_Buf[i]);//从缓存区中获取当前数码管的数字
i++;
if(i>=9){i=1;}}//设置显示的缓存区
void Nixie_SetBuf(unsigned char Location,Number){
Nixie_Buf[Location]=Number;}
main.c,同时中断Key_Loop()和Nixie_Loop()
void Timer0_Routine() interrupt 1
{static unsigned int T0Count,T0Count1;TL0 = 0x18; //设置定时初值TH0 = 0xFC; //设置定时初值T0Count++;T0Count1++;if(T0Count>=20)//20ms调用一次{T0Count=0;Key_Loop();}if(T0Count1>=2){T0Count1=0;Nixie_Loop();}}
案例:数码管实现秒表,k1启动和暂停,k2清零,k3写入EEPROM,k4读取
#include <REGX52.H>
#include "AT24C02.h"
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
#include "Nixie.h"
unsigned char KeyNum;
unsigned char Min,Sec,MinSec;
unsigned char RunFlag=0;//1表示启动,0表示暂停
int main(){
Timer0_Init();while(1){
KeyNum=Key();
if(KeyNum==1){
RunFlag=!RunFlag;
}
if(KeyNum==2){Min=0,Sec=0,MinSec=0;
}if(KeyNum==3) //K3按键按下{AT24C02_Write(0,Min); //将分秒写入AT24C02Delay(5);AT24C02_Write(1,Sec);Delay(5);AT24C02_Write(2,MinSec);Delay(5);}if(KeyNum==4) //K4按键按下{Min=AT24C02_Read(0); //读出AT24C02数据Sec=AT24C02_Read(1);MinSec=AT24C02_Read(2);}Nixie_SetBuf(1,Min/10); //设置显示缓存,显示数据Nixie_SetBuf(2,Min%10);Nixie_SetBuf(3,11);//11表示 - 0x40Nixie_SetBuf(4,Sec/10);Nixie_SetBuf(5,Sec%10);Nixie_SetBuf(6,11);Nixie_SetBuf(7,MinSec/10);Nixie_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 Timer0_Routine() interrupt 1
{static unsigned int T0Count,T0Count1,T0Count2;TL0 = 0x18; //设置定时初值TH0 = 0xFC; //设置定时初值T0Count++;if(T0Count>=20)//20ms调用一次{T0Count=0;Key_Loop();}T0Count1++;if(T0Count1>=2){T0Count1=0;Nixie_Loop();}T0Count2++;if(T0Count2>=10){//10ms调用一次Sec_Loop();}}
修改一下数码管显示-符号
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};
DS18B20
先延时15us,将要发送的数据给DQ,再延时50us
先延时5us,释放总线,再延时5us,读取数据
本次只使用SKIP ROM,CONVERT T,READ SCRATCHPAD
OneWire.h
#ifndef __ONEWIRE_H__
#define __ONEWIRE_H__
#include <REGX52.H>unsigned char OneWire_Init(void);
void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit(void);
void OneWire_SendByte(unsigned char Byte);
char OneWire_ReceiveByte(void);#endif
OneWire.c
#include "OneWire.h"sbit OneWire_DQ=P3^7;unsigned char OneWire_Init(void){
unsigned char i;
unsigned char AckBit;
OneWire_DQ=1;
OneWire_DQ=0;
i=247;while(--i); //Delay 500us 500/2-3
OneWire_DQ=1;
i=32;while(--i);//Delay 70us 70/2 -3
//接受从机回复,0收到,1未收到
AckBit=OneWire_DQ;
i=247;while(--i);//Delay 500us
return AckBit;
}void OneWire_SendBit(unsigned char Bit){
unsigned char i;
OneWire_DQ=0;
i=4;while(--i);//Delay 10us
OneWire_DQ= Bit; //如果发送的是1,则总线被释放 => 拉低15us,然后释放总线,表示发送1
i=24;while(--i);//Delay 50us //如果发送的是0,则总线一直被拉低(60us)=>表示发送的是0
OneWire_DQ=1;
}unsigned char OneWire_ReceiveBit(void){
unsigned char i;
unsigned char Bit;
OneWire_DQ=0;
i=2;while(--i);//Delay 5us
OneWire_DQ=1;
i=2;while(--i);//Delay 5us
Bit=OneWire_DQ;
i=24;while(--i);//Delay 50usreturn Bit;
}//单总线发送一个字节
void OneWire_SendByte(unsigned char Byte){unsigned char i;
for(i=0;i<8;i++){
OneWire_SendBit(Byte&(0x01<<i));}
}
//单总线接受一个字节
char OneWire_ReceiveByte(void){
unsigned char i;unsigned char Byte=0x00;for(i=0;i<8;i++){if(OneWire_ReceiveBit()){Byte|=(0x01<<i);
}}return Byte;
}
DS18B20.h
#ifndef __DS18B20_H__
#define __DS18B20_H__
#include "OneWire.h"
#include <REGX52.H>
void DS18B20_ConvertT(void);
float DS18B20_ReadT(void);#endif
DS18B20.c
#include "DS18B20.h"
//DS18B20指令
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBEvoid DS18B20_ConvertT(void){OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}float DS18B20_ReadT(void){unsigned char TLSB,TMSB;int Temp;//16位有符号数float T;OneWire_Init();OneWire_SendByte(DS18B20_SKIP_ROM);OneWire_SendByte(DS18B20_READ_SCRATCHPAD);TLSB=OneWire_ReceiveByte();//8位TMSB=OneWire_ReceiveByte();//8位Temp=(TMSB<<8)|TLSB;T=Temp/16.0;//因为后四位代表 2的-1 2的-2 2的-3次方 2的-4次方。而Temp的最后一位代表2的0次方,所以Temp扩大了16倍return T;
}
main.c
#include <REGX52.H>
#include "DS18B20.h"
#include "LCD1602.h"//通过LCD1602显示数据
#include "Delay.h"float T;
int main(){DS18B20_ConvertT(); //上电先转换一次温度,防止第一次读数据错误Delay(1000); //等待转换完成LCD_Init();LCD_ShowString(1,1,"Temperature:");
while(1){DS18B20_ConvertT();T=DS18B20_ReadT();if(T<0){LCD_ShowChar(2,1,'-');//显示负号T=-T;}else{LCD_ShowChar(2,1,'+');}LCD_ShowNum(2,2,T,3);//前三位整数部分LCD_ShowChar(2,3,'.');LCD_ShowNum(2,6,(unsigned long)(T*10000)%1000,4);//将温度小数部分变成整数,显示出来}
}
注:如果涉及到定时器中断,由于单总线的时序非常严格,所以在进入前需要把中断关闭
unsigned char OneWire_ReceiveBit(void){
unsigned char Bit,i;EA=0;
OneWire_DQ=1;
OneWire_DQ=0;
i=2;while(--i);//Delay 5usOneWire_DQ=1;//释放i=2;while(--i);//Delay 5us
Bit=OneWire_DQ;i=24;while(--i);//Delay 50usEA=1;return Bit;}
LCD1602
LCD1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);#endif
LCD1602.c
#include <REGX52.H>//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0//函数定义:
/*** @brief LCD1602延时函数,12MHz调用可延时1ms* @param 无* @retval 无*/
void LCD_Delay()
{unsigned char i, j;i = 2;j = 239;do{while (--j);} while (--i);
}/*** @brief LCD1602写命令* @param Command 要写入的命令* @retval 无*/
void LCD_WriteCommand(unsigned char Command)
{LCD_RS=0;LCD_RW=0;LCD_DataPort=Command;LCD_EN=1;LCD_Delay();LCD_EN=0;LCD_Delay();
}/*** @brief LCD1602写数据* @param Data 要写入的数据* @retval 无*/
void LCD_WriteData(unsigned char Data)
{LCD_RS=1;LCD_RW=0;LCD_DataPort=Data;LCD_EN=1;LCD_Delay();LCD_EN=0;LCD_Delay();
}/*** @brief LCD1602设置光标位置* @param Line 行位置,范围:1~2* @param Column 列位置,范围:1~16* @retval 无*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{if(Line==1){LCD_WriteCommand(0x80|(Column-1));}else if(Line==2){LCD_WriteCommand(0x80|(Column-1+0x40));}
}/*** @brief LCD1602初始化函数* @param 无* @retval 无*/
void LCD_Init()
{LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动LCD_WriteCommand(0x01);//光标复位,清屏
}/*** @brief 在LCD1602指定位置上显示一个字符* @param Line 行位置,范围:1~2* @param Column 列位置,范围:1~16* @param Char 要显示的字符* @retval 无*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{LCD_SetCursor(Line,Column);LCD_WriteData(Char);
}/*** @brief 在LCD1602指定位置开始显示所给字符串* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param String 要显示的字符串* @retval 无*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=0;String[i]!='\0';i++){LCD_WriteData(String[i]);}
}/*** @brief 返回值=X的Y次方*/
int LCD_Pow(int X,int Y)
{unsigned char i;int Result=1;for(i=0;i<Y;i++){Result*=X;}return Result;
}/*** @brief 在LCD1602指定位置开始显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~65535* @param Length 要显示数字的长度,范围:1~5* @retval 无*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');}
}/*** @brief 在LCD1602指定位置开始以有符号十进制显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:-32768~32767* @param Length 要显示数字的长度,范围:1~5* @retval 无*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{unsigned char i;unsigned int Number1;LCD_SetCursor(Line,Column);if(Number>=0){LCD_WriteData('+');Number1=Number;}else{LCD_WriteData('-');Number1=-Number;}for(i=Length;i>0;i--){LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');}
}/*** @brief 在LCD1602指定位置开始以十六进制显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~0xFFFF* @param Length 要显示数字的长度,范围:1~4* @retval 无*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i,SingleNumber;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){SingleNumber=Number/LCD_Pow(16,i-1)%16;//同10进制一样if(SingleNumber<10){LCD_WriteData(SingleNumber+'0');}else{LCD_WriteData(SingleNumber-10+'A');}}
}/*** @brief 在LCD1602指定位置开始以二进制显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~1111 1111 1111 1111* @param Length 要显示数字的长度,范围:1~16* @retval 无*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');}
}
直流电机驱动
呼吸灯实现
#include <REGX52.H>sbit LED=P2^0;void Delay(unsigned int t){
while(t--);
}int main(){unsigned char Time,i;
while(1){
for(Time=0;Time<100;Time++){for(i=0;i<20;i++){//延时//变暗LED=0;Delay(Time);LED=1;Delay(100-Time);}}for(Time=100;Time>0;Time--){for(i=0;i<20;i++){//延时LED=0;Delay(Time);LED=1;Delay(100-Time);}}}}
通过定时器+中断实现呼吸灯
sbit LED=P2^0;void main()
{Timer0_Init();while(1){for(i=0;i<100;i++){Compare=i; //设置比较值,改变PWM占空比Delay(10);}for(i=100;i>0;i--){Compare=i; //设置比较值,改变PWM占空比Delay(10);}}
}void Timer0_Routine() interrupt 1
{TL0 = 0x9C; //设置定时初值TH0 = 0xFF; //设置定时初值Counter++;Counter%=100; //计数值变化范围限制在0~99if(Counter<Compare) //计数值小于比较值{LED=1; //输出1}else //计数值大于比较值{LED=0; //输出0}
}
直流电机调速
引入NiXie.c,Timer0.c,Key.c,Delay.c
main.c
#include <REGX52.H>#include "Key.h"
#include "Timer0.h"
#include "NiXie.h"sbit Motor=P1^0;
unsigned char Counter,Compare;
unsigned char KeyNum,Speed;
int main(){
Timer0Init();
while(1){
KeyNum=Key();if(KeyNum==1){Speed++;Speed%=4;if(Speed==0){Compare=0;}if(Speed==1){Compare=50;}if(Speed==2){Compare=75;}if(Speed==3){Compare=100;}}NiXie(1,Speed);}
}void Timer0_Routine() interrupt 1
{TL0 = 0x9C; //设置定时初值TH0 = 0xFF; //设置定时初值Counter++;Counter%=100;//注意这里取模,而不是Count=0,if(Counter<Compare){Motor=1;//电机转动}else{Motor=0;}
}
电机可能这种方式有局限性,比如电阻太大直接不转,驱动不起来,太小烧毁。
脉冲调制比如:“转2s”“停1s”“转2s”“停1s”……因为电机有惯性,所以可行。
AD/DA
使得调节开发板上的电位器时,数码管上能够显示 AD 模块 采集电位器的电压值且随之变化。
开发板上有三个应用:光敏电阻,热敏电阻,电位器。
VBAT:电池电压。
AUX:辅助电压。
XP YP:XY 正极。
XPT2046.h
#ifndef __XPT2046_H__
#define __XPT2046_H__#define XPT2046_VBAT 0xAC //光敏电阻
#define XPT2046_AUX 0xEC
#define XPT2046_XP 0x9C //0xBC 可调电阻
#define XPT2046_YP 0xDC //热敏电阻 unsigned int XPT2046_ReadAD(unsigned char Command);#endif
XPT2046.c
#include "XPT2046.h"#include <REGX52.H>
//引脚定义
sbit XPY2046_DIN=P3^4;
sbit XPY2046_CS=P3^5;
sbit XPY2046_DCLK=P3^6;
sbit XPY2046_DOUT=P3^7;unsigned int XPT2046_ReadAD(unsigned char Command){unsigned char i;unsigned int Data=0;XPY2046_CS=0;XPY2046_DCLK=0;//要读取的地址for(i=0;i<8;i++){XPY2046_DIN=Command&(0x80>>i);XPY2046_DCLK=1;XPY2046_DCLK=0;}for(i=0;i<16;i++){XPY2046_DCLK=1;XPY2046_DCLK=0;if(XPY2046_DOUT){Data|=(0x8000>>i);//Data是16位}}XPY2046_CS=1;return Data>>8;//AD转换后的数字量,范围:8位为0~255,12位为0~4095}
main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "XPT2046.h"unsigned int ADValue;void main(void)
{LCD_Init();LCD_ShowString(1,1,"ADJ NTC GR");while(1){ADValue=XPT2046_ReadAD(XPT2046_XP); //读取AIN0,可调电阻LCD_ShowNum(2,1,ADValue,3); //显示AIN0ADValue=XPT2046_ReadAD(XPT2046_YP); //读取AIN1,热敏电阻LCD_ShowNum(2,6,ADValue,3); //显示AIN1ADValue=XPT2046_ReadAD(XPT2046_VBAT); //读取AIN2,光敏电阻LCD_ShowNum(2,11,ADValue,3); //显示AIN2Delay(100);}
}
DA
类似于pwm
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"sbit DA=P2^1;unsigned char Counter,Compare; //计数值和比较值,用于输出PWM
unsigned char i;void main()
{Timer0_Init();while(1){for(i=0;i<100;i++){Compare=i; //设置比较值,改变PWM占空比Delay(10);}for(i=100;i>0;i--){Compare=i; //设置比较值,改变PWM占空比Delay(10);}}
}void Timer0_Routine() interrupt 1
{TL0 = 0x9C; //设置定时初值TH0 = 0xFF; //设置定时初值Counter++;Counter%=100; //计数值变化范围限制在0~99if(Counter<Compare) //计数值小于比较值{DA=1; //输出1}else //计数值大于比较值{DA=0; //输出0}
}
红外遥控
键位的键码值
Int0.h
#ifndef __INT0_H__
#define __INT0_H__void Int0_Init(void);#endif
初始化外部中断
Int0.c
#include <REGX52.H>
#include "Int0.h"
/*** @brief 外部中断0初始化* @param 无* @retval 无*/
void Int0_Init(void)
{IT0=1;IE0=0;EX0=1;EA=1;PX0=1;
}
修改一下定时器的初始化函数,开始不计时并设置初值为0
Timer0.c
void Timer0_Init(void)
{TMOD &= 0xF0; //设置定时器模式TMOD |= 0x01; //设置定时器模式TL0 = 0; //设置定时初值TH0 = 0; //设置定时初值TF0 = 0; //清除TF0标志TR0 = 0; //定时器0不计时
}
IR.h
#ifndef __IR_H__
#define __IR_H__#define IR_POWER 0x45
#define IR_MODE 0x46
#define IR_MUTE 0x47
#define IR_START_STOP 0x44
#define IR_PREVIOUS 0x40
#define IR_NEXT 0x43
#define IR_EQ 0x07
#define IR_VOL_MINUS 0x15
#define IR_VOL_ADD 0x09
#define IR_0 0x16
#define IR_RPT 0x19
#define IR_USD 0x0D
#define IR_1 0x0C
#define IR_2 0x18
#define IR_3 0x5E
#define IR_4 0x08
#define IR_5 0x1C
#define IR_6 0x5A
#define IR_7 0x42
#define IR_8 0x52
#define IR_9 0x4A
//红外遥控初始化
void IR_Init(void);unsigned char IR_GetDataFlag(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetCommand(void);#endif
IR.c
#include <REGX52.H>
#include "IR.h"
#include "Int0.h"
#include "Timer0.h"unsigned int IR_Time;
unsigned char IR_State;unsigned char IR_Data[4];//存放32位数据。每个数组位代表8位
unsigned char IR_pData;
//0表示空闲状态 1表示等待start或repeat信号 2表示接受到start信号,准备接受数据
unsigned char IR_DataFlag;//是否收到数据unsigned char IR_RepeatFlag;//是否收到连发
unsigned char IR_Address;
unsigned char IR_Command;void IR_Init(void){Timer0_Init();Int0_Init();}//外部中断0中断函数,下降沿触发执行
void Int0_Routine(void) interrupt 0{
if(IR_State==0){//空闲状态Timer0_SetCounter(0);//定时器计数器清零
Timer0_Run(1);//启动计时器
IR_State=1;//设置状态为1
}
else if(IR_State==1){
IR_Time=Timer0_GetCounter();//获取运行时间
Timer0_SetCounter(0);//重新计数//如果计时为13.5ms,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)if(IR_Time>12442-500 && IR_Time<12442+500){IR_State=2; //置状态为2}//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)else if(IR_Time>10368-500 && IR_Time<10368+500){IR_RepeatFlag=1; //置收到连发帧标志位为1Timer0_Run(0); //定时器停止IR_State=0; //置状态为0}else //接收出错{IR_State=1; //置状态为1}
}else if(IR_State==2) //状态2,接收数据{IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间Timer0_SetCounter(0); //定时计数器清0//如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)if(IR_Time>1032-500 && IR_Time<1032+500){IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0,pData为32位IR_pData++; //数据位置指针自增}//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)else if(IR_Time>2074-500 && IR_Time<2074+500){IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1IR_pData++; //数据位置指针自增}else //接收出错{IR_pData=0; //数据位置指针清0IR_State=1; //置状态为1}if(IR_pData>=32) //如果接收到了32位数据,{IR_pData=0; //数据位置指针清0if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3])) //数据验证{//验证通过IR_Address=IR_Data[0]; //转存数据IR_Command=IR_Data[2];IR_DataFlag=1; //收到数据 置为1}Timer0_Run(0); //停止定时器IR_State=0; //置状态为0}}
}/*** @brief 红外遥控获取收到数据帧标志位* @param 无* @retval 是否收到数据帧,1为收到,0为未收到*/
unsigned char IR_GetDataFlag(void)
{if(IR_DataFlag){IR_DataFlag=0;return 1;}return 0;
}/*** @brief 红外遥控获取收到连发帧标志位* @param 无* @retval 是否收到连发帧,1为收到,0为未收到*/
unsigned char IR_GetRepeatFlag(void)
{if(IR_RepeatFlag){IR_RepeatFlag=0;return 1;}return 0;
}/*** @brief 红外遥控获取收到的地址数据* @param 无* @retval 收到的地址数据*/
unsigned char IR_GetAddress(void)
{return IR_Address;
}/*** @brief 红外遥控获取收到的命令数据* @param 无* @retval 收到的命令数据*/
unsigned char IR_GetCommand(void)
{return IR_Command;
}
main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "IR.h"unsigned char Num;
unsigned char Address;
unsigned char Command;void main()
{LCD_Init();LCD_ShowString(1,1,"ADDR CMD NUM");LCD_ShowString(2,1,"00 00 000");IR_Init();while(1){if(IR_GetDataFlag() || IR_GetRepeatFlag()) //如果收到数据帧或者收到连发帧{Address=IR_GetAddress(); //获取遥控器地址码Command=IR_GetCommand(); //获取遥控器命令码LCD_ShowHexNum(2,1,Address,2); //显示遥控器地址码LCD_ShowHexNum(2,7,Command,2); //显示遥控器命令码if(Command==IR_VOL_MINUS) //如果遥控器VOL-按键按下{Num--; //Num自减}if(Command==IR_VOL_ADD) //如果遥控器VOL+按键按下{Num++; //Num自增}LCD_ShowNum(2,12,Num,3); //显示Num}}
}