导读:《蓝桥杯单片机组》专栏文章是博主2018年参加蓝桥杯的单片机组比赛所做的学习笔记,在当年的比赛中,博主是获得了省赛一等奖,国赛二等奖的成绩。成绩虽谈不上最好,但至少问心无愧。如今2021年回头再看该系列文章,仍然感触颇多。为了能更好地帮助到单片机初学者,今年特地抽出时间对当年的文章逻辑和结构进行重构,以达到初学者快速上手的目的。需要指出的是,由于本人水平有限,如有错误还请读者指出,非常感谢。那么,接下来让我们一起开始愉快的学习吧。
一、基础理论
1302是变种的SPI,上升沿DS1302写入数据,下降沿DS1302读出数据! 上沿采样,下沿输出。
这里写图片描述
还有,得会查这七个寄存器!
承认第一次自己写蓝桥DS1302底层后不好使,然后也没找到原因。稍作修改了官网的底层后,虽然好使了,但是心里还是有点膈应蓝桥的底层。太多nop,让我们自己写怎么可能写的出来嘛!!!(我倒是觉得它的那个底层俨然是根据逻辑分析的波形进行的!)
昨晚(3月17日)又重新推到重写了底层,又辅助逻辑分析仪和datasheet,在即要放弃之际,没想到找到了答案!
受益匪浅,感触良多,特更此文,以作分享。
(同时DS1302的显示也都摒弃了1602,直接显示到数码管,也更符合比赛的要求!)
底层能用的两种方法:
1、加一句话,在DS1302SingleRead()
以及DS1302BurstRead()
后加上一句DS1302_IO = 0
!
2、在DS1302_IO对应的引脚外面上拉一个4.7K
左右的电阻。
接上拉电阻,再拔下的效果。
DS1302参考电路
/*
*******************************************************************************
* 文件名:ds1302.c
* 描 述:
* 作 者:CLAY
* 版本号:v1.0.0
* 日 期:
* 备 注:
*
*******************************************************************************
*/
#include "config.h"
#include "ds1302.h"
void DS1302ByteWrite(u8 dat)
{
u8 mask;
DS1302_IO = 1;
for(mask=0x01; mask!=0; mask<<=1)
{
if((dat&mask) != 0)
DS1302_IO = 1;
else
DS1302_IO = 0;
DS1302_CK = 1;
DS1302_CK = 0;
}
DS1302_IO = 1; //写完之后确保释放IO总线
}
u8 DS1302ByteRead()
{
u8 mask, dat=0;
for(mask=0x01; mask!=0; mask<<=1)
{
if(DS1302_IO)
{
dat |= mask;
}
DS1302_CK = 1;
DS1302_CK = 0;
}
return dat;
}
void DS1302SingleWrite(u8 reg,u8 dat)
{
DS1302_CE = 1;
DS1302ByteWrite((reg<<1) | 0x80);
DS1302ByteWrite(dat);
DS1302_CE = 0;
}
u8 DS1302SingleRead(u8 reg)
{
u8 dat;
DS1302_CE = 1;
DS1302ByteWrite((reg<<1) | 0x81);
dat = DS1302ByteRead();
DS1302_CE = 0;
DS1302_IO = 0;//单字节读必须加的!
return dat;
}
void DS1302BurstWrite(u8 *dat)
{
u8 i;
DS1302_CE = 1;
DS1302ByteWrite(0xBE);
for(i=0; i<7; i++)
{
DS1302ByteWrite(*dat++);
}
DS1302_CE = 0;
}
void DS1302BurstRead (u8 *dat)
{
u8 i;
DS1302_CE = 1;
DS1302ByteWrite(0xBF);
for(i=0; i<7; i++)
{
dat[i] = DS1302ByteRead();
}
DS1302_CE = 0;
DS1302_IO = 0;//突发读必须加
}
void GetRealTime(struct sTime *time)
{
u8 buf[8];
DS1302BurstRead(buf);
time->year = buf[6] + 0x2000;
time->mon = buf[4];
time->day = buf[3];
time->hour = buf[2];
time->min = buf[1];
time->sec = buf[0];
time->week = buf[5];
}
void SetRealTime(struct sTime *time)
{
u8 buf[8];
buf[7] = 0;
buf[6] = time->year;
buf[4] = time->mon;
buf[3] = time->day;
buf[2] = time->hour;
buf[1] = time->min;
buf[0] = time->sec;
buf[5] = time->week;
DS1302BurstWrite(buf);
}
void InitDS1302()
{
struct sTime InitTime[] = { //2018年3月1日 星期四 9:44:00
0x18, 0x03, 0x01, 0x10, 0x40, 0x00, 0x04
};
DS1302_CE = 0;
DS1302_CK = 0;
DS1302SingleWrite(7, 0x00); //撤销写保护以允许写入数据
SetRealTime(&InitTime);
}
二、动手实验
代码下载可以到我的Github<传送门>。
2.1、DS1302单次读写操作模式
2018 年 2 月 22 号星期四 12 点 30 分 00 秒这个时间写到DS1302 内部,让 DS1302 正常运行,然后再不停的读取 DS1302 的当前时间,并显示在我们的液晶屏上。
单字节读的指令 (reg<<1)|0x81
单字节写的指令 (reg<<1)|0x80
2.2、DS1302 的 的 BURST模式
仔细想一下上节的程序:
定时器时间到了 200ms 后,我们连续把 DS1302
的时间参数的 7 个字节读了出来。但是不管怎么读,都会有一个时间差,在极端的情况下就会出现这样一种情况:假如我们当前的时间是 00:00:59
,我们先读秒,读到的秒是 59,然后再去读分钟,而就在读完秒到还未开始读分钟的这段时间内,刚好时间进位了,变成了 00:01:00 这个时间,我们读到的分钟就是 01,显示在液晶上就会出现一个 00:01:59
,这个时间很明显是错误的。出现这个问题的概率极小,但却是实实在在可能存在的。
所以这个时候就有了BURST模式
,BURST模式就是一次把7 个字节全部读或写到缓冲区,然后再来进行后续操作!
就是实现为:将要写的5位地址全部写1,即 读操作用 0xBF
, 写操作用 0xBE
, 这样的指令送给 DS1302 之后,它就会自动识别出来是 burst 模式。
2.3、百尺竿头更进一步,结构体的应用实例
上面程序的实现思路,大概是我们把DS1302的7个字节的时间放到一个缓冲数组中,然后把数组中的值稍作转换显示到液晶上,这里就存在一个小问题,DS1302
时间寄存器的定义并不是我们常用的“年月日时分秒”
的顺序,而是在中间加了一个字节的“星期几”
,而且每当我要用这个时间的时候都要清楚的记得数组的第几个元素表示的是什么,这样一来,一是很容易出错,二是程序的可读性不强。
当然你可以把每一个元素都定一个明确的变量名字
,这样就不容易出错也易读了,但结构上却显得很零散了。于是,我们就可以用结构体来将这一组彼此相关的数据做一个封装
,它们既组成了一个整体,易读不易错,而且可以单独定义其中每一个成员的数据类型,比如说把年份用 unsigned int 类型,即 4 个十进制位来表示显然比 2 位更符合日常习惯,而其它的类型还是可以用 2 位来表示。
struct sTime { //日期时间结构体定义
unsigned int year;
unsigned char mon;
unsigned char day;
unsigned char hour;
unsigned char min;
unsigned char sec;
unsigned char week;
};
下面以一个电子钟实例来体会上面所言的BURST读写
以及定义结构体
的方便。
电子钟1602显示的实现注意要点:(保留)
1、lcd1602.c
中又添加了两个函数叫做LcdOpenCursor()
以及LcdCloseCursor()
,其实还是两条指令的区别,0x0C
- 关闭光标。0x0F
- 开启光标!
2、加入了结构体,所以我们初始化的时候要注意,自己定义的顺序。
struct sTime InitTime[] = { //2018年3月1日 星期四 9:44:00
0x18, 0x03, 0x01, 0x10, 0x40, 0x00, 0x04
};
3、SetIndex
时间设置索引标志,当它为0的时候表示不处于时间设置状态,当他不为0,表示位于设置某位时间的状态。根据程序我们需要设置年、月、日、时、分、秒的高位和低位,所以它的取值范围是0~12
(闭区间)。
4、程序还是采用模块化的编程思想,写程序的时候注意先实现大块的内容,然后再去实现具体的函数细节!
5、另外注意,LeftShiftTimeSet()
和RightShiftTimeSet()
的条件必须是 setIndex !=0
才能设置!!!
void RightShiftTimeSet()
{
if(setIndex != 0)
{
if(setIndex < 12)
setIndex++;
else
setIndex = 1;
RefreshSetShow();
}
}
void LeftShiftTimeSet()
{
if(setIndex != 0)
{
if(setIndex > 1)
setIndex--;
else
setIndex = 12;
RefreshSetShow();
}
}
小结:本篇文章主要介绍了单片机学习中的一个重要模块:DS1302。从基础理论到试验以及试验踩坑,都有涉及。在该部分也并没有太难的知识点,多多练习该模块对比赛大有裨益。
希望大家多多支持我的原创文章。如有错误,请大家及时指正,非常感谢。