模块化编程
a.将实现不同功能的代码块单独提出作为函数,降低开发难度,提高移植性。
keil内需要将子程序.c和.h文件放入project文件所在文件夹。
b.定义.h文件常用格式:
#ifndef __XXX_H__
#def __XXX_H__
//函数声明
#endif
LCD1602
功能更强大的显示屏,功能实现易于模块化,便于不了解原理而直接使用现成代码。江科大提供如下函数:
LCD_Init();
LCD_ShowString(1,1,"hello");
LCD_ShowChar(1,6,'A');
LCD_ShowNum(1,7,123,3);
LCD_ShowHexNum(1,10,0xA8,2);
LCD_ShowBinNum(2,1,0xAA,8);
矩阵键盘
a.数码管扫描为输出扫描,而类似于数码管扫描,矩阵键盘扫描为输入扫描,分为按行扫描和按列扫描,如此可节约I/O口。
按列扫描时,需要扫描的列置0,其他置1,某行按下会被下拉至0.
b.STC89C52的P15口在逐行扫描会出问题,与蜂鸣器有关,建议按列。
c.检测函数如下:
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;}
//以下省略
return keynum;
}
定时器
51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
STC89C52有3个定时器(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是STC89C52型号单片机新增。
STC89C52的T0,T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器
中断
配合计数器,可跳出循环执行额外操作。
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
以下为该型号的中断资源:
相关寄存器
手册里关于各寄存器有详细解释。
定时器应用
定时器初始化代码可用stc-isp辅助生成,如下:
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;
}
定时器时钟
#include <STC89C5xRC.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char second = 0;
unsigned char minute = 0;
unsigned char hour = 0;
void main()
{
Timer0_Init();
LCD_Init();
LCD_ShowString(1, 1, "Clock:");
while(1)
{
LCD_ShowNum(2, 1, hour, 2);
LCD_ShowString(2, 3, ":");
LCD_ShowNum(2, 4, minute, 2);
LCD_ShowString(2, 6, ":");
LCD_ShowNum(2, 7, second, 2);
}
}
void Timer0_Isr(void) interrupt 1
{
static unsigned int T0_count = 0;
TL0 = 0x66;
TH0 = 0xFC;
T0_count++;
if(T0_count >= 1000)
{
T0_count = 0;
second++;
if(second == 60)
{
second = 0;
minute++;
if(minute == 60)
{
minute = 0;
hour++;
if(hour == 24)
{
hour = 0;
}
}
}
}
}
串口通信
a.串口可用于实现两个设备之间的互相通信。
TTL电平:+5V表示1,0V表示0(单片机用的)
RS232电平:-3-15V表示1,+3+15V表示0(一般用在电脑等高电压的传输)
RS485电平:两线压差+2+6V表示1,-2-6V表示0
通信接口
b.51应用UART接口,其初始化代码可借由stc-isp生成:
void UART_Init(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率,这里0x40是ren置0,只能接收;若要能接受发送需要置1,即0x50
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设置定时初始值
TH1 = 0xF4; //设置定时重载值
ET1 = 0; //禁止定时器中断
TR1 = 1; //定时器1开始计时
EA=1; //打开总中断
ES=1; //打开串口中断
}
void UART_SendByte(unsigned char Byte) //向电脑发送数据
{
SBUF=Byte;
while(TI==0); //发送
TI=0; //软件复位
}
LED点阵屏
a.类似为8组数码管,行列式连接,每行为共阴/共阳。
点阵屏的图像显示也是通过不断扫描来实现的,分逐行/逐列扫描。
b.74HC595
点阵屏应用
a.点亮点阵屏,用函数简单控制动画帧。
b.用数组储存动画数据,如下:
unsigned char code Animation[]={ //这里加code后数组在flash存放,且不能更改,不加code的话存放在ram里
0x70,0xF8,0xFC,0x7F,0x7F,0xFC,0xF8,0x70, //
0x30,0x78,0x7C,0x3E,0x3E,0x7C,0x78,0x30,
0x00,0x00,0x30,0x38,0x38,0x30,0x00,0x00,
0x30,0x78,0x7C,0x3E,0x3E,0x7C,0x78,0x30,
}
模块部分如下:
#include <REGX52.H>
#include "delay.h"
#define MatriLED_PORT P0 //不加分号
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SERCLK
sbit SER=P3^4; //SER
void Matrix_Init()
{
SCK=0;
RCK=0;
}
void _74HC595_WriteByte(unsigned char Byte)
{
unsigned char i; //74HC595写入一个字节Byte
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); //高位在上低位在下,1亮0灭,控制点阵屏的每一列
MatriLED_PORT=~(0x80>>Column);
Delay(1); //消影
MatriLED_PORT=0xff; //对于点阵屏的每一列来说1灭0亮,所以复位用0xff
}
DS1302
a.内置时钟,自带进位逻辑,但任需代码判断。
b.芯片电路
CE:芯片使能,类似中介开关,置1才能输入移位
SLCK:串行时钟,0是下降沿(单片机读取DS1302数据),1是上升沿(向DS1302输入数据)
I/O:输入输出(一次一位)
(SLCK与IO配合实现输入移位)
VCC,GND:电源与电源地,左侧VCC是主电源,右侧VCC是备用电源
X1,X2:晶振,利用晶振计数
BCD码
用四位数表示十进制数的一位。
BCD码转十进制:DEC=BCD/1610+BCD%16; (2位BCD)
十进制转BCD码:BCD=DEC/1016+DEC%10; (2位BCD)
可调时钟
DS1302模块化(此略)后,主函数代码主要关注越界判断和利用定时器实现闪烁。
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Timer0.h"
unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;
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(2,1,DS1302_Time[3],2);//显示时
LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分
LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒
}
void TimeSet(void)//时间设置功能
{
if(KeyNum==2)//按键2按下
{
TimeSetSelect++;//设置选择位加1
TimeSetSelect%=6;//越界清零
}
if(KeyNum==3)//按键3按下
{
DS1302_Time[TimeSetSelect]++;//时间设置位数值加1
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]%4==0)
{
if(DS1302_Time[2]>29){DS1302_Time[2]=1;}//闰年2月
}
else
{
if(DS1302_Time[2]>28){DS1302_Time[2]=1;}//平年2月
}
}
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(KeyNum==4)//按键3按下
{
DS1302_Time[TimeSetSelect]--;//时间设置位数值减1
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]%4==0)
{
if(DS1302_Time[2]<1){DS1302_Time[2]=29;}//闰年2月
if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
}
else
{
if(DS1302_Time[2]<1){DS1302_Time[2]=28;}//平年2月
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;}//秒越界判断
}
//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1," ");}
else {LCD_ShowNum(1,1,DS1302_Time[0],2);}
if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4," ");}
else {LCD_ShowNum(1,4,DS1302_Time[1],2);}
if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7," ");}
else {LCD_ShowNum(1,7,DS1302_Time[2],2);}
if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1," ");}
else {LCD_ShowNum(2,1,DS1302_Time[3],2);}
if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4," ");}
else {LCD_ShowNum(2,4,DS1302_Time[4],2);}
if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7," ");}
else {LCD_ShowNum(2,7,DS1302_Time[5],2);}
}
void main()
{
LCD_Init();
DS1302_Init();
Timer0Init();
LCD_ShowString(1,1," - - ");//静态字符初始化显示
LCD_ShowString(2,1," : : ");
DS1302_SetTime();//设置时间
while(1)
{
KeyNum=Key();//读取键码
if(KeyNum==1)//按键1按下
{
if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换
else if(MODE==1){MODE=0;DS1302_SetTime();}
}
switch(MODE)//根据不同的功能执行不同的函数
{
case 0:TimeShow();break;
case 1:TimeSet();break;
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=500)//每500ms进入一次
{
T0Count=0;
TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
}
}