模块化编程
把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其他.c文件想使用其中的代码时,只需要提供#include"XXX.h"文件即可。使用模块化编程可极大提高代码的可阅读性、可维护性、可移植性。
模块化编程框图
也就是说在Delay.c中写实现功能的代码,然后借助Delay.h实现一个预编译和中介作用,这里不要忘了在void函数后面加上分号,最后在main.c中直接调用Delay函数即可,注意头文件的形式是#include “Delay.h”
LCD1602调试工具
有两行十六列,可以显示数字或字符(串)方便调试
矩阵键盘
采用输入扫描:读取第一行(列),读取第二行(列)......快速循环,最终实现所有按键同时检测的效果。因为按行扫描会影响蜂鸣器。
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 矩阵键盘读取按键键码
* @param 无
* @retval KeyNumber 按下按键的键码值
如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
*/
unsigned char MatrixKey()
{
unsigned char KeyNumber=0;
P1=0xFF;
P1_3=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
P1=0xFF;
P1_2=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
P1=0xFF;
P1_1=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
P1=0xFF;
P1_0=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
return KeyNumber;
}
也就是说按键按下并松开后,返回键码
//在main.c中
unsigned char KeyNum=0;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"MatrixKey");
while(1)
{
KeyNum=MatrixKey();//将返回键值传给一个新变量
if(KeyNum)//对返回键值进行判断
{
LCD_ShowNum(2,1,KeyNum,2);//显示相应的数字
}
}
}
定时器
属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作;替代长时间的Delay,提高CPU的运行效率和处理速度。
STC89C52定时器/计数器资源有三个(T0、T1、T2),使用单片机要多看手册查看内部资源
定时器0模式1学习
T0和T1都分别有模式 1、2、3、4,模式1(最常用)对应到手册即将M1置0,M0置1,定时器0模式1框图如下:分为时钟(左)、计数(中间)、中断(右)
//配置TMOD不可位寻址
TMOD=0x01; //0000 0001 前四位配置定时器1,后四位配置定时器0
//配置TCON可位寻址
TF0=0; //中断溢出标志位清零,防止立刻产生中断
TR0=1; //TimerReady定时器开始工作
时钟
SYSclk(内部):系统时钟即晶振周期,给0即Timer定时器。
T0Pin(外部):由外部引脚提供时钟时,单片机的定时器相当于计数器,给1即Counter计数器。
计数
TL0(低字节TimerLow)TH0(高字节TimerHigh)十六位计数系统能计65535个数,左边每来一个脉冲,计数系统加一。65535加一溢出置标志位TF0(TimerFlag)。
//配置定时器0模式1后
TH0=64535/256; //获取高位
TL0=64535%256+1; //获取低位
中断
计到TF0申请中断
另外,STC89C52的中断资源包括8个中断源(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)、4个中断优先级、中断号
//中断号:中断查询次序号就是中断号,例如:
void Int0_Rountine(void) interrupt0; //外部中断
void Timer0_Rountine(void) interrupt1; //定时器中断
void Int1_Rountine(void) interrupt2;
void Timer1_Rountine(void) interrupt3;
void UART_Rountine(void) interrupt4; //串口中断
void Timer2_Rountine(void) interrupt5;
void Int2_Rountine(void) interrupt6;
void Int3_Rountine(void) interrupt7;
//配置中断
ET0=1;
EA=1;
PT0=0; //默认低优先级
unsigned int T0Count;
void Timer0_Routine() interrupt 1 //中断子函数
{
//每次中断都要重新赋初值
TH0=64535/256;
TL0=64535%256;
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
//要执行的程序
}
}
改进
由于TMOD是不可位寻址,为了不影响定时器1和定时器0的同时使用有以下改进:
TMOD&=0xF0; //把TMOD的低四位清零,高四位保持不变
TMOD|=0x01; //把TMOD的最低位置1,高四位保持不变
定时器的使用
常用的模板
可以将其模块化,方便使用。
按键检测
#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;
}
定时器0的配置
#include <REGX52.H>
/**
* @brief 定时器0初始化
* @param 无
* @retval 无
*/
void Timer0_Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
延时
void Delay(unsigned char xms) //@11.0592MHz
{
unsigned char data i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
按键控制LED流水灯模式
新函数库#include <INTRINS.H>
unsigned char a=0x01;
a=_crol_(a,1); //a=0x02
实现流水灯
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>
#include "Delay.h"
unsigned char KeyNum=0,LEDMode=0;
void main()
{
P2=0xFE;
Timer0_Init();
while(1)
{
KeyNum=Key();
if(KeyNum)
{
if(KeyNum==1)
{
LEDMode++;
if(LEDMode>=2)LEDMode=0;
}
}
}
}
void Timer0_Routine() interrupt 1 //中断子函数
{
static unsigned int T0Count=0;
//每次中断都要重新赋初值
TH0=0x18;
TL0=0xFC;
T0Count++;
if(T0Count>=500)
{
T0Count=0;
if(LEDMode==0)
P2=_crol_(P2,1);
if(LEDMode==1)
P2=_cror_(P2,1);
}
}
定时器时钟
就是一个多重循环判断,需要用到LCD1602进行显示、定时器
串口
应用广泛的通讯接口,可实现两个设备的互相通信
电平标准
TTL电平:+5V表示1,0V表示0
RS232电平:-3~-15V表示1,+3V~15V表示0
RS485电平:两线压差+2~+6表示1,-2~-6表示0(差分信号)
接口及引脚定义
51单片机的UART
有四种工作模式,常用的是模式1:8位UART,波特率可变
串口模式图
需要用到定时器,还有中断系统
串口相关寄存器
LED点阵屏
显示原理
LED点阵屏结构类似于数码管,且有共阴和共阳两种接法,进行逐行或逐列的扫描进行显示。
引脚和相关寄存器
74HC595是8位串行输入/输出、并行输出移位寄存器
SER口输入串行的数据,当SRCLK有上升沿移位信号时,数据就往下移一位,当数据占满8位时,给RCLK上升沿锁存信号时,8位数据就锁存到QA~QH,QH’连接到下一片74HC595,继续拓展16位、32位…
DS1302实时时钟
是RTC时钟芯片的一种,内含有一个实时时钟/日历和 31 字节静态 RAM,通过简单的串行接口与单片机进行通信。实时时钟/日历电路提供秒、分、时、日、周、月、年的信息,每月的天数和闰年的天数可自动调整。
应用电路
引脚名:
VCC2是主电源,VCC是备用电源,X1、X232.768Hz晶振(稳定提供1Hz脉冲)
CE芯片使能,IO数据输入/输出,SCLK串行时钟
内部结构框图
读写(寄存器定义)
地址和命令字
最高位7必须是1;
第6位给1操作RAM,给0操作时钟;
5~1地址位,给00000操作秒,写秒为80h(bcd码高字节表示第一位,低字节表示第二位,1000 0000)
第0位为0则为写,为1则为读.
时序图
整个操作过程中CE给高电平,写入操作才是有效的,结束后给0
SCLK上升沿写入,下降沿读出
写:CE置1,先发RW最低位,上升沿,时钟置0,IO次低位A0放线上,上升沿......
读:同上
BCD码:用4位二进制数来表示1位十进制数
例:0001 0011表示13,1000 0101表示85,0001 1010不合法
在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
BCD码转十进制:DEC=BCD/16*10+BCD%16;(2位BCD)
十进制转BCD码:BCD=DEC/10*16+DEC%10;(2位BCD)
#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
/**
* @brief DS1302初始化
* @param 无
* @retval 无
*/
void DS1302_Init(void)
{
DS1302_CE=0;
DS1302_SCLK=0;
}
/**
* @brief DS1302写一个字节
* @param Command 命令字/地址
* @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;
Command|=0x01; //将指令转换为读指令
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=0;
DS1302_SCLK=1; //先给0再给1,卡一个脉冲
}
for(i=0;i<8;i++)
{
DS1302_SCLK=1; //重复置1,去掉一个脉冲
DS1302_SCLK=0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_CE=1;
DS1302_IO=0; //读取后将IO设置为0,不然会出错
return Data;
}
/**
* @brief DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
* @param 无
* @retval 无
*/
void DS1302_SetTime(void)
{
DS1302_WriteByte(DS1302_WP,0x00);
DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10); //十进制转BCD码后写入
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);
}
/**
* @brief DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
* @param 无
* @retval 无
*/
void DS1302_ReadTime(void)
{
unsigned char Temp;
Temp=DS1302_ReadByte(DS1302_YEAR);//BCD码转十进制后读取
DS1302_Time[0]=Temp/16*10+Temp%16;
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;
}