一.模块化编程
各个模块的代码放在不同的.c文件里,在.h文件里提供各个模块的外部可调用函数的声明;其他从.c文件想使用其中的代码时,只需要在开头声明调用的头文件:#include“XXX.h”即可。
可极大的提高代码的可阅读性、可维护性、可移植性等。
#include后<>–>在安装目录中寻找头文件
后“ ”–>在程序目录中寻找头文件
#define pi 3.14–>定义pi,将pi替换为3.14
#ifdef:如果定义了__XXX_H_
#ifndef:如果没有定义__XXX_H_
#endif:与if形式语句匹配,组成括号;
.h文件格式
#ifndef __XXX_H__
#define __XXX_H__ //防止重复定义出错,对代码进行选择;
>
>//给出函数的定义(执行预编译)
>
>#endif
二.LCD1602
使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可以实时观察单片机内部数据的变化情况,便于调试和演示。
三.矩阵键盘
使用矩阵排布可减少I/O口的占用;
通过循环快速逐行(列)扫描实现所有按键同时检测;
51单片机P0~P3:弱上拉强下拉(0驱动强,1驱动弱)——在靠近Vcc处置有上拉电阻,I/O口另一端接地时由于电势降落显示低电平,否则显示高电平;
设置扫描按键取值代码
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;}
按以上操作覆盖四列(行),最后返回keynum的值;
根据按键在LCD1602显示按键值:
while(1)
{
keynum=Matrixkey();//无按键按下时,返回值为0
if(keynum)//只有按键按下时改变LCD的显示,否则显示会在循环中被刷新为0
{
LCD_ShowNum(2,1,keynum,2);
}
}
密码锁(矩阵键盘输入密码):
#include <REGX52.H>
#include "Delay.h"
#include "Matrixkey.h"
#include "LCD1602.h"
unsigned char keynum;
unsigned int password;
unsigned int key;
unsigned char cnt;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"PassWord?");
while(cnt<4)//键盘输入四位数密码;
{
keynum=Matrixkey();
if(keynum)
{
if(keynum<=10)
{
key*=10;
key+=keynum%10;
cnt++;
}
}
}
cnt=0;
LCD_ShowString(2,1,"GUESS:");
while(1)
{
keynum=Matrixkey();
if(keynum)
{
if(keynum<=10)
{
if(cnt<4)//是否能进入输入模式的判断
{
password*=10;
password+=keynum%10;//按键输入10时置为0
cnt++;
}LCD_ShowNum(2,7,password,4);
}
if(keynum==11)
{
if(password==key)
{
LCD_ShowString(1,12," OK");
Delay(1000);
LCD_Init();//清屏
Delay(500);
LCD_ShowString(1,6,"Welcome");
LCD_ShowString(2,5,"MY Master");//MVP结算画面
}
else
{
LCD_ShowString(1,12," ERR");
Delay(500);
LCD_ShowString(1,12,"Again");
password=0;
cnt=0;//刷新输入资格
LCD_ShowNum(2,7,password,4);//重置
}
}
if(keynum==12)
{
password=0;
cnt=0;
LCD_ShowNum(2,7,password,4);//重置
}
if(keynum==13)//提示
{
LCD_ShowNum(2,7,key,4);
Delay(200);
LCD_ShowNum(2,7,password,4);
}
}
}
}
四.定时器
时钟系统(产生脉冲)——>计数单元(计数溢出)——>产生中断,执行中断任务;
SYSclk:系统时钟,即晶振周期,所使用开发板晶振为11.0592MHz
T0 Pin:计数器,由外部提供时钟;
通过CT选择计时还是计数;
+12:12分频;+6同理
TL0:time low,低8位;TH1:高8位;
TR0:控制定时器启动GATE:非门,置反;GATE置0时输出1,经过与门输出1,TR0通过与门单独控制定时器启动;GATE置1时由INT0和TR0共同控制定时器启动
往右 或门:有1置1; 与门:有0记0;TF0中断请求标志;
该模式定时器的中断源相关信息
中断查询次序即中断号;则该中断号为1;
该中断源下原理图
相关寄存器
定时器设置代码:
void Timer0Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式(清除低四位,不影响高四位)
TMOD |= 0x01; //设置定时器模式(设置低四位)
TL0 = 0x66; //设置定时初值(软件生成1ms)
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0(溢出)标志,溢出后由硬件清零,只需清零一次
TR0 = 1; //定时器0开始计时
PT0=0;//设置优先级
EA=1;//打开中断总开关
ET0=1;//打开该中断
}
中断函数:
void Time0_movement() interrupt 1
{
static unsigned int t;//静态变量,不丢失初值
t++;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
while(t>=1000)//设置计时时长
{
t=0;
//写出中断后的操作
}
}
函数_crol_(unsigned char, unsigned char)头文件:<INTRINS.H>
作用:crol(a,n)—>a向左按位移n位,可刷新;cror();右移;
定时器应用:
时钟:
#include <REGX52.H>
#include "Timer.h"
#include "LCD1602.h"
unsigned char sec=55,min=59,hour=23;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"CLOCK:");
LCD_ShowChar(2,3,':');
LCD_ShowChar(2,6,':');
Timer0Init();
while(1)
{
LCD_ShowNum(2,1,hour,2);
LCD_ShowNum(2,4,min,2);
LCD_ShowNum(2,7,sec,2);
}
}
void Time0_movement() interrupt 1
{
static unsigned int t;
t++;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
while(t>=1000)
{
t=0;
sec++;
if(sec>=60){sec=0;min++;}
if(min>=60){min=0;hour++;}
if(hour>=24){hour=0;}
}
}
五.串口通信
通信接口:UART;
通信方式:全双工、异步;
串口参数
波特率:串口通信的速率(发送和接受各数据位的间隔时间)
检验位:用于数据验证奇校验:保证1个数为奇;
偶校验:保证1个数为偶;停止位:用于数据帧间隔
串口模式图
SBUF:串口数据缓存寄存器,物理上为两个独立的寄存器,但占用相同的地址;写入时为发送寄存器,读入时为接送寄存器;
设置T1溢出时间来约定波特率
TI:发送控制器,发送数据后置1,触发中断;硬件不会自动复位,须用软件复位
RI:接收控制器,接收数据后置1,触发中断;硬件不会自动复位,须用软件复位
波特率计算:计数溢出所用时间相应频率经过16分频后的频率;
UART中断源相关资料
中断号为4
相关寄存器
可位寻址:可以对寄存器中单个单元操作
不可位寻址:只能对寄存器整体赋值;
SCON:设置TI,RI,SM0,SM1(决定模式),RED(决定是否能接受数据)
PCON:设置波特率;
初始化串口代码:
void UART_Init() //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1,以约定波特率发送/接收数据
EA=1; //打开中断总开关(发送时不用)
ES=1; //打开UART中断开关(发送时不用)
}
8位自动重装:将TH1的值重装进TL1中,TH1值不变,不需要重新赋值;计数个数为0~255;
发送数据代码:
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;//发送数据
while(TI==0);//若未发送则停留在此处
TI=0;//软件置零
}
接受数据后中断操作代码:
void UART_Routine() interrupt 4
{
if(RI==1)//判断数据是否接收
{
P2=SBUF;//用LED显示接受的数据
UART_SendByte(SBUF);
RI=0;//软件置零
}
}
文本模式:发送ASCII编码对应的符号
六.LED点阵屏
(使用时需将OE接到低电平GND处)
通过逐行(列)扫描显示所有LED;
74HC595模块串转并实现减少I/O口;
RCLK:寄存器时钟;上升沿沿将一个字节的数据锁存
SRCLK:串形时钟;上升沿将一个位的数据位移
SER:串行数据
QH;用于级联,扩展I/O口
写入数据代码:
sbit RCK=P3^5;//特殊位声明(取地址)
sbit SRCK=P3^6;
sbit SER=P3^4;
/**
* @brief 读入选择行
* @param Byte 行数相应的代码
* @retval 无
*/
void linectr(unsigned char Byte)
{
unsigned char cnt=0;
for(cnt=0;cnt<8;cnt++)
{
SER=Byte&(0x80>>cnt);//SER位赋值为有1赋1,从最高位开始读入
SRCK=1;//位移
SRCK=0;
}
RCK=1;//锁存
RCK=0;
}
在LED屏上显示一列代码:
/**
* @brief 点阵显示列
* @param column,line 分别确定行和列
* @retval 无
*/
void MatrixLED_Showcolumn(unsigned char column,line)
{
linectr(line);//1为选中
P0=~(0x80>>column);//为0则选中
Delay(1);//暂留效果
P0=0xFF;//防止段选窜跑
}
若要显示画面只需在while中循环显示各列;
若要显示动画:
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"
unsigned char code Animin[]={0x2C,0xFF,0x28,0x11,0x7F,0x11,0x11,0x00,
0x14,0x28,0x5F,0x28,0x0A,0x2F,0x28,0x08,
0x08,0x08,0x7E,0x08,0x08,0x7E,0x08,0x08,
0x20,0x7F,0x80,0x4C,0xC2,0x5F,0x60,0x4C,
0x00,0x00,0x82,0xBA,0x8A,0xF8,0x0B,0x0F,
0x00,0x00,0x00,0xFB,0xFB,0x00,0x00,0x00,
};//软件对动画取模,将数据储存在数组中;code:将数据置于flash中,节省空间,不能更改
void main()
{
unsigned char i=0,offset=0,cnt=0;
MatrixLED_Init();
while(1)
{
for(i=0;i<8;i++)//循环读取列显示画面
{
MatrixLED_Showcolumn(i,Animin[i+offset]);
}
cnt++;
while(cnt>=50)//决定画面暂留/换帧时间
{
offset+=8;//切换到下八位显示
cnt=0;
if(offset>40)offset=0;//重复显示
}
}
}
七.DS1302
DS1302:具有涓细电流充电能力的低功耗实时时钟芯片;
数据传输原理图:
CE:高电平有效;
SCLK:通过上升沿和下降沿来写入/读入数据
I/O:位数据暂存;输入写命令后写入数据,输入读命令字时读入数据;
从最低位写入数据;
相关寄存器:
0~3位为时间的个位,后4位为时间的十位;即输出时间为BCD码;
WP:写入保护,写入时需将该位置零。
数据写入代码:
void DS1302_WriteByte(unsigned char command,time)
{
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=time&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_CE=0;
}
读出数据:
#define DS1302_SEC 0x80
#define DS1302_HOUR 0x84
#define DS1302_MIN 0x82
#define DS1302_YEAR 0x8C
#define DS1302_MON 0x88
#define DS1302_DATE 0x86
#define DS1302_DAY 0x8A
#define DS1302_WP 0x8E//定义地址的名字
unsigned char DS1302_ReadByte(unsigned char command)
{
DS1302_CE=1;//使能
command|=0x01;//将最低位置1后上述写地址转变为读地址。
ret=0x00;
for(i=0;i<8;i++)
{
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){ret|=(0x01<<i);}//不影响其他位情况下读入当前位数据;
}
DS1302_CE=0;
DS1302_IO=0;//防止出错
return ret;
}
可调时钟(位选闪烁)代码:
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Timer.h"
#include "key.h"
unsigned char keynum,mode,tselect,flash;
void showtime()//刷新LCD显示
{
DS1302_Timeread();
LCD_ShowNum(1,1,Time[0],2);
LCD_ShowNum(1,4,Time[1],2);
LCD_ShowNum(1,7,Time[2],2);
LCD_ShowNum(2,1,Time[3],2);
LCD_ShowNum(2,4,Time[4],2);
LCD_ShowNum(2,7,Time[5],2);
}
void settime()//更改时间 模式
{
keynum=key();
if(keynum==2)//对时间进行位选
{
tselect++;
tselect%=6;
}
if(keynum==3)
{
Time[tselect]++;
if(Time[0]>99){Time[0]=0;}
if(Time[1]>12){Time[1]=1;}
if(Time[1]==1||Time[1]==3||Time[1]==5||Time[1]==7||Time[1]==8||Time[1]==10||Time[1]==12){if(Time[2]>31)Time[2]=1;}
else if(Time[1]==4||Time[1]==6||Time[1]==9||Time[1]==11){if(Time[2]>30)Time[2]=1;}
else if(Time[1]==2){if(Time[0]%4==0){if(Time[2]>29)Time[2]=1;}else{if(Time[2]>28)Time[2]=1;}}
if(Time[3]>23){Time[3]=0;}
if(Time[4]>59){Time[4]=0;}
if(Time[5]>59){Time[5]=0;}//对时间溢出的判断
}
if(keynum==4)
{
Time[tselect]--;
if(Time[0]<0){Time[0]=99;}
if(Time[1]<1){Time[1]=12;}
if(Time[1]==1||Time[1]==3||Time[1]==5||Time[1]==7||Time[1]==8||Time[1]==10||Time[1]==12){if(Time[2]<1)Time[2]=31;}
else if(Time[1]==4||Time[1]==6||Time[1]==9||Time[1]==11){if(Time[2]<1)Time[2]=30;if(Time[2]>30)Time[2]=1;}
else if(Time[1]==2){if(Time[0]%4==0){if(Time[2]<1)Time[2]=29;if(Time[2]>29)Time[2]=1;}else{if(Time[2]<1)Time[2]=28;if(Time[2]>28)Time[2]=1;}}
if(Time[3]<0){Time[3]=23;}
if(Time[4]<0){Time[4]=59;}
if(Time[5]<0){Time[5]=59;}//对时间小于0/设置天数后更改月时天数溢出的处理
}
if(tselect==0&&flash==1){LCD_ShowString(1,1," ");}
else{LCD_ShowNum(1,1,Time[0],2);}
if(tselect==1&&flash==1){LCD_ShowString(1,4," ");}
else{LCD_ShowNum(1,4,Time[1],2);}
if(tselect==2&&flash==1){LCD_ShowString(1,7," ");}
else{LCD_ShowNum(1,7,Time[2],2);}
if(tselect==3&&flash==1){LCD_ShowString(2,1," ");}
else{LCD_ShowNum(2,1,Time[3],2);}
if(tselect==4&&flash==1){LCD_ShowString(2,4," ");}
else{LCD_ShowNum(2,4,Time[4],2);}
if(tselect==5&&flash==1){LCD_ShowString(2,7," ");}
else{LCD_ShowNum(2,7,Time[5],2);}
LCD_ShowNum(2,10,tselect,2);//选中闪烁
}
void main()
{
DS1302_Init();
LCD_Init();
Timer0Init();
DS1302_Timeset();
LCD_ShowString(1,1," - -");
LCD_ShowString(2,1," : :");
while(1)
{
keynum=key();
if(keynum==1)
{
mode=!mode;
if(!mode)DS1302_Timeset();//将更改的数据确认设置
}
if(!mode)//通过mode状态控制模式
{
showtime();
}
else{
settime();
}
}
}
void Time0_movement() interrupt 1
{
static unsigned int t;
t++;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
while(t>=500)
{
t=0;
flash=!flash;//通过该变量控制闪烁
}
}
DS1302模块代码:
#include <REGX52.H>
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;
unsigned char i=0,ret=0x00;
char Time[]={23,12,13,14,3,55,3};//初始化时间,依次为年月日时分秒
#define DS1302_SEC 0x80
#define DS1302_HOUR 0x84
#define DS1302_MIN 0x82
#define DS1302_YEAR 0x8C
#define DS1302_MON 0x88
#define DS1302_DATE 0x86
#define DS1302_DAY 0x8A
#define DS1302_WP 0x8E
void DS1302_Init()
{
DS1302_SCLK=0;
DS1302_CE=0;
DS1302_IO=0;
}//初始化
void DS1302_WriteByte(unsigned char command,time)
{
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=time&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_CE=0;
}
unsigned char DS1302_ReadByte(unsigned char command)
{
DS1302_CE=1;
command|=0x01;
ret=0x00;
for(i=0;i<8;i++)
{
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){ret|=(0x01<<i);}
}
DS1302_CE=0;
DS1302_IO=0;
return ret;
}
void DS1302_Timeset()//读入初始时间/读入修改后的时间
{
DS1302_WriteByte(DS1302_WP,0x00);//解除写入保护
DS1302_WriteByte(DS1302_YEAR,Time[0]/10*16+Time[0]%10);
DS1302_WriteByte(DS1302_MON,Time[1]/10*16+Time[1]%10);
DS1302_WriteByte(DS1302_DATE,Time[2]/10*16+Time[2]%10);
DS1302_WriteByte(DS1302_HOUR,Time[3]/10*16+Time[3]%10);
DS1302_WriteByte(DS1302_MIN,Time[4]/10*16+Time[4]%10);
DS1302_WriteByte(DS1302_SEC,Time[5]/10*16+Time[5]%10);
DS1302_WriteByte(DS1302_DAY,Time[6]/10*16+Time[6]%10);//将时间的BCD码写入
DS1302_WriteByte(DS1302_WP,0x80);
}
unsigned char BCD(unsigned char Byte)
{
unsigned char temp=0;
temp=Byte/16*10+Byte%16;
return temp;
}//BCD码转变为十进制数
void DS1302_Timeread()
{
Time[0]=BCD(DS1302_ReadByte(DS1302_YEAR));
Time[1]=BCD(DS1302_ReadByte(DS1302_MON));
Time[2]=BCD(DS1302_ReadByte(DS1302_DATE));
Time[3]=BCD(DS1302_ReadByte(DS1302_HOUR));
Time[4]=BCD(DS1302_ReadByte(DS1302_MIN));
Time[5]=BCD(DS1302_ReadByte(DS1302_SEC));
Time[6]=BCD(DS1302_ReadByte(DS1302_DAY));
}
以上图片取自普中开发板原理图,STC89C52手册,DS1302原理图