【聚沙成塔 | 4T网单片机练习】数码管DS1302实时时钟显示

题目:

掌握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代码的注释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值