目录
一、模块化编程
1.什么是模块化编程
模块化编程是一种软件开发方法,它将程序拆分为独立的、相互关联的模块。在单片机编程中,模块化设计有助于提高代码的可读性、可维护性和可重用性。
2.模块化编程的好处
1.代码结构清晰2.易于维护3.提高代码重用性4.降低调试难度5.提高系统的灵活性6.更好的单元测试
3.如何实现模块化
1.创建.c文件,在文件中输入相应模块代码
2.创建.h文件,在文件中定义相关函数,如:
#ifndef__xx_H_//此处可直接使用keli的templates区域内的##define(需稍作修改)
#define__xx_H_
void .....;//注意:一定要打上分号
##endif
3.调用函数:在主函数中使用#include"xxx.h"(也可在templates中修改后直接使用)
二.矩阵按键
1.简述
在单片机应用系统中,通过按键实现数据输入及功能控制是非常普遍的,通常在所需按键数量不多时,系统常采用独立式按键。需要按键数量比较多,为了减少I/O口的占用,通常将按键排列成矩阵.
2.硬件设计
该处使用的url网络请求的数据。
3.矩阵按键扫描
思路:
1、查询是否有键按下。
2、键的抖动处理。
3、查询按下键所在行和列位置,得到键值。
代码设计
#include <REGX52.H>
#include "delay.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;
}
法二:
void KeyDown(void)
{
char a=0;
GPIO_KEY=0x0f;
if(GPIO_KEY!=0x0f)//读取按键是否按下
{
delay(1000);//延时 10ms 进行消抖
if(GPIO_KEY!=0x0f)//再次检测键盘是否按下
{
//测试列
GPIO_KEY=0X0F;
switch(GPIO_KEY)
{
case(0X07): KeyValue=0;break;
case(0X0b): KeyValue=1;break;
case(0X0d): KeyValue=2;break;
case(0X0e): KeyValue=3;break;
}
//测试行
GPIO_KEY=0XF0;
switch(GPIO_KEY)
{
case(0X70): KeyValue=KeyValue;break;
case(0Xb0): KeyValue=KeyValue+4;break;
case(0Xd0): KeyValue=KeyValue+8;break;
case(0Xe0): KeyValue=KeyValue+12;break;
}
}
}
return keyvalue;
}
三.定时器
1.定时器有关知识
1.CPU时许相关内容
2.基本知识
2.定时器原理
51单片机定时器0内部有两个寄存器TH0(高位寄存器)和TL0(低位寄存器),都是一字节的,2字节最大能存65535。
每过一个指令周期(1us),寄存器的值+1,当加到溢出后发出一个溢出中断,程序可以捕获到这个中断,就可以知道此时经历了(65535+1)us。
若要定时1ms,只需设置寄存器的初值为64536,这样到溢出值65536就正好1ms。
如:对一个以12mhz为晶振的单片机,其振荡周期=1/12M(s)=1/12(us)
其机器周期(指令周期)=12*振荡周期=1us;
3.定时器结构
定时器内部结构如图:
TCON控制寄存器:
TF0 = 0; //清除TF0溢出中断标志,加到1s后TF0会被置为1
TR0 = 1; //允许定时器0计时(TF1和TR1是定时器1)
TMOD模式寄存器:
模式1工作图:
TMOD &= 0xF0; //设置定时器模式(低4位清0,高四位不变)
TMOD |= 0x01; //设置定时器模式(最低位置1,高四位不变)
TL0&&TH0
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
中断程序
EA=1:CPU总中断允许位(0—>全部断开)
ET0=1; 中断允许
PT0=0;低优先级
定时器程序直接使用keli5自带程序即可,注意加上EA,ET0,PT0
四.串口
1.串口简介
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了单片机系统的硬件实力。
51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。
2.硬件电路
简单双向串口通信有两根通信线(发送端TXD和接收端RXD)。
TXD与RXD要交叉连接
当只需单向的数据传输时,可以直接一根通信线
当电平标准不一致时,需要加电平转换芯片
- T——transmit(发送);
- X——exchange(交换);
- D——data(数据);
- R——receive(接收);
3.单片机URAT
stc89c52的uart有四种工作模式:0:同步移位寄存器,1:8位uart,波特率可变,2:9位uart,波特率固定,3:9位uart,波特率可变
串口模式图
送数据的过程是:先把要传的数据写入发送缓冲区SBUF,以一定的波特率,通过发送数据源串口TXD,把数据发送出去。接收数据的过程是:数据通过RXD,以一定的波特率,写入接收缓冲区SBUF。
相关寄存器
4.单片机串口收发数据
1.发送数据
1.串口初始化
1:配置串口控制寄存器SCON为0x40(或0x50);
2:配置电源控制寄存器PCON(计算波特率);
3:配置定时器T1(串口通信只能用定时器1,只能使用8位自动重装工作模式),启动定时器T1;
4:禁止定时器T1中断;
1.SCON的配置。
SCON:用于设定串行口的工作方式,这里选择方式1(SM0=0,SM1=1)10位UART(8位数据,1位起始位,1位停止位),波特率可变。REN是允许串行接收控制位,这里是发送,所以不需要置1。SCON=0x40。
TI:发送中断标志位。在方式 0 时,当串行发送第 8 位数据结束时,或在其它方式,串行发送停止位的开始时,由内部硬件使 TI 置 1,向 CPU 发中断申请。 在中断服务程序中,必须用软件将其清 0,取消此中断申请.
RI:接收中断标志位。在方式 0 时,当串行接收第 8 位数据结束时,或在其 它方式,串行接收停止位的中间时,由内部硬件使 RI 置 1,向 CPU 发中断申请。 也必须在中断服务程序中,用软件将其清 0,取消此中断申请。
2.PCON的配置。
PCON:令SMOD=0,其他位不变。PCON&=0x7f。
SMOD:波特率选择位。
当用软件置位SMOD,即SMOD=1,则使串行通信方式1、2、3的波特率加倍;
SMOD=0,则各工作方式的波特率不加倍。复位时SMOD=0。SMOD0:帧错误检测有效控制位。
SMOD0=1,SCON寄存器中的SM0/FE位用于FE(帧错误检测)功能;○ 当SMOD0=0,SCON寄存器中的SM0/FE位用于SM0功能,和SM1一起指定串行口的工作方式。复位时SMOD0=0
3. 配置定时器T1:
TMOD &= 0x0F; //TMOD高四位置0
TMOD |= 0x20; //使用“8位自动重装”模式
波特率的确定
用8位自动重装(方式2)定时器T1的溢出率来产生波特率。
例:波特率为9600,晶振为12Mhz
溢出频率=波特率x16x2=0.3072Mhz
T1溢出一次的时间=1/溢出频率 =3.2552us
计数一次的时间=1/(12M)x12=1us,计数次数=溢出一次的时间/计数一次的时间=3.2552。
初始值就是2^8-3=253,化成16进制就是0xfd,所以初始值TH1=0xfd,重装值TL1=0xfd。
4.禁止定时器T1中断:ET1=0;
2.代码实现
#include <STC89C5xRC.H>
#include "Delay.h"
void UART_inti()//4800bps@11.0592MHz
//初始化寄存器
{
SCON = 0x40;
PCON = 0;
//配置定时器1
TMOD &= 0x0F; //TMOD高四位置0
TMOD |= 0x20; //使用“8位自动重装”模式
TL1 = 0xFA; //设置定时初始值
TH1 = 0xFA; //设置定时重载值
ET1 = 0; //禁止定时器中断
TR1 = 1; //定时器1开始计时
}
void UART_SendByte(unsigned int Byte)
//发送数据
{
SBUF = Byte;
//将数据写入SBUF
while(TI == 0);
TI = 0;//重置
}
unsigned char sec = 0;
void main()
{
UART_inti();
while(1)
{
UART_SendByte(sec);
sec++;
Delay(1000);
}
}
2.接收数据
1:配置串口控制寄存器SCON为0x50;
2:配置电源控制寄存器PCON(计算波特率);
3:配置定时器T1(串口通信只能用定时器1,只能使用8位自动重装工作模式),启动定时器T1;
4:启动总中断和串口中断;
//串口初始化
void UartInit() //4800bps@11.0592MHz
{
PCON &= 0x7F;
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //定时器1高四位置0
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
//开启中断
EA=1; //总中断控制
ES=1; //串口中断
}
//串口发送一个字节数据
void UART_SendByte(unsigned char Byte){
SBUF=Byte;
//检测是否完成
while(TI==0);
TI=0;//TI复位
}
void main()
{
UartInit();
while(1)
{
}
}
//串口中断
void UART_Routine() interrupt 4
{
if(RI==1){
//在程序打开总中断和串口中断只需T1>=1||R1>=1就会触发中断
//串口在接受到数据后,硬件自动置R1=1
P2=SBUF;//显示LED
UART_SendByte(SBUF);//将数据发回电脑
RI=0;//复位
}
}
五.LED点阵屏
1.LED点阵屏原理
8*8 点阵共由 64 个发光二极管组成,且每个发光二极管是放置在行线和列线 的交叉点上,当对应的某一行置 1 电平,某一列置 0 电平,则相应的二极管就亮,如要将第一个点点亮,则 1 脚接高电平 a 脚接低电平,则第一个点就亮了;点亮多个LED灯,即通过快速扫描,产生点灯效果(实则视觉延时)
2.74HC59芯片
左侧矩形为移位寄存器,ser输出1时,当serclk达到上升沿时(即1),向下推移一个数字,当八个数字储存完毕,rclk上升沿锁位打开,八位数字同时进入右侧矩形。
代码设计
sbit RCK=P3^5;
sbit SCK=P3^6;
sbit SER=P3^4;
unsigned char a;
void 74hc59_write(unsigned char b)
{
for(a=0;a<8;a++)
{
SER=b&(0x80>>a);(取出第八位值)
SCK=1;
SCK=0;
}
RCK=1;
RCK=0;
}
3.一些零碎知识
4.LED点阵屏软件设计
void LEDdz_init(()//初始化
{
RCK=0;
SCK=0;
}
void leddz_show(unsigned char column,data)
{
74hc59_write(data);//段选
P0=~(0x80>>column);//位选
Delay(1);//防篡位
P0=0xFF;//清零
}
静态动画
void main()
{
leddz_init()
while(1)
{
leddz_show(0,0x3C);
leddz_show(1,0x42);
leddz_show(2,0xA9);
leddz_show(3,0x85);
leddz_show(4,0x85);
leddz_show(5,0xA9);
leddz_show(6,0x42);
leddz_show(7,0x3C);
}
}
动态动画
unsigned char code Animation[]=
// code 代表数组中的数据放到了FLASH中,而非RAM里,好处是不会占用内存本就不多的RAM太多内存,但在后续操作中无法进行更改
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0x08,0x08,0x08,0xFF,0x00,0x0E,0x15,
0x15,0x15,0x08,0x00,0x7E,0x01,0x02,0x00,
0x7E,0x01,0x02,0x00,0x0E,0x11,0x11,0x0E,
0x00,0x7D,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
}
;
unsigned char i,offset,count;
void main()
{
RCK=0;
SCK=0;
//单片机给电时两个时钟得到高电平值变为1,故须进行初始化清零
while(1)
{
for(i=0;i<8;i++)
{
leddz_Show(i,Animation[i+offset]);
//offset即偏移,每过一段时间offset加一,动画跳一帧
}
count++;
if(count>10)//此处可以使用定时器做
{
offset++;
count=0;
}
if(offset>36)
offset=0;
}
}
六.ds1302时钟
1.ds1302时钟芯片简介
主要的性能指标:
2.ds1302使用
1.控制寄存器
控制寄存器用于存放 DS1302 的控制命令字,DS1302 的CE引脚回到高电平后写入的第一个字节就为控制命令。它用于对 DS1302 读写过程进行控制,格式如下:
2.日历/时钟寄存器
即通过红色框中的地址,控制相关寄存器的读与写
写保护寄存器:当该寄存器最高位 WP 为 1 时,DS1302 只读不写,所以要在往 DS1302 写数据之前确保 WP 为0;
3.BCD码
时钟的寄存器中各位数据均以BCD码形式存储,在读写时间程序中需将十进制转为BCD,BCD转为十进制。
4.DS1302 的读写时序
1、CE:初始化后为低电平, 在整个读写器件,要保持高电平,一次字节读写完毕之后,在进行置低电平;
2、单字节写入:在CE为高电平后,SCLK会输出周期脉冲,每一个上升沿,IO线的数据就会进入控制寄存器,当控制寄存器配置完成(为写入数据,并且地址已给),紧接着的脉冲IO线的数据就会在上升沿进入对应地址的寄存器;(上升沿读入)
3、单字节读出:在CE为高电平后,SCLK会输出周期脉冲,每一个上升沿,IO线的数据就会进入控制寄存器,当控制寄存器配置完成(为读出数据,并且地址已给),紧接着对应地址的寄存器的数据就会在下降沿进入IO线;(下降沿读出)
3.ds1302软件设置
#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
//定义数组,用于存放设定的时间,年月日时分秒星期
unsigned char DS1302_Time[]={23,10, 28,19,00,59,6};//顺序:年月日时分秒星期
/**
*@breaf DS1302初始化
*@param无
*@retval无
*/
void DS1302_Init()
{
DS1302_CE=0;//将使能位置0,低电平;
DS1302_SCLK=0;//将时钟位置0,低电平;
}
/**
*@breaf DS1302单字节写入函数
*@param command:写入控制指令的指令,包含要写入寄存器的地址;
*@param Data:将要写入的数据内容;
*@retval 无
*/
void DS1302_WriteBety(unsigned char command,Data)
{
unsigned char i;
DS1302_CE=1;//使能位置高电平;
for(i=0;i<8;i++)//控制寄存器数据需要通过IO线一个一个写入控制寄存器;低位先写入
{
DS1302_IO=command&(0x01<<i);//相当于把第1--7位置0,只留第0位,如果第0位是0,则为0;反之则为1;
DS1302_SCLK=1;
DS1302_SCLK=0;
}
for(i=0;i<8;i++)//数据写入
{
DS1302_IO=Data&(0x01<<i);//相当于把第1--7位置0,只留第0位,如果第0位是0,则为0;反之则为1;
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_CE=0;
}
/**
*@breaf DS1302单字节读出函数
*@param command:写入控制指令的指令,包含要读出寄存器的地址;
*@retval Data:读出的数据;
*/
unsigned char DS1302_ReadBety(unsigned char command)
{
unsigned i,Data=0X00;
command|=0X01;//写入指令与读出指令只在最后一位相差1,故在此利用或运算消除;
DS1302_CE=1;//使能位置高电平;
for(i=0;i<8;i++)//控制寄存器数据需要通过IO线一个一个写入控制寄存器;低位先写入
{
DS1302_IO=command&(0x01<<i);//相当于把第1--7位置0,只留第0位,如果第0位是0,则为0;反之则为1;
DS1302_SCLK=0;
DS1302_SCLK=1;
}
DS1302_IO=0;
for(i=0;i<8;i++)//数据读出
{
DS1302_SCLK=1;
DS1302_SCLK=0;
if(DS1302_IO)
{Data |=(0X01<<i);}
}
DS1302_CE=0;
return Data;
}
/**
*@breaf 向DS1302内设定时间
*@param无
*@retval无
*/
void DS1302_SetTime()
{
DS1302_WriteBety(DS1302_WP,0x00);//操作 DS1302 之前,关闭写保护,不然指令无法进入控制寄存器;
DS1302_WriteBety(DS1302_YEAR, DS1302_Time[0]/10*16+DS1302_Time[0]%10);//写入年,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_MONTH, DS1302_Time[1]/10*16+DS1302_Time[1]%10);//写入月,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_DATE, DS1302_Time[2]/10*16+DS1302_Time[2]%10);//写入日,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_HOUR, DS1302_Time[3]/10*16+DS1302_Time[3]%10);//写入时,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_MINUTE, DS1302_Time[4]/10*16+DS1302_Time[4]%10);//写入分,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_SECOND, DS1302_Time[5]/10*16+DS1302_Time[5]%10);//写入秒,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_DAY, DS1302_Time[6]/10*16+DS1302_Time[6]%10);//写入星期,并将10进制转化BCD码;
DS1302_WriteBety( DS1302_WP,0x80);//写入结束,开启写保护;
}
/**
*@breaf 读取DS1302内时间
*@param无
*@retval无
*/
void DS1302_ReadTime()
{
unsigned char Temp;//定义变量,用于暂时存储BCD码
Temp=DS1302_ReadBety(DS1302_YEAR);//读取年BCD码;
DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制;
Temp=DS1302_ReadBety(DS1302_MONTH);//读取月BCD码;
DS1302_Time[1]=Temp/16*10+Temp%16;//BCD码转十进制;
Temp=DS1302_ReadBety(DS1302_DATE);//读取日BCD码;
DS1302_Time[2]=Temp/16*10+Temp%16;//BCD码转十进制;
Temp=DS1302_ReadBety(DS1302_HOUR);//读取小时BCD码;
DS1302_Time[3]=Temp/16*10+Temp%16;//BCD码转十进制;
Temp=DS1302_ReadBety(DS1302_MINUTE);//读取分钟BCD码;
DS1302_Time[4]=Temp/16*10+Temp%16;//BCD码转十进制;
Temp=DS1302_ReadBety(DS1302_SECOND);//读取秒BCD码;
DS1302_Time[5]=Temp/16*10+Temp%16;//BCD码转十进制;
Temp=DS1302_ReadBety(DS1302_DAY);//读取星期BCD码;
DS1302_Time[6]=Temp/16*10+Temp%16;//BCD码转十进制;
}
#include <STC89C5xRC.H>//for循环简化
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;
void DS1302_Init()
{
DS1302_CE=0;
DS1302_SCLK=0;
}
void write_ds1302(unsigned char temp)
{
unsigned char i;
for(i=0;i<8;i++)
{
DS1302_SCLK=0;
DS1302_IO=temp&(0x01<<i);
DS1302_SCLK=1;
}
}
void ds1302_writebyte(unsigned char adr,dat)
{
DS1302_SCLK=0;
DS1302_CE=0;
DS1302_CE=1;
write_ds1302(adr);
write_ds1302(dat);
DS1302_CE=0;
}
unsigned char ds1302_readbyte(unsigned char adr)
{
unsigned char i,date=0x00;
DS1302_SCLK=0;
DS1302_CE=0;
DS1302_CE=1;
adr|=0x01;
write_ds1302(adr);
for(i=0;i<8;i++)
{
DS1302_SCLK=1;
DS1302_SCLK=0;
if(DS1302_IO){date|=(0x01<<i);}
}
DS1302_SCLK=0;
DS1302_CE=0;
DS1302_SCLK=1;
DS1302_IO=0;
DS1302_IO=1;
return date;
}
void timewriter()
{
unsigned char i;
ds1302_writebyte(0x8e,0x00);
for(i=0;i<3;i++)
{
ds1302_writebyte(timewrite[i],day[i]/10*16+day[i]%10);
}
ds1302_writebyte(0x8e,0x80);
}
void ds1302_timeread()
{
unsigned char j;
for(j=0;j<3;j++)
{
unsigned char b=0;
b=ds1302_readbyte(timewrite[j]);
day[j]=b/16*10+b%16;
}
}