前言
单片机资源数据包_2023(点击下载)
一、DS1302的驱动与功能
1.DS1302的驱动
2.DS1302的功能
3.DS1302的电路连接
二、关于DS1302
1.DS1302的寄存器
2.补充:BCD码
3.代码实现
三、测试代码
本系列文章意在帮助各位正在准备蓝桥杯单片机组的同学,提供一个参考与指南,但是所有指南的前提是,默认你已经有单片机基础,本系列文章会提供本人对蓝桥杯单片机组编程方面的一些源码实现。当然,或许与你写代码的style完全不想同,那咱们也可以彼此相互交流各自的优缺点,或许你可能只是一个小白,就是想来抄一些可供借鉴的代码,这些都不重要,重要的是能给正在准备单片机组的同学提供到一些帮助。
此外代码可能有写的不完善的地方,但是每一个代码都是经过测试可行之后才发出来的,不敢保证十全十美,但是跑起来应该没问题。
此外,比赛时会提供一个单片机资源数据包,里面的内容比较多,这里只放一个下载链接,是2023年省赛是提供的单片机资源数据包(今年才2024,已经是最新的资源数据包了),正文中会直接使用资源数据包内的资料:
关于此资源数据包还得补充两句,他的3-底层驱动代码参考与往些年不同(但其实差异也很小,这里不再赘述),而且还是快比赛时才公布的这个资源数据包。也就是说,在写底层驱动代码时,我都是以2023年的资源数据包为基础写的,如果你使用的不是2023年的资源数据包的话,可能会跟我使用的底层驱动不完全相同,不过影响也不大。
下图为单片机资源数据包_2023内的所有资源,其中最关键的就是3-底层代码驱动以及SCH_硬件原理图V30了,当然共阳数码管段码表也十分常用,不过一般也就新写一个项目时,会把里面的东西CV到main函数里,然后这个文件就用不着了。
单片机资源数据包_2023中给出来了ds1302驱动的底层代码:
/* # DS1302代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
//
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);
RST=0;
}
//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
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_();
return (temp);
}
与其他驱动代码相同,我们直接把这个文件加入到项目中并点击编译,编译器会报错,为了使编译器不报错,我们需要先对驱动代码进行完善:添加相应的头文件以及引脚定义,新建并添加ds1302.h的头文件,方便我们引用。
DS1302的原理图如下:
这里需要简单提一点,我们在ds1302.c文件中定义三个引脚,分别是:SCK,SDA和RST,但是从DS1302的连接图上只有SCL,I/O和RST,不难理解,其实I/O引脚就是我们要找的SDA,所以我们需要在ds1302.c文件前添加如下代码:
#include
#include
#include "ds1302.h"sbit SDA=P2^3;
sbit RST=P1^3;
sbit SCK=P1^7;
并且新建ds1302.h文件,ds1302.h的内容为空,代码如下:
#ifndef _DS1302_H_
#define _DS1302_H_
#endif
当然,做到这里我们只完成了第一步:点击编译时让编译器不报错,但是此时还是会有一些警告,这些警告是说已经定义的函数未被调用,暂时可以不用去管,因为我们还没写完呢。
DS1302是一款实时时钟(RTC)芯片,由美国达拉斯半导体(Dallas Semiconductor)公司生产。它被广泛应用于各种电子设备中,如数字时钟、计时器、温度计等。
DS1302采用串行接口与控制器通信,可以通过三线串行总线(Data、Clock、RST)与微控制器相连。它内部包括了一个时钟电路、一个时钟频率发生器、一个时钟计数器、静态RAM存储器和控制逻辑电路。
DS1302的时钟电路可以提供给外部系统一个稳定的时钟信号,其精度高,误差非常小。时钟频率发生器可以产生不同的时钟频率,以适应不同的应用需求。时钟计数器可以记录当前的时间和日期,包括年、月、日、时、分、秒等信息。
DS1302还具有一个静态RAM存储器,用于存储温度补偿系数、控制寄存器、时钟计数器的时间和日期等数据。通过串行通信接口,可以读写这些数据。
DS1302具有低功耗特性,工作电流低,非常适合用于电池供电的系统。此外,它还具有防止数据丢失的特性,可以在断电情况下保持时间和日期的准确性。
总之,DS1302是一款功能强大、稳定可靠的实时时钟芯片,广泛应用于各种电子设备中,提供准确的时间和日期信息。
这一部分内容蓝桥杯考试不会考,但是这里还是简单介绍一下:
DS1302是由三根线与单片机连接并传输数据的,虽然和SPI总线类似,也是由三根线传输数据的,但是DS1302的三根线并不是我们所说的SPI总线传输,它只能叫做三总线传输。
DS1302的电路连接示意图:
很明显的可以看到DS1302有两个电源正极,其中一个电源正极连接Vcc,另一个则是连接了一个电池。
其实,正常的DS1302在低功耗模式完全可以由纽扣电池来供电,我们的手表等也往往是采用纽扣电池来供电的。一般学习用的板子上的DS1302为了方便处理,会提供外部供电(由板子上的电路供电)以及一个电子,使得我们再给板子上电之后(不管操不操作DS1302),DS1302会使用外部电源供电,在给板子断电之后,DS1302又会使用纽扣电池供电,从而形成类似于“掉电不丢失”的现象,当然,其实在这个过程中DS1302并没有断电。
从蓝桥杯的原理图可以看出,DS1302右上角的VCC1连接的是N BATTERY网络(字面意思就是电池供电的网络),但是实际上并没有这个网路,也就是说DS1302并没有纽扣电池给它供电,这点从板子上也能看出来,根本没有装纽扣电池,所以只要板子一断电,DS1302的数据就停了(相当于一个钟表不转了,停在了某一个时刻,但是数据不会消失)。
从刚才的原理图中我们还可以看到,DS1302连接了一个32.768的晶振,在板子上大概是一个细圆柱形,银白色的东西。
DS1302使用32.768KHz的晶振是因为这个频率是非常稳定的,并且具有重要的特性。
首先,32.768KHz频率是实时时钟(RTC)系统中常用的标准频率。它是2的15次方,可以方便地与RTC芯片的计数器进行配合。通过精确的频率划分,能够实现秒、分、时、日、月和年的计数,从而精确地获取时间和日期信息。
DS1302的时间数据都存储在寄存器当中,我们在更新时间数据或者从DS1302中读取时间时,其实都是在对寄存器操作,写入或者读取寄存器内的信息。对 DS1302的操作就是对其内部寄存器的操作, DS1302内部共有12个寄存器,其中有:7 个寄存器与日历、时钟相关,存放的数据位为 BCD 码形式。此外, DS1302 还有年份寄存器、控制寄存器、充电寄存器、时钟突发寄存器及与 RAM 相关的寄存器等。时钟突发寄存器可一次性顺序读写除充电寄存器以外的寄存器。
下面列举几个常用寄存器及其地址:
时钟日历包含在 7 个读/写寄存器内,读/写寄存器中的数据是二——十进制的 BCD 码。
这个表格的第一列写的是读取某一行的寄存器时,需要发送的地址的值。
第二列写的是写入某一行的寄存器时,需要发送的地址的值。
往某个地址写入值的过程就是在控制寄存器的过程,这个“往某个地址写入值”与EEPROM存数据时类似,后续会有代码演示,这是不在赘述。
与之前讲的类似,每个寄存器的八位地址中,最后以为表示读/写为,为1时表示读操作。为0时表示写操作,比如读取秒寄存器发送的是0x81,也就是1000 0001,而写时则发生0x80,也就是1000 0000.
对于秒寄存器(第一行),BIT7位表示暂停,BIT7为1时时钟暂停,进入低功耗模式,为0时正常工作。BIT6到BIT4为秒的10位数,BIT3到BIT0表示秒的个位数字。比如我们想给秒存入59,也就是当前时间是59秒,则BIT6到BIT4为5,转化为二进制就是0101,BIT3到BIT0的值为9,转化为二进制就是1001,则数据位就是0101 1001.
第二到七行分别表示分钟,小时,日,月,星期,年。表示方法与秒类似。
关于第三行小时,常用的计时方法有24小时计时还有12小时计时。当BT7为1时表示12小时计时,为0时表示24小时计时。相应的,如果是12小时计时的话,小时的十位只有0或者1,需要一位二进制数就可以表示,如果24小时计时的话十位数是0 1 或者2,需要两位二进制来表示。如果是12小时计时的话,BIT6表示小时的十位,BIT5就表示AM/PM,BIT5=0:AM,BIT5=1PM;如是24小时计时的话,BIT6和BIT5共同表示小时的十位。
第八行是写保护寄存器,BIT也就是WP为0是写保护位,对时钟/日历寄存器或 RAM 进行写操作之前, WP 必须为 0, 当 WP 为高电平的时候,不能对任何时钟/日历寄存器或 RAM 进行写操作。
除此之外,我们还发现,这些寄存器上下两个之间刚好相差2,这也方便了我们操作。
之前提到,存入DS1302的数据是以BCD码的方式存储的,这里介绍一下BCD码与十进制的转化:
1. 十进制转BCD:将十进制数的每一位数值转换为对应的BCD码。
例如,将十进制数567转换为BCD码:
- 个位数7转换为BCD码0111
- 十位数6转换为BCD码0110
- 百位数5转换为BCD码0101
则567的BCD码为0101 0110 0111。
2. BCD转十进制:将BCD码的每一位数值组合成对应的十进制数值。
例如,将BCD码0101 0110 0111转换为十进制数:
- 个位数的BCD码为0111,数值为7
- 十位数的BCD码为0110,数值为6
- 百位数的BCD码为0101,数值为5
则BCD码0101 0110 0111对应的十进制数为567。
需要注意的是,BCD码是以4位二进制数表示一个十进制数位,因此在转换过程中需要保证每一位的BCD码不超过4位。如果在转换过程中出现超过4位的BCD码,需要进行修正。
我们在使用DS1302时只需要进行两个操作,一个是写入,一个是读取,而写入时间的操作我们往往只需要上电进行一次,所以我们也可以把写入操作称之为对DS1302的初始化。
我们把时间数据存到一个数组里,方便我们读取和写入。
具体代码如下:
unsigned char Time[6]={55,59,23,1,1,1};//定义一个数组,储存秒 分 时 日 月 星期数据
//初始化DS1302,也就是写入DS1302
void ds_init(void)
{
unsigned char add;//定义一个变量用于暂时存储地址
unsigned char i=0;
add=0x80;//0x80是秒的地址,下边分 时等的地址以此加2即可。
Write_Ds1302_Byte(0x8E,0x00);//关闭写保护,使得后续可以正常写入数据
for(i=0;i<6;i++)//依次写入秒分时日月星期,如果需要写年的话,可以把Time数组加长,并把这里的6改为7
{
Write_Ds1302_Byte(add,(Time[i]/10<<4)|(Time[i]%10));//写入数据,写之前需要将Time转化为BCD码
add+=2;//地址加2,对下一个寄存器进行操作
}
Write_Ds1302_Byte(0x8E,0x80);//开写保护
}
//读DS1302,读取到的数据直接存到Time数组中
void read_ds1302(void)
{
unsigned char add;//定义一个变量用于暂时存储地址
unsigned char i=0;
unsigned char dat=0;
add=0x81;//0x81是秒的地址,下边分 时等的地址以此加2即可。
for(i=0;i<6;i++)//依次读取秒分时日月星期
{
dat=Read_Ds1302_Byte(add);//读取数据到dat内
Time[i]=dat/16*10+dat%16;//将dat转化为十进制存入Time数组
add+=2;//地址加2,对下一个寄存器进行操作
}
}
这里写一个小代码,对刚才的功能进行测试与总结,主要完成以下功能:
1.上电关闭所以LED灯
2.初始化DS1302的数据,为23时,59分55秒。
3.读取DS1302的数据,并将其显示到数码管上,显示格式如下:
数码管第一二位显示小时
数码管第三位显示“-”
数码管第四五位显示分钟
数码管第六位显示“-”
数码管第七八位显示秒
main.c
#include
#include
#include "ds1302.h"
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xBF
};//共阳极断码表,不用记,考试时会提供,直接CV过来即可
unsigned char LED_Num=0xFF;//用来记录当前LED的状态
unsigned char ULN=0x00;//用来记录当前ULN的值,ULN主要控制蜂鸣器和继电器,也会用来控制电机
//LED有关的三个宏函数的思路都是,先更新LED_Num的值,然后将P0=LED_Num,再然后开关一次LED灯的锁
//存器,以更新数据
//点亮第x的LED灯,将LED_Num中第x位置为0(因为是共阳极LED灯)关闭同理,需将第x位置为1
#define LED_ON(x) LED_Num&=~(0x01<
ds1302.c
/* # DS1302代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include
#include
#include "ds1302.h"
sbit SDA=P2^3;
sbit RST=P1^3;
sbit SCK=P1^7;
unsigned char Time[6]={55,59,23,1,1,1};//定义一个数组,储存秒 分 时 日 月 星期数据
//
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);
RST=0;
}
//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
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_();
return (temp);
}
//初始化DS1302,也就是写入DS1302
void ds_init(void)
{
unsigned char add;//定义一个变量用于暂时存储地址
unsigned char i=0;
add=0x80;//0x80是秒的地址,下边分 时等的地址以此加2即可。
Write_Ds1302_Byte(0x8E,0x00);//关闭写保护,使得后续可以正常写入数据
for(i=0;i<6;i++)//依次写入秒分时日月星期,如果需要写年的话,可以把Time数组加长,并把这里的6改为7
{
Write_Ds1302_Byte(add,(Time[i]/10<<4)|(Time[i]%10));//写入数据,写之前需要将Time转化为BCD码
add+=2;//地址加2,对下一个寄存器进行操作
}
Write_Ds1302_Byte(0x8E,0x80);//开写保护
}
//读DS1302,读取到的数据直接存到Time数组中
void read_ds1302(void)
{
unsigned char add;//定义一个变量用于暂时存储地址
unsigned char i=0;
unsigned char dat=0;
add=0x81;//0x81是秒的地址,下边分 时等的地址以此加2即可。
for(i=0;i<6;i++)//依次读取秒分时日月星期
{
dat=Read_Ds1302_Byte(add);//读取数据到dat内
Time[i]=dat/16*10+dat%16;//将dat转化为十进制存入Time数组
add+=2;//地址加2,对下一个寄存器进行操作
}
}
ds1302.h
#ifndef _DS1302_H_
#define _DS1302_H_
void ds_init(void);
void read_ds1302(void);
#endif
其实,学到这里几乎已经可以开始上手省赛题目了,剩下的NE555,超声波和串口,省赛考的比较少,但是最近几年省赛题目逐渐变难,这些模块也开始出现在省赛题目了。
后续会带大家逐步深入的分享蓝桥杯比赛的代码