🚀write in front🚀
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝💬本系列哔哩哔哩江科大51单片机的视频为主以及自己的总结梳理📚
前言:
本文是根据哔哩哔哩网站上“江协科技51单片机”视频的学习笔记,在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技51单片机教学视频和链接中的内容。
引用:
51单片机入门教程-2020版 程序全程纯手打 从零开始入门_哔哩哔哩_bilibili
c51语言变量语句意思,C51中循环语句-CSDN博客
DS1302引用:
DS1302时钟 ---- 自学笔记_ds1302时寄存器地址-CSDN博客
DS1302(实时时钟芯片)_keil5ds1302-CSDN博客
DS1302时钟(实现时钟案例)_ds1302实现实时时钟的程序设计流程-CSDN博客
正文:
0. 🌿概述
在淘宝上购买了江协科技51单片机开发板套件(普中科技STC51单片机A2型号),就上在上一篇博文里说的自己计划学习下江协科技51单片机开发教程,通过STC51单片机这种MCU这种贴近于裸机的开发来增加对于系统硬件层面知识的了解和掌握。
术语和缩略语:
缩写 | 全称 | 说明 |
RTC | Real Time Clock | 实时时钟 |
1. 🚀DS1302实时时钟芯片
DS1302实时时钟芯片引脚定义如下
引脚 | 描述 |
VCC2 | 双供电配置中的主电源供应管脚 |
VCC1 | VCC1 连接到备用电源,在主电源失效时保持时间和日期数据 |
X1 | 接外部晶振 32.768KHz |
X2 | 接外部晶振 32.768KHz |
GND | 接地 |
SCLK | 串行时钟 |
I/O | 输出输出 |
CE | 使能控制位 |
🍎CE使能位控制对DS1302时间寄存器的读写,当CE=0时对寄存器的读写无效。注意CE为并不影响DS1302的计时部分,计时CE=0 DS1602的计时部分也会正常计时。
DS1302内部模块框图
DS1302命令字:
- MSB7:第7位必须为1
- MSB6:第6位为0时表示实时时钟数据,为1表示内部RAM数据。
- MSB5~MSB1:共5位表示寄存器地址
- MSB0:第0位为0表示写,为1表示读。
DS1302单字节读/写时序:
- 🧃单字节读指令时序:
🐳 CE为1高电平,使能DS1302读写。
🐳 SCLK串行时钟每次上升沿,I/O口上的数据一位移动到DS1302的移位寄存器中,I/O发送数据时先发送LSB最低位。SCLK 经过8个时钟上升沿之后,DS1302命令字传输完成。
🐳 接下来,在SCLK的时钟下降沿DS1302将命令字中要读取的指定地址的寄存器内容的一位送到I/O,SCLK每一次时钟下降沿指定要读取寄存器的下一位输出到I/O,经过8个时钟下降沿之后,要读取指定地址的寄存器里的值输出完成。DS1302寄存器读取时LSB先输出。
- 🧃单字节写指令时序:
🐳 CE为1高电平,使能DS1302读写。
🐳 SCLK串行时钟每次上升沿,I/O口上的数据一位移动到DS1302的移位寄存器中,I/O发送数据时先发送LSB最低位。SCLK 经过8个时钟上升沿之后,DS1302命令字传输完成。
🐳 接下来,要写入的数据的D0位写到I/O,SCLK串行时钟每次上升沿,I/O口上的数据一位移动到DS1302的移位寄存器中,SCLK 经过8个时钟上升沿之后要写入的数据 D0~D7写入到寄存器完成。
注意:
😎,SCLK时钟一直是由单片机控制输出的,DS1302按照SCLK时钟的时序从I/O口读取数据到移位寄存器,或者从移位寄存器输出数据到I/O口。
DS1302的寄存器地址定义
2. 🚀开发板DS1302电路原理图
3. 隔一次读取DSS1302数据错误问题
注意其中一个关键问题:
🥸上面引用链接里的读取 DS1302 时钟的问题现象和我自己实验测试遇到的问题一样,每隔一次读取出来的值是正确,然后下一次读取出来的值是 0xFF,再一次读取出来的值正确,再下一次读取出来值是 0xFF。😰
😰问题的原因到处找没有找到详细的解释,只是可以找到解决这个问题的方法,就是如在江协科技视频教程里所讲在读取DS1302的值之后,手动将 DS1302_IO=0 置为低电平,这样可以解决问题,但是为什么不这样做就会每隔一次读取出来错误的值还是在网上资料里没有找到详细的解释予原因。
😰,在网上的51单片机DS1302的其它学习资料,例如,蓝桥杯51 DS1302 或者是普中科技51 DS1302的教程里,在读取DS1302的数据之后附加了一段代码来对DS1302进行复位,来解决读取DS1302读取到错误值的问题。
/**
* @brief
* @param
* @retval
*/
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i = 0;
unsigned char Data = 0;
DS1302_CE = 1;
for(i=0; i<8; i++)
{
DS1302_IO = Command & (0x01 << i);
DS1302_SCLK = 0;
DS1302_SCLK = 1;
}
/* 为什么需要这一句??*/
//DS1302_IO = 0;
for(i=0; i<8; i++)
{
DS1302_SCLK = 1;
DS1302_SCLK = 0;
if(DS1302_IO)
{
Data |= (0x01 << i);
}
}
/* 为什么需要这一句??*/
DS1302_IO = 0; //加上这一句否则会随机读取出错误值
DS1302_CE = 0;
return Data;
}
4. DS1302读取年月日小时分钟
DS1302读取年月日小时分钟,DS1302 年,月,日,小时,分钟,秒 寄存器里存放的并不是10进制或者16进制的数据,在DS1302 年月日小时分钟寄存器里存放的是 BCD 码(Binary Coded Decimal),寄存器长度为8位,高4位存放10进制数的十位,低4为存放10进制数的个位。
🚗 我们需要将DS1302 年月日寄存器里存放的 BCD 编码格式的值转换为10进制格式的值。
🚗 转换方法为:
十进制数 = (BCD/16)*10 + (BCD%16)
BCD数 = (十进制数/10)*16 + (十进制数%10)
5. DS1302综合源码编写
🚀 本次实验将综合运用之前练习过得单片机实验内容做一个综合性和使用按键可以调节的DS1302时钟,所使用到的之间的知识点有:
- 🌵 51单片机独立按键检测
- 🌵 51单片机 Timer0 定时器/计数器中断
- 🌵51单片机 LCD1602屏幕显示
所以本次实验的源码文件比较多:
main.c
#include <REGX52.H>
#include <INTRINS.H>
#include "delay.h"
#include "DS1302.h"
#include "LCD1602.h"
#include "Key.h"
#include "Timer0.h"
#include "UART.H"
unsigned char KeyNum = 0;
unsigned char Mode = 0;
unsigned char TimeSetSelect = 0;
unsigned char TimeFlashFlag = 0;
void TimeShow(void)
{
DS1302_ReadTime();
LCD_ShowString(1, 1, " - -");
LCD_ShowNum(1, 1, DS1302_Time[0], 2);
LCD_ShowNum(1, 4, DS1302_Time[1], 2);
LCD_ShowNum(1, 7, DS1302_Time[2], 2);
LCD_ShowString(2, 1, " : :");
LCD_ShowNum(2, 1, DS1302_Time[3], 2);
LCD_ShowNum(2, 4, DS1302_Time[4], 2);
LCD_ShowNum(2, 7, DS1302_Time[5], 2);
}
void TimeSet(void)
{
if(KeyNum == 2)
{
TimeSetSelect++;
TimeSetSelect %= 6;
}
else if(KeyNum == 3)
{
DS1302_Time[TimeSetSelect]++;
if(DS1302_Time[0] > 99) { DS1302_Time[0] = 0;}
if(DS1302_Time[1] > 12) { DS1302_Time[1] = 1;}
if(DS1302_Time[1] == 1 || DS1302_Time[1] == 3 || DS1302_Time[1] == 5 || DS1302_Time[1] == 7
|| DS1302_Time[1] == 8 || DS1302_Time[1] == 10 || DS1302_Time[1] == 12)
{
if(DS1302_Time[2] > 31) { DS1302_Time[2] = 1;}
}
else if(DS1302_Time[1] == 4 || DS1302_Time[1] == 6 || DS1302_Time[1] == 9 || DS1302_Time[1] == 11)
{
if(DS1302_Time[2] > 30) { DS1302_Time[2] = 1;}
}
else if(DS1302_Time[1] == 2)
{
if((DS1302_Time[0] % 4) == 0)
if(DS1302_Time[2] > 29) { DS1302_Time[2] = 1;}
else
if(DS1302_Time[2] > 29) { DS1302_Time[2] = 1;}
}
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;}
}
else if(KeyNum == 4)
{
DS1302_Time[TimeSetSelect]--;
if(DS1302_Time[0] < 0) { DS1302_Time[0] = 99;}
if(DS1302_Time[1] < 1) { DS1302_Time[1] = 12;}
if(DS1302_Time[1] == 1 || DS1302_Time[1] == 3 || DS1302_Time[1] == 5 || DS1302_Time[1] == 7
|| DS1302_Time[1] == 8 || DS1302_Time[1] == 10 || DS1302_Time[1] == 12)
{
if(DS1302_Time[2] < 1) { DS1302_Time[2] = 31;}
if(DS1302_Time[2] > 31) { DS1302_Time[2] = 1;}
}
else if(DS1302_Time[1] == 4 || DS1302_Time[1] == 6 || DS1302_Time[1] == 9 || DS1302_Time[1] == 11)
{
if(DS1302_Time[2] < 1) { DS1302_Time[2] = 30;}
if(DS1302_Time[2] > 30) { DS1302_Time[2] = 1;}
}
else if(DS1302_Time[1] == 2)
{
if((DS1302_Time[0] % 4) == 0){
if(DS1302_Time[2] < 1) { DS1302_Time[2] = 29;}
if(DS1302_Time[2] > 29) { DS1302_Time[2] = 1;}
}
else{
if(DS1302_Time[2] < 1) { DS1302_Time[2] = 28;}
if(DS1302_Time[2] > 28) { DS1302_Time[2] = 1;}
}
}
if(DS1302_Time[3] < 0) { DS1302_Time[3] = 23;}
if(DS1302_Time[4] < 0) { DS1302_Time[4] = 59;}
if(DS1302_Time[5] < 0) { DS1302_Time[5] = 59;}
}
//if(KeyNum)
{
if(TimeSetSelect==0 && TimeFlashFlag==1)
{
LCD_ShowString(1, 1, " ");
}
else{
LCD_ShowNum(1, 1, DS1302_Time[0], 2);
}
if(TimeSetSelect==1 && TimeFlashFlag==1)
{
LCD_ShowString(1, 4, " ");
}
else{
LCD_ShowNum(1, 4, DS1302_Time[1], 2);
}
if(TimeSetSelect==2 && TimeFlashFlag==1)
{
LCD_ShowString(1, 7, " ");
}
else{
LCD_ShowNum(1, 7, DS1302_Time[2], 2);
}
if(TimeSetSelect==3 && TimeFlashFlag==1)
{
LCD_ShowString(2, 1, " ");
}
else{
LCD_ShowNum(2, 1, DS1302_Time[3], 2);
}
if(TimeSetSelect==4 && TimeFlashFlag==1)
{
LCD_ShowString(2, 4, " ");
}
else{
LCD_ShowNum(2, 4, DS1302_Time[4], 2);
}
if(TimeSetSelect==5 && TimeFlashFlag==1)
{
LCD_ShowString(2, 7, " ");
}
else{
LCD_ShowNum(2, 7, DS1302_Time[5], 2);
}
LCD_ShowNum(2, 13, TimeFlashFlag, 1);
if(KeyNum)
LCD_ShowNum(2, 10, KeyNum, 1);
}
}
void main()
{
DS1302_Init();
LCD_Init();
Timer0_Init();
DS1302_SetTime();
while(1)
{
KeyNum = Key();
if(KeyNum)
{
if(KeyNum == 1)
{
if(Mode == 0) {Mode=1; }
else if(Mode == 1)
{ Mode=0;
DS1302_SetTime();
TimeSetSelect = 0;
}
}
}
switch(Mode)
{
case 0: TimeShow(); break;
case 1: TimeSet(); break;
}
//Delay(1000);
}
}
/**
* @brief 定时器0中断处理函数模版
* @param 无
* @retval 无
*/
void Timer_Routine(void) interrupt 1
{
static unsigned int count = 0;
count++;
if(count >= 500) //定时器T0每1ms中断一次,进入1000次经过了1s
{
TimeFlashFlag = !TimeFlashFlag;
count = 0;
}
//定时器溢出之后需要重新装载
TH0 = (65535 - 1000) / 256; //12MHz晶振,12分频
TL0 = (65535 - 1000) % 256 + 1; //
}
DS1302.c
#include "DS1302.h"
#include "Delay.h"
char DS1302_Time [] = {24, 7, 2, 22, 35, 2, 2};
#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
/**
* @brief
* @param
* @retval
*/
void DS1302_Init(void)
{
DS1302_CE = 0;
DS1302_SCLK = 0;
}
/**
* @brief
* @param
* @retval
*/
void DS1302_WriteByte(unsigned char Command, unsigned char Data)
{
unsigned char i = 0;
DS1302_CE = 1;
for(i=0; i<8; i++)
{
DS1302_IO = Command & (0x01 << i);
DS1302_SCLK = 1;
51单片机的速度比较慢,不需要延时
DS1302_SCLK = 0;
}
for(i=0; i<8; i++)
{
DS1302_IO = Data & (0x01 << i);
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
DS1302_CE = 0;
}
/**
* @brief
* @param
* @retval
*/
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i = 0;
unsigned char Data = 0;
DS1302_CE = 1;
Command |= 0x01;
for(i=0; i<8; i++)
{
DS1302_IO = Command & (0x01 << i);
DS1302_SCLK = 0;
DS1302_SCLK = 1;
}
/* 为什么需要这一句??*/
//DS1302_IO = 0;
for(i=0; i<8; i++)
{
DS1302_SCLK = 1;
DS1302_SCLK = 0;
if(DS1302_IO)
{
Data |= (0x01 << i);
}
}
/* 为什么需要这一句??*/
DS1302_IO = 0; //加上这一句否则会随机读取出错误值
DS1302_CE = 0;
return Data;
}
/**
* @brief
* @param
* @retval
*/
void DS1302_SetTime(void)
{
//关闭DS1302写保护
DS1302_WriteByte(DS1302_WP, 0x00);
DS1302_WriteByte(DS1302_YEAR, DS1302_Time[0]/10*16 + DS1302_Time[0]%10);
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写保护
//DS1302_WriteByte(DS1302_WP, 0x80);
}
/**
* @brief
* @param
* @retval
*/
void DS1302_ReadTime(void)
{
unsigned char temp;
temp = DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0] = temp/16*10 + temp%16;
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;
}
DS1302.h
#ifndef __DS1302_H__
#define __DS1302_H__
#include <REGX52.H>
sbit DS1302_SCLK = P3^6;
sbit DS1302_CE = P3^5;
sbit DS1302_IO = P3^4;
extern char DS1302_Time [];
void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command, unsigned char Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_ReadTime(void);
void DS1302_SetTime(void);
#endif
Key.c
#include <REGX52.H>
#include "key.h"
#include "delay.h"
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围0~4,无按键按下时返回0
*/
unsigned char Key(void)
{
unsigned char keyNumber = 0;
if(P3_1 == 0){
Delay(10); //按键按下消抖
while(P3_1 == 0); //检测松手,没有松手一直循环
Delay(10); //按键松开消抖
keyNumber = 1;
}
if(P3_0 == 0){
Delay(10); //按键按下消抖
while(P3_0 == 0); //检测松手,没有松手一直循环
Delay(10); //按键松开消抖
keyNumber = 2;
}
if(P3_2 == 0){
Delay(10); //按键按下消抖
while(P3_2 == 0); //检测松手,没有松手一直循环
Delay(10); //按键松开消抖
keyNumber = 3;
}
if(P3_3 == 0){
Delay(10); //按键按下消抖
while(P3_3 == 0); //检测松手,没有松手一直循环
Delay(10); //按键松开消抖
keyNumber = 4;
}
return keyNumber;
}
Key.h
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key(void);
#endif
Timer0.c
#include <REGX52.H>
#include "timer0.h"
/**
* @brief 定时器0初始化函数, 1ms 12MHz
* @param 无
* @retval 无
*/
void Timer0_Init()
{
//AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
//中断部分寄存器
ET0 = 1; //允许定时器T0中断
EA = 1; //允许中断
PT0 = 0; //定时器T0中断优先级
}
/**
* @brief 定时器0中断处理函数模版
* @param 无
* @retval 无
*/
//void Timer_Routine(void) interrupt 1
//{
// static unsigned int count = 0;
//
// count++;
// //P2_0 = 0;
// if(count >= 500) //定时器T0每1ms中断一次,进入1000次经过了1s
// {
// P2_0 = ~P2_0;
// count = 0;
// }
//
// //定时器溢出之后需要重新装载
// TH0 = (65535 - 1000) / 256; //12MHz晶振,12分频
// TL0 = (65535 - 1000) % 256 + 1; //
//}
Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init();
void Timer_Routine(void);
#endif
6. 实验总结
🌵51单片机 DS1302实时时钟芯片实验需要注意的点是:
- 🐟️ 对DS1302注意开启写保护
- 🐟️ 对DS1302进行读取时,在读取的最后一步需要将 DS1302_IO=0,否则会读取到0xFF值。在其它的51单片机DS1302实验的教程里都有提到这一点需要再读取DS1302寄存器的最有将 DS1302_IO=0引脚拉低。