os:由于水平很菜,本篇笔记对外设原理的讲解都十分粗略,错漏很多,请大家批判地阅读学习哦
模块化编程
模块化编程就是把不同外设的驱动代码分装在不同的.c文件中,让我们的主函数更加简洁
随着学习的外设不断增多,模块化能使主函数更简洁,提高驱动代码的可移植性,提高编程的效率
- .c文件:函数、变量的定义
- .h文件:可被调用的函数、变量的声明,注意防重复定义
注意调用时,安装包中的文件用#include<**.h>
,自定义的文件用#include"**.h"
注意
- 任何自定义的变量、函数在调用前必须有定义或声明(同一个.c)
例如在独立按键模块中需要用到delay函数,那就必须在开头加上#include "Delay.h"
- 使用到的自定义函数的.c文件必须添加到工程参与编译
- 使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)
矩阵键盘
连接方式
在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式
矩阵扫描
矩阵键盘扫描(输入扫描)
原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果
特性:节省I/O口
代码如下:
#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;
}
定时器
定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成
定时器作用:
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度
定时器框图
定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行
工作模式1框图:
SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHz
寄存器
- 寄存器是连接软硬件的媒介
- 在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式
- 寄存器相当于一个复杂机器的“操作按钮”
定时器0寄存器配置如下:
#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;
}
中断系统
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
中断号:
中断寄存器:
定时器中断函数模板
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000)//设定1ms进入一次
{
//此处写指令
T0Count=0; //回零
}
}
DS1302
DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能
内部结构
-
晶振:32.768Hz,提供稳定的1Hz脉冲,用于高精度计时
-
实时时钟(RTC):内部寄存器(31*8RAM),存储时间数据(年月日时分秒)
-
CE:置一使能I/O,SCLK
-
I/O,SCLK:对时间数据进行读写访问
初始化:
/**
* @brief DS1302初始化
* @param 无
* @retval 无
*/
void DS1302_Init(void)
{
DS1302_CE=0;
DS1302_SCLK=0;
}
任务:
1.在哪 写入 什么
2.在哪 读出 什么
地址命令字:1byte,决定在哪,读还是写
//寄存器写入地址/指令定义
//提高代码可读性
#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//取消写保护
//只要将末位置1就是读的地址,不需要定义两次
时序图
读写实操
- 整个读写过程中,CE保持置一,完成后置0
- SCLK给固定的时钟,I/O给数据(地址命令字)
SCLK上升沿写入数据,下降沿读出数据
地址命令字写入,这一步读写操作一致
作用:决定读写与位置
方法:写第一位(R/W),SCLK给一个上升沿
接下来每写一位,SCLK给一个上升沿
接下来读写的操作有所不同
写:SCLK上升沿写
读:SCLK下降沿读
最后:SCLK时钟置零,CE置零
代码如下:
/**
* @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;
Command|=0x01; //将指令转换为读指令
DS1302_CE=1;
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){Data|=(0x01<<i);}
}
DS1302_CE=0;
DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错
return Data;
}
这样我们就实现了与DS1302的读写交互
为了使时间的读取和设置更加方便,我们再定义一个数组存储时间数据
//时间数组,索引0~6分别为年、月、日、时、分、秒、星期,设置为有符号的便于<0的判断
char DS1302_Time[]={19,11,16,12,00,00,6};
这样一来,我们在主函数中可以直接在数组中读写时间,只需要数组与芯片实时同步数据。
注意:由于DS1302内部用BCD码存储数据,读写时要注意转换
BCD码用4位二进制数来表示1位十进制数
例:0001 0011表示13,1000 0101表示85,0001 1010不合法
在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
BCD码转十进制:DEC=BCD/1610+BCD%16; (2位BCD)
十进制转BCD码:BCD=DEC/1016+DEC%10; (2位BCD
将数组时间数据写入芯片:
/**
* @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);
}
将芯片时间数据同步给数组:
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;
}
第三次任务
任务和视频中讲解的例题很相似,很多地方都参考了视频。
最大的感受是:一定要边写边调试。
led,数码管,LSD602都是很好的调试工具
比如,要测试从main函数跳转到指定模式灵不灵,可以在对应的子函数里点个灯,灯亮了就说明跳转正常;
再比如,要看按键检测灵不灵,可以用数码管显示按下按键的键码。
还有几个值得注意的点:
1.不要在主函数主函数开头用DS1302_SetTime();
,这样时钟芯片内部就提前开始工作了,应该每次进入计时模式前用
2.关于闪烁,需要1s和500ms两个周期。只需要设定定时器250ms进入一次中断,然后用两种不同的判断
unsigned char flashflag//定义标志变量;
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=250)//每250ms进入一次
{
T0Count=0;
flashflag++;
if(flashflag==4)flashflag=0;
}
}
if(flashflag>=2){}//以1s为周期的判断
if(flashflag%=2){}//以500ms为周期的判断
3.键值kn
是不断扫描更新的;mode,select
这两个变量功能相似,都是为了保存最后一次按下按键的键值。注意在调节模式的加减操作中,只要加/减一次,所以用键值判断;判断闪烁位(加减位)时用则用select判断。
主文件代码:
#include <REGX52.H>
#include "DS1302.h"
#include "nixie.h"
#include "MatrixKey.h"
#include "Delay.h"
#include "Timer0.h"
unsigned char kn,mode=66,select,flashflag;
//调节模式
void set()
{
nixie(3,10);nixie(6,10);
//加减并选位
if(kn==1){DS1302_Time[3]++;select=3;}
if(kn==2){DS1302_Time[4]++;select=4;}
if(kn==3){DS1302_Time[5]++;select=5;}
if(kn==5){DS1302_Time[3]--;select=3;}
if(kn==6){DS1302_Time[4]--;select=4;}
if(kn==7){DS1302_Time[5]--;select=5;}
//越界判断
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(DS1302_Time[3]<0){DS1302_Time[3]=59;}
if(DS1302_Time[4]<0){DS1302_Time[4]=59;}
if(DS1302_Time[5]<0){DS1302_Time[5]=59;}
//闪烁
//时
if(select==3&&flashflag%2==0)
{nixie(1,11);nixie(2,11);}
else
{
nixie(1,DS1302_Time[3]/10);
nixie(2,DS1302_Time[3]%10);
}
//分
if(select==4&&flashflag%2==0)
{nixie(4,11);nixie(5,11);}
else
{
nixie(4,DS1302_Time[4]/10);
nixie(5,DS1302_Time[4]%10);
}
//秒
if(select==5&&flashflag%2==0)
{nixie(7,11);nixie(8,11);}
else
{
nixie(7,DS1302_Time[5]/10);
nixie(8,DS1302_Time[5]%10);
}
}
//计时模式
void show()
{
DS1302_ReadTime();
//1ms为周期闪烁
if(flashflag>=2){nixie(3,10);nixie(6,10);}
nixie(1,DS1302_Time[3]/10);
nixie(2,DS1302_Time[3]%10);
nixie(4,DS1302_Time[4]/10);
nixie(5,DS1302_Time[4]%10);
nixie(7,DS1302_Time[5]/10);
nixie(8,DS1302_Time[5]%10);
}
void main()
{
Timer0Init();//定时器初始化
DS1302_Init();//时钟芯片初始化
while(1)
{
if(mode==66)
{
//1ms为周期闪烁
if(flashflag>=2)
{nixie(3,10);nixie(6,10);}
nixie(1,DS1302_Time[3]/10);
nixie(2,DS1302_Time[3]%10);
nixie(4,DS1302_Time[4]/10);
nixie(5,DS1302_Time[4]%10);
nixie(7,DS1302_Time[5]/10);
nixie(8,DS1302_Time[5]%10);
}
kn=MatrixKey();//按键扫描
//保存键值
if(kn==4)
{
mode=1;
}
if(kn==8)
{
mode=2;
DS1302_SetTime();//数组写入芯片,计时开始
}
模式选择
switch(mode)
{
case 1:set();break;
case 2:show();break;
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=250)//每250ms进入一次
{
T0Count=0;
flashflag++;
if(flashflag==4)flashflag=0;
}
}