嵌入式STM32F103C8T6使用RTC DS3231实现时间输出教程(也许是万年历)
计划
这学期有一门嵌入式的课程,课是没怎么听,但是期末要交大作业,只有这个万年历好写一点,老师给的板子很是便宜,淘宝上就那一家什么Microvue STM32F103C8T6开发版,中文名微视界,怪难搜索到33块钱,的确能跑,还有演示程序,我便开始实现这个万年历。
可是我作为一个菜鸡CS学生,这嵌入式太可怕,这个便宜的板子是没个什么正经RTC的,意思就是其没有办法存储实时时间,如果要写,就是写个“假”的,用系统内置的delay函数,延时1s之后自己写程序计算,需要考虑闰年等等事情,我觉得很累,就上网搜索了一波,发现可以外接RTC,淘宝买到的RTC大多自带一个小电池,单片机掉电了也可以存储时间,而且贼强,我随手买的这个DS3231据说精度很高,可以存储年,月,日,星期几,时,分,秒,还带着两个闹钟存储,自动闰年等等调整,真的很强,我就决定外接这个来实现年月日等等,而且我还会用到很多模块,我打算把我能跑的代码放出来开源,方便大家测试,最早我也是抄来的吗,嘤。
在此还要感谢这篇文章,靠这篇文章我获取到了时间链接
RTC DS3231
但是我们毕竟是学习,原理是要搞懂的,我们简单来看看这个RTC是如何给我们数据的
对于我们写程序主要是看这幅图,首先其有8位,本质是两个BCD码,回忆BCD码,每4位表示一个十进制数字,这样就清晰了起来,比如06H这个地址,我们发现低4位(我就这么说了)是单位为1的年份表示,这就是一个数字,也就是0-99的个位部分,之后高4位我们发现单位是10,也就是十位,在我实验之后发现这个模块应该是可以从2000年开始计数,最高数到2100年前一天,如果到99年多一年,系统会认为一个世纪过去了,观察05H地址,我们发现有一个Century位,代表年份的移除标志位,通过这个例子,我们就能看懂这张表,最下面几个应该是模式选择,这里我没有研究,有人有研究可以在底下评论告诉我们。
那么我们知道了对应的地址和代表的意义,那我们就知道要如何设计程序,首先要写两个函数,因为这里是BCD码,这样我们需要实现16toBCD和BCDto16,之后再实现两个函数,一个读一个写,这样就实现了日期的设置和读取,这样整个流程就完了,在此感谢那篇文章,那个程序大部分在我这里是可以跑对的。
实现
这里给出实现,分成三个文件,大家在移植的时候,注意根据自己的系统来修改里面的延时函数,我在这里用的是商家样例程序中的Delay函数,修改LED输出函数等等,有必要的情况下要修改引脚位置保存兼容,修改引入和删除的相关的库,保持工程师的精神,凑乎凑乎,东拼西凑,干就完事,能跑就行:
iic.h
#ifndef __DSIIC_H
#define __DSIIC_H
#include "stm32f10x_i2c.h"
//我这里的延时函数之类的定义的位置,有需要要改
#include "common.h"
//我这里的引脚位置定义文件,有需要要修改成自己的
#include "bitband.h"
/***************************************IO方向设置**********************************************/
#define DS_SDA_IN() \
{ \
GPIOA->CRH &= 0XFFFFFF0F; \
GPIOA->CRH |= 8 << 4; \
} //PA9
#define DS_SDA_OUT() \
{ \
GPIOA->CRH &= 0XFFFFFF0F; \
GPIOA->CRH |= 2 << 4; \
} //1 - 10MHZ 2 - 2MHZ 3- 50MHZ
/***************************************IO操作函数**********************************************/
// 注释给出了IO的绑定
#define DS_IIC_SCL PAout(10) //SCL
#define DS_IIC_SDA PAout(9) //SDA
#define DS_READ_SDA PAin(9) //输入SDA
#define DS_Write_ADD 0xD0 //DS3231地址+写操作
#define DS_Read_ADD 0xD1 //DS3231地址+读操作
extern unsigned char set_rtc_date[7];
extern unsigned char read_rtc_date[7];
extern u8 year, month, date, hour, minute, second;
/*************************************IIC所有操作函数*******************************************/
void DS_IIC_Init_Func(void);
void setsda(void);
void clrsda(void);
u8 BCD2HEX(u8 val);
u8 B_BCD(u8 val);
void delay(u16 us);
void Start(void);
void Stop(void);
void SendByte(u8 Dat);
u8 ReceiveByte(u8 b);
void I2cByteWrite(u8 device, u8 addr, u8 bytedata);
u8 I2cByteRead(u8 device, u8 addr);
void Readtime(void);
void ModifyTime(u8 yea, u8 mon, u8 da,u8 we, u8 hou, u8 min, u8 sec);
void get_show_time(void);
u8 get_show_year(void);
u8 get_show_month(void);
u8 get_show_date(void);
u8 get_show_hour(void);
u8 get_show_min(void);
u8 get_show_sec(void);
u8 get_show_week(void);
#define SCL_H PAout(10) = 1
#define SCL_L PAout(10) = 0
#define SDA_H PAout(9) = 1
#define SDA_L PAout(9) = 0
#define SDA (GPIOA->IDR & 1 << 9)
#endif
iic.c
#include "iic.h"
#include "common.h"
void DS_IIC_Init_Func(void)
{
GPIOA->CRH &= 0xfffff00f;
GPIOA->CRH |= 0x00000220; //0x22000000; //SCK SDA
Delay_ms(100);
// I2cByteWrite(0xD0,0x07,0x80); //为DS3231芯片闹钟1设置,通过SQW输出1HZ信号
// I2cByteWrite(0xD0,0x08,0x80);
// I2cByteWrite(0xD0,0x09,0x80);
// I2cByteWrite(0xD0,0x0a,0x80);
// I2cByteWrite(0xD0,0x0e,0x05);
I2cByteWrite(0xD0, 0x0e, 0);
I2cByteWrite(0xD0, 0x0f, 0);
//ModifyTime(15,0x03,20,15,32,20);
}
/***********************************************************************************************
** 函数名称:setsda
** 输入参数:无
** 输出参数:无
** 功能描述:SDA输出
***********************************************************************************************/
void setsda(void) //DS_SDA_OUT
{
GPIOA->CRH &= 0XFFFFFF0F;
GPIOA->CRH |= 2 << 4;
}
/***********************************************************************************************
** 函数名称:clrsda
** 输入参数:无
** 输出参数:无
** 功能描述:SDA输入
***********************************************************************************************/
void clrsda(void) //DS_SDA_IN
{
GPIOA->CRH &= 0XFFFFFF0F;
GPIOA->CRH |= 8 << 4;
}
/***********************************************************************************************
** 函数名称:BCD2HEX
** 输入参数:val:BCD码数据
** 输出参数:十六进制数据
** 功能描述:将BCD码数据转换成十六进制数据
***********************************************************************************************/
u8 BCD2HEX(u8 val)
{
u8 i;
i = val & 0x0f;
val >>= 4;
val &= 0x0f;
val *= 10;
i += val;
return i;
}
/***********************************************************************************************
** 函数名称:B_BCD
** 输入参数:val:十进制数据
** 输出参数:BCD码数据
** 功能描述:将十进制数据转换成BCD码数据
***********************************************************************************************/
u8 B_BCD(u8 val)
{
u8 i, j, k;
i = val / 10;
j = val % 10;
k = j + (i << 4);
return k;
}
/***********************************************************************************************
** 函数名称:delay
** 输入参数:us:延时us时间
** 输出参数:无
** 功能描述:延时设定的时间
***********************************************************************************************/
void delay(u16 us)
{
Delay_us(us * 7);
}
/***********************************************************************************************
** 函数名称:Start
** 输入参数:无
** 输出参数:无
** 功能描述:IIC开始条件
***********************************************************************************************/
void Start(void)
{
SDA_H;
delay(5);
SCL_H;
delay(5);
SDA_L;
delay(5);
}
/***********************************************************************************************
** 函数名称:Stop
** 输入参数:无
** 输出参数:无
** 功能描述:IIC结束条件
***********************************************************************************************/
void Stop(void)
{
SDA_L;
delay(5);
SCL_H;
delay(5);
SDA_H;
delay(5);
}
/***********************************************************************************************
** 函数名称:SendByte
** 输入参数:Dat:需发送的数据
** 输出参数:无
** 功能描述:发送一字节数据
***********************************************************************************************/
void SendByte(u8 Dat)
{
u8 i = 0;
u8 T_Data = 0;
DS_SDA_OUT();
SCL_L;
delay(10);
T_Data = Dat;
for (i = 0; i < 8; i++)
{
if (T_Data & 0x80)
SDA_H;
else
SDA_L;
delay(5);
SCL_L;
delay(5);
SCL_H;
delay(5);
T_Data = T_Data << 1;
SCL_L;
delay(5);
}
SDA_H;
delay(5);
SCL_L;
delay(5);
SCL_H;
delay(5);
SCL_L;
}
/***********************************************************************************************
** 函数名称:ReceiveByte
** 输入参数:b:是否产生应答 0-产生应答 1-不产生应答
** 输出参数:无
** 功能描述:接收一字节数据,并根据b决定是否发送应答信息。
***********************************************************************************************/
u8 ReceiveByte(u8 b)
{
u8 i;
u8 temp;
u8 Dat = 0;
DS_SDA_IN();
for (i = 0; i < 8; i++)
{
SCL_H;
delay(5);
Dat = Dat << 1;
delay(5);
if (SDA)
{
temp = 1;
}
else
{
temp = 0;
}
if (temp)
Dat |= 0x01;
else
Dat |= 0x00;
delay(5);
SCL_L;
delay(5);
}
DS_SDA_OUT();
if (b)
SDA_H;
else
SDA_L;
delay(5);
SCL_H;
delay(5);
SCL_L;
delay(5);
SDA_H;
delay(100);
return Dat;
}
/***********************************************************************************************
** 函数名称:I2cByteWrite
** 输入参数:device:设备地址,0xd0; addr:寄存器地址 bytedata:寄存器地址中数据
** 输出参数:无
** 功能描述:向DS3231相关寄存器中写入设定数据
***********************************************************************************************/
void I2cByteWrite(u8 device, u8 addr, u8 bytedata)
{
Start();
delay(5);
SendByte(device);
delay(5);
SendByte(addr);
delay(5);
SendByte(bytedata);
delay(5);
Stop();
}
/***********************************************************************************************
** 函数名称:I2cByteRead
** 输入参数:device:设备地址,0xd1; addr:寄存器地址
** 输出参数:无
** 功能描述:从DS3231相关寄存器中读取数据
***********************************************************************************************/
u8 I2cByteRead(u8 device, u8 addr)
{
u8 Dat = 0;
Start();
SendByte(device);
delay(5);
SendByte(addr);
delay(5);
Start(); //Restart
SendByte(0xd1);
delay(5);
Dat = ReceiveByte(1);
Stop();
return Dat;
}
/***********************************************************************************************
** 函数名称:Readtime
** 输入参数:无
** 输出参数:无
** 功能描述:从DS3231相关寄存器中读取时间数据
***********************************************************************************************/
void Readtime(void)
{
u8 year = I2cByteRead(0xd0, 0x06); //年
u8 month = I2cByteRead(0xd0, 0x05); //月
u8 date = I2cByteRead(0xd0, 0x04); //日
u8 hour = I2cByteRead(0xd0, 0x02); //时
u8 minute = I2cByteRead(0xd0, 0x01); //分
u8 second = I2cByteRead(0xd0, 0x00); //秒
}
/***********************************************************************************************
** 函数名称:ModifyTime
** 输入参数:yea,mon,da,hou,min,sec分别代表:年,月,日,时,分,秒
** 输出参数:无
** 功能描述:修改DS3231相关寄存器中时间数据
***********************************************************************************************/
void ModifyTime(u8 yea, u8 mon, u8 da,u8 we, u8 hou, u8 min, u8 sec)
{
u8 temp = 0;
temp = B_BCD(yea);
I2cByteWrite(0xD0, 0x06, temp);
temp = B_BCD(mon);
I2cByteWrite(0xD0, 0x05, temp);
temp = B_BCD(da);
I2cByteWrite(0xD0, 0x04, temp);
temp = B_BCD(we);
I2cByteWrite(0xD0, 0x03, temp);
temp = B_BCD(hou);
I2cByteWrite(0xD0, 0x02, temp);
temp = B_BCD(min);
I2cByteWrite(0xD0, 0x01, temp);
temp = B_BCD(sec);
I2cByteWrite(0xD0, 0x00, temp);
}
/***********************************************************************************************
** 函数名称:get_show_time
** 输入参数:无
** 输出参数:无
** 功能描述:读取DS3231相关寄存器中时间数据
***********************************************************************************************/
void get_show_time(void)
{
//int d[6];
u8 year,month,date,hour,minute,second;
year = I2cByteRead(0xd0, 0x06);
year = BCD2HEX(year);
month = I2cByteRead(0xd0, 0x05);
month = BCD2HEX(month);
date = I2cByteRead(0xd0, 0x04);
date = BCD2HEX(date);
hour = I2cByteRead(0xd0, 0x02);
hour &= 0x3f;
hour = BCD2HEX(hour);
minute = I2cByteRead(0xd0, 0x01);
minute = BCD2HEX(minute);
second = I2cByteRead(0xd0, 0x00);
second = BCD2HEX(second);
}
u8 get_show_year(void){
u8 year;
year = I2cByteRead(0xd0, 0x06);
year = BCD2HEX(year);
return year;
}
u8 get_show_month(void){
u8 month;
month = I2cByteRead(0xd0, 0x05);
month = BCD2HEX(month);
return month;
}
u8 get_show_date(void){
u8 date;
date = I2cByteRead(0xd0, 0x04);
date = BCD2HEX(date);
return date;
}
//获取星期几
u8 get_show_week(void){
u8 week;
week = I2cByteRead(0xd0, 0x03);
week = BCD2HEX(week);
return week;
}
u8 get_show_hour(void){
u8 hour;
hour = I2cByteRead(0xd0, 0x02);
hour &= 0x3f;
hour = BCD2HEX(hour);
return hour;
}
u8 get_show_min(void){
u8 minute;
minute = I2cByteRead(0xd0, 0x01);
minute = BCD2HEX(minute);
return minute;
}
u8 get_show_sec(void){
u8 second;
second = I2cByteRead(0xd0, 0x00);
second = BCD2HEX(second);
return second;
}
main.c
//LED显示相关的应该移植的时候都要改写
#include "iic.h" //这次主要用到的
#include <stdio.h>
#include <stdlib.h>
#include "stm32f10x.h"
#include "common.h"
#include "oled.h"
#include "page.h"
#include "key.h"
#include "task_manage.h"
#include "AD9834.h"
int main(void)
{
int G_un8TestFlag = 1;
int G_un32WandRNum;
char buffer [20];
int v1=0,v2=0;
SystemConfiguration(); //系统初始化
RCC_Configuration();
DS_IIC_Init_Func();
Lcd_Init();
ad9834_reset();
T_Adc_Init();
BACK_COLOR=BLACK; //0xef5d;
POINT_COLOR=RED;
LCD_Clear(BLACK,320,240); //0x3211);
OLED_BLK_Set();
POINT_COLOR=WHITE;
Delay_ms(100);
BACK_COLOR=BLACK;
POINT_COLOR=BLUE;
//!注意这里时设置时间的函数,年,月,日,星期几,时,分,秒
//!其中年我认为这个RTC时从2000年开始计算的所以19年应该是设置19
//ModifyTime(19, 12, 10, 2, 13, 12, 0);
while(1)
{
//KeySweep();
//task_inputfre();
if (HRR == 1){
LCD_WR_REG(0x28);
OLED_BLK_Set();
LCD_ShowString3216(83,103,(unsigned char const *)"People");
}else{
OLED_BLK_Set();
POINT_COLOR=WHITE;
BACK_COLOR=BLACK;
POINT_COLOR=BLUE;
LCD_ShowString3216(83,103,(unsigned char const *)"No People");
}
if (G_un8TestFlag)
{
G_un32WandRNum = get_show_year();
G_un32WandRNum += 2000;
sprintf(buffer, "%4d", G_un32WandRNum);
LCD_ShowString3216(43,30,(unsigned char const *)buffer);
//LCD_ShowString3216(86,73,(unsigned char const *)":");
G_un32WandRNum = get_show_month();
sprintf(buffer, "%2d", G_un32WandRNum);
LCD_ShowString3216(43,60,(unsigned char const *)buffer);
//LCD_ShowString3216(146,93,(unsigned char const *)":");
G_un32WandRNum = get_show_date();
sprintf(buffer, "%2d", G_un32WandRNum);
LCD_ShowString3216(43,90,(unsigned char const *)buffer);
//LCD_ShowString3216(216,113,(unsigned char const *)":");
G_un32WandRNum = get_show_week();
if (G_un32WandRNum == 1){
LCD_ShowString3216(113,30,(unsigned char const *)"MON");
}else if (G_un32WandRNum == 2){
LCD_ShowString3216(113,30,(unsigned char const *)"TUE");
}else if (G_un32WandRNum == 3){
LCD_ShowString3216(113,30,(unsigned char const *)"WED");
}else if (G_un32WandRNum == 4){
LCD_ShowString3216(113,30,(unsigned char const *)"THU");
}else if (G_un32WandRNum == 5){
LCD_ShowString3216(113,30,(unsigned char const *)"FRI");
}else if (G_un32WandRNum == 6){
LCD_ShowString3216(113,30,(unsigned char const *)"SAT");
}else if (G_un32WandRNum == 7){
LCD_ShowString3216(113,30,(unsigned char const *)"SUN");
}
G_un32WandRNum = get_show_hour();
sprintf(buffer, "%2d", G_un32WandRNum);
LCD_ShowString3216(43,120,(unsigned char const *)buffer);
//LCD_ShowString3216(266,63,(unsigned char const *)":");
G_un32WandRNum = get_show_min();
sprintf(buffer, "%2d", G_un32WandRNum);
LCD_ShowString3216(43,150,(unsigned char const *)buffer);
//LCD_ShowString3216(316,63,(unsigned char const *)":");
G_un32WandRNum = get_show_sec();
sprintf(buffer, "%2d", G_un32WandRNum);
LCD_ShowString3216(43,180,(unsigned char const *)buffer);
}
Delay_ms(50);
}
}
修改完之后接好模块,应该就可以运行了,注意按照自己的设备相关的函数。
我认为应该开放这些代码,并且让大家都能方便的使用,即使是在干像我一样的大作业,希望也有更多人能够开放自己的源代码出去。下一篇应该是写加DHT11的代码,我还没有研究,感觉应该不是很难吧(希望)。