题目:
掌握DS1302的原理图,记住三个引脚:
sbit SCK = P1^7;
sbit SDA = P2^3;
sbit RST = P1^3;
main.c代码:
一、STC15F2K60S2基础配置、宏定义、类型重定义
#include <STC15F2K60S2.H>
#define Y4C P2=P2&0X1F|0X80
#define Y5C P2=P2&0X1F|0XA0
#define Y6C P2=P2&0X1F|0XC0
#define Y7C P2=P2&0X1F|0XE0
#define Y0C P2=P2&0X1F|0X00
typedef unsigned char u8;
typedef unsigned int u16;
二、定义变量和数组
code u8 Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
//带小数点(高位减去8)
0x40, //0
0x79, //1
0x24, //2
0x30, //3
0x19, //4
0x12, //5
0x02, //6
0x78, //7
0x00, //8
0x10, //9
//符号
0xff, //空白 20
0xbf, //短横线 21
//字母
0x88, //A 22
0x83, //b 23
0xc6, //C 24
0xa1, //d 25
0x86, //E 26
0x8e //F 27
};
code u8 com[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //位选
u8 Seg_index[8] = {0}; //存储数码管要显示的内容的索引
u8 time_DS=0; //用于ds1302的计时
三、预声明
//外部声明,调用其他源文件的函数或者数组、变量
extern unsigned char set_data[7];
extern void ds_init();
extern void ds_get();
// 预声明函数,防止调用顺序影响编译
void init();
void Segment_Display();
void set_index(u8 d0,d1,d2,d3,d4,d5,d6,d7);
void Timer2Init();
四、自定义函数
//系统初始化
void init()
{
Y4C;P0=0XFF;Y0C;
Y5C;P0=0X00;Y0C;
Y6C;P0=0XFF;Y0C;
Y7C;P0=0XFF;Y0C;
}
//动态数码管扫描
void Segment_Display()
{
// 定义一个静态的无符号8位整型变量num,用于记录当前扫描的数码管位
// 静态变量:只在第一次进入函数时进行初始化,后续调用函数时,其值会保留上一次调用结束时的值
static u8 num = 0;
// 消隐操作,避免数码管出现重影
// 关闭当前数码管段选
Y7C;
P0 = 0XFF;
Y0C;
// 位选操作,选择当前要显示的数码管。
// com数组存储了各个数码管的位选信号,通过num索引选择对应的数码管。
Y6C;
P0 = com[num];
Y0C;
// 段选操作,确定当前数码管要显示的数字或字符
// Seg_index数组存储了每个数码管要显示的内容的索引,Seg_Table数组存储了各个数字或字符对应的段码
Y7C;
P0 = Seg_Table[Seg_index[num]];
Y0C;
// 判断是否完成一轮数码管扫描
// 先将num的值自增1,再使用num的当前值与8进行比较,如果相等,说明已经扫描完8个数码管
if(++num == 8)
num = 0;
}
//动态数码管显示索引赋值
void set_index(u8 d0,d1,d2,d3,d4,d5,d6,d7)
{
Seg_index[0] = d0;
Seg_index[1] = d1;
Seg_index[2] = d2;
Seg_index[3] = d3;
Seg_index[4] = d4;
Seg_index[5] = d5;
Seg_index[6] = d6;
Seg_index[7] = d7;
}
//定时器2中断服务程序
void t2int() interrupt 12 //中断入口
{
time_DS++;
Segment_Display(); //定时中断1ms驱动一次数码管
}
//定时器2初始化
void Timer2Init() //1毫秒@12.000MHz
{
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0x20; //设置定时初值
T2H = 0xD1; //设置定时初值
AUXR |= 0x10; //定时器2开始计时
IE2 |= 0x04; //开定时器2中断
EA = 1;
}
五、main函数
main()
{
init();
Timer2Init();
ds_init();
while(1)
{
if(time_DS>=100) //每过100ms读一次
{
time_DS=0;
ds_get();
}
set_index(set_data[2]/10,set_data[2]%10, //小时
21,
set_data[1]/10,set_data[1]%10, //分钟
21,
set_data[0]/10,set_data[0]%10); //秒
}
}
完整代码(方便一键复制):
#include <STC15F2K60S2.H>
#define Y4C P2=P2&0X1F|0X80
#define Y5C P2=P2&0X1F|0XA0
#define Y6C P2=P2&0X1F|0XC0
#define Y7C P2=P2&0X1F|0XE0
#define Y0C P2=P2&0X1F|0X00
typedef unsigned char u8;
typedef unsigned int u16;
code u8 Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
//带小数点(高位减去8)
0x40, //0
0x79, //1
0x24, //2
0x30, //3
0x19, //4
0x12, //5
0x02, //6
0x78, //7
0x00, //8
0x10, //9
//符号
0xff, //空白 20
0xbf, //短横线 21
//字母
0x88, //A 22
0x83, //b 23
0xc6, //C 24
0xa1, //d 25
0x86, //E 26
0x8e //F 27
};
code u8 com[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //位选
u8 Seg_index[8] = {0}; //存储数码管要显示的内容的索引
u8 time_DS=0; //用于ds1302的计时
//外部声明
extern unsigned char set_data[7];
extern void ds_init();
extern void ds_get();
// 预声明函数,防止调用顺序影响编译
void init();
void Segment_Display();
void set_index(u8 d0,d1,d2,d3,d4,d5,d6,d7);
void Timer2Init();
//系统初始化
void init()
{
Y4C;P0=0XFF;Y0C;
Y5C;P0=0X00;Y0C;
Y6C;P0=0XFF;Y0C;
Y7C;P0=0XFF;Y0C;
}
//动态数码管扫描
void Segment_Display()
{
// 定义一个静态的无符号8位整型变量num,用于记录当前扫描的数码管位
// 静态变量:只在第一次进入函数时进行初始化,后续调用函数时,其值会保留上一次调用结束时的值
static u8 num = 0;
// 消隐操作,避免数码管出现重影
// 关闭当前数码管段选
Y7C;
P0 = 0XFF;
Y0C;
// 位选操作,选择当前要显示的数码管。
// com数组存储了各个数码管的位选信号,通过num索引选择对应的数码管。
Y6C;
P0 = com[num];
Y0C;
// 段选操作,确定当前数码管要显示的数字或字符
// Seg_index数组存储了每个数码管要显示的内容的索引,Seg_Table数组存储了各个数字或字符对应的段码
Y7C;
P0 = Seg_Table[Seg_index[num]];
Y0C;
// 判断是否完成一轮数码管扫描
// 先将num的值自增1,再使用num的当前值与8进行比较,如果相等,说明已经扫描完8个数码管
if(++num == 8)
num = 0;
}
//动态数码管显示索引赋值
void set_index(u8 d0,d1,d2,d3,d4,d5,d6,d7)
{
Seg_index[0] = d0;
Seg_index[1] = d1;
Seg_index[2] = d2;
Seg_index[3] = d3;
Seg_index[4] = d4;
Seg_index[5] = d5;
Seg_index[6] = d6;
Seg_index[7] = d7;
}
//定时器2中断服务程序
void t2int() interrupt 12 //中断入口
{
time_DS++;
Segment_Display(); //定时中断1ms驱动一次数码管
}
//定时器2初始化
void Timer2Init() //1毫秒@12.000MHz
{
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0x20; //设置定时初值
T2H = 0xD1; //设置定时初值
AUXR |= 0x10; //定时器2开始计时
IE2 |= 0x04; //开定时器2中断
EA = 1;
}
main()
{
init();
Timer2Init();
ds_init();
while(1)
{
if(time_DS>=100) //每过100ms读一次
{
time_DS=0;
ds_get();
}
set_index(set_data[2]/10,set_data[2]%10, //小时
21,
set_data[1]/10,set_data[1]%10, //分钟
21,
set_data[0]/10,set_data[0]%10); //秒
}
}
ds1302.c完整代码:
一、注意:仔细看注释,有提到哪里需要修改或者添加代码!!!
//添加2个头文件
#include <STC15F2K60S2.H>
#include <INTRINS.H>
//定义引脚
sbit SCK = P1^7;
sbit SDA = P2^3;
sbit RST = P1^3;
//需要自己创建大小为8的时钟数组,并进行初始化
//秒、分钟、小时、日、月、星期、年
unsigned char set_data[7]={59,59,23,0,0,0,0};
void Write_Ds1302(unsigned char temp)
{
unsigned char i;
for (i=0;i<8;i++)
{
SCK = 0;
SDA = temp&0x01;
temp>>=1;
SCK=1;
}
}
//需要修改此代码
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )
{
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
//Write_Ds1302(dat);
//修改为下:
//功能:将一个十进制数转换为 BCD 码,并将其写入 DS1302 芯片。
Write_Ds1302(dat/10<<4 | dat%10);
RST=0;
}
//需要修改此代码
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
//修改:增加两个u8变量,如下
unsigned char dat1,dat2;
unsigned char i,temp=0x00;
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
for (i=0;i<8;i++)
{
SCK=0;
temp>>=1;
if(SDA)
temp|=0x80;
SCK=1;
}
RST=0; _nop_();
SCK=0; _nop_();
SCK=1; _nop_();
SDA=0; _nop_();
SDA=1; _nop_();
//增加三行代码
//功能:将temp十六进制数转换为temp十进制数
//目的:从ds1302读回数据的时候,需要把BCD(看作十六进制数)转换为十进制数
dat1=temp/16;
dat2=temp%16;
temp=dat1*10+dat2;
return (temp);
}
//增加:初始化函数,用于对DS1302实时时钟芯片进行写入初始时间设置
void ds_init()
{
// i用于循环计数,address用于存储DS1302的地址
unsigned char i, address;
// 0x80是DS1302秒寄存器的写地址
address = 0x80;
// 向DS1302的写保护寄存器(地址为0X8E)写入0X00,关闭写保护
// 只有关闭写保护,才能向DS1302的其他寄存器写入数据
Write_Ds1302_Byte(0X8E, 0X00);
// 循环7次,依次设置秒、分、时、日、月、周、年的时间数据
for (i = 0; i < 7; i++)
{
// 调用Write_Ds1302_Byte函数,将set_data数组中的时间数据写入到DS1302的相应寄存器中
// address表示当前要写入的寄存器地址,set_data[i]表示要写入的时间数据
Write_Ds1302_Byte(address, set_data[i]);
// 地址加2,指向下一个时间寄存器的写地址
address = address + 2;
}
// 向DS1302的写保护寄存器(地址为0X8E)写入0X80,开启写保护
// 写入完成后,开启写保护以防止数据被意外修改
Write_Ds1302_Byte(0X8E, 0X80);
}
//增加:读取时间函数,用于从DS1302实时时钟芯片读取当前的时间信息
void ds_get()
{
unsigned char i, address;
// 0x81是DS1302秒寄存器的读地址
address = 0x81;
// 向DS1302的写保护寄存器(地址为0X8E)写入0X00,关闭写保护
Write_Ds1302_Byte(0X8E, 0X00);
for (i = 0; i < 7; i++)
{
// 调用Read_Ds1302_Byte函数,从DS1302的相应寄存器中读取时间数据
set_data[i] = Read_Ds1302_Byte(address);
// 地址加2,指向下一个时间寄存器的读地址
address = address + 2;
}
// 开启写保护
Write_Ds1302_Byte(0X8E, 0X80);
}
学习总结:
1. 外部声明——extern 关键字的作用
在多文件的 C 语言项目中,一个程序可能由多个源文件(.c 文件)组成。当一个源文件需要调用另一个源文件中定义的函数时,就需要使用 extern 关键字来声明这个函数。
例如:
extern unsigned char set_data[7];
extern void ds_init();
extern void ds_get();
易错点:全局变量的外部声明不能再进行初始化!!!!
2、作用定义变量要避开关键字
data是关键字,不能定义为变量名。小技巧:在keil中编写代码时,关键字会变成彩色。
3、预声明——主函数需要在预声明函数,防止调用顺序影响编译
void init();
void Segment_Display();
void set_index(u8 d0,d1,d2,d3,d4,d5,d6,d7);
void Timer2Init();
4、初始化的重要性
只要有初始化函数,都需要放到main函数的while循环前面,进行初始化。
main()
{
init(); //系统初始化
Timer2Init(); //定时器2初始化
ds_init(); //DS1302时钟初始化
while(1);
}
5、数码管动态扫描的消隐逻辑
记住“段选-位选-段选”的逻辑,先关闭所有段选,再选择位选,再打开想要真正显示的段选。
6、静态变量static的用法
在数码管动态扫描时,开头定义了static u8 num=0;
静态变量的作用是:
① 定义了一个变量;
② 对变量进行初始化,但是,只在第一次进入函数时进行初始化,后续调用函数时,其值会保留上一次调用结束时的值。
7、DS1302在main.c中的使用方法
- 外部声明:时钟数组,初始化函数,读时间函数
//外部声明
extern unsigned char set_data[7];
extern void ds_init();
extern void ds_get();
- main函数中调用初始化函数 ds_init();
- 定义一个计数变量time_DS,利用定时器中断服务,不断对time_DS加1(代表1ms)。然后在while循环中,用if判断time_DS的语句,使得每过100ms,调用一次 ds_get(),并且对time_DS清零。
- 数码管显示时钟,调用 set_index() 数码管显示函数,对时钟数组进行显示。
set_index( set_data[2]/10,set_data[2]%10, //小时
21, //短横线的索引值是21
set_data[1]/10,set_data[1]%10, //分钟
21, //短横线的索引值是21
set_data[0]/10,set_data[0]%10); //秒
8、学会了修改和编写ds1302.c文件
具体请看上面ds1302.c代码的注释。