STM32驱动DS3231时钟模块(OLED显示)
STM32驱动DS3231时钟模块(OLED显示)
模块简介
DS3231M是低成本、高精度I2C实时时钟(RTC)。该器件包含电池输入端,断开主电源时仍可保持精确计时。集成微机电系统(MEMS)提高了器件的长期精确度,并减少了生产线的元件数量。DS3231M采用与流行的DS3231 RTC相同的器件封装。RTC保存秒、分、时、星期、日期、月和年信息。少于31天的月份,将自动调整月末的日期,包括闰年修正。时钟格式可以是24小时或带AM/PM指示的12小时格式。提供两个可设置的日历闹钟和一个1Hz输出。地址与数据通过I2C双向总线串行传输。精密的、经过温度补偿的电压基准和比较器电路用来监视VCC状态,检测电源故障,提供复位输出,并在必要时自动切换到备份电源。
寄存器说明
寄存器存储的是BCD(8421)编码,读取后要转换成十进制进行显示。前7个寄存器(00h-06h)都是和时间相关的,注意小时寄存器(02h)的第6位定义为12小时或24小时模式选择位。该位为高时,选择12小时模式。在12小时模式下,第5位为AM/PM指示位,逻辑高时为PM。在24小时模式下,第5位为20小时位(20至23小时)。当年寄存器由99溢出至00时,会转换世纪位(月寄存器(05h)的第7位)。(下面代码中定义的是24小时制)
星期寄存器(03h)在午夜时递增。对应于星期的值由用户定义,但是该值必须连续(即,如果1等于星期日,那么2等于星期一,依次类推)。不合逻辑的时间和日期输入会导致不确定的操作。(下面代码中定义的是周一为1)。
后面的7个寄存器是与闹钟相关的(07h-0Dh)是与闹钟相关的,具体的定义可以自行查阅手册进行查看,这里就不展开说明了。
接下来是控制寄存器(0Eh)和状态寄存器(0Fh),以及老化补偿寄存器(10h),这几个寄存上电前就都已经有默认配置了,如果没有特别需求的话是不需要进行修改的,如果有特定的需求可以根据手册中的说明进行详细配置。
最后是温度寄存器(11h以及12h)温度值采用10位编码表示,具有0.25°C的分辨率,访问地址为11h和12h。温度编码为2的补码格式。高8位(整数部分)位于地11h,低2位(小数部分)位于地址12h。例如,0001 1001 01b = +25.25°C。上电复位后,寄存器的缺省温度值设定为0°C,控制器启动温度转换。在VCC初次上电或VBAT供电下首次进行I2C通信时,开始读取温度值,之后每秒(采用VCC供电)或每10秒(采用VBAT供电)读取一次。每次由用户启动的转换结束后都会更新温度寄存器,温度寄存器是只读的。
代码说明
这里使用的是STM32F103C8T6模拟IIC驱动DS3231,在OLED屏幕上进行显示。
ds3231.h
#ifndef _DS3231_H
#define _DS3231_H
void DS3231_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t DS3231_ReadReg(uint8_t RegAddress);
void DS3231_Init(void);
void DS3231_SetTime(u8 year,u8 mon,u8 data,u8 week,u8 hour,u8 min,u8 sec);
void DS3231_GetTime(u8 *year, u8 *month, u8 *date, u8 *week, u8 *hour, u8 *min, u8 *sec);
#endif
ds3231.c
#include "Device/Include/stm32f10x.h" // Device header
#include "DS3231.h"
#include "DS3231_Reg.h"
#include "MyI2C.h"
#define DS3231_ADDRESS 0xD0//DS3213的写地址(这里是写地址,方便后面代码的编写,把0位也计算进去了)
//DS3231的IIC地址:0x68
/**
* 函 数:DS3231写寄存器
* 参 数:RegAddress 寄存器地址
* 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF
* 返 回 值:无
*/
void DS3231_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start(); //I2C起始
MyI2C_SendByte(DS3231_ADDRESS); //发送从机地址,读写位为0,表示即将写入
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(RegAddress); //发送寄存器地址
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(Data); //发送要写入寄存器的数据
MyI2C_ReceiveAck(); //接收应答
MyI2C_Stop(); //I2C终止
}
/**
* 函 数:DS3231读寄存器
* 参 数:RegAddress 寄存器地址
* 返 回 值:读取寄存器的数据,范围:0x00~0xFF
*/
uint8_t DS3231_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start(); //I2C起始
MyI2C_SendByte(DS3231_ADDRESS); //发送从机地址,读写位为0,表示即将写入
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(RegAddress); //发送寄存器地址
MyI2C_ReceiveAck(); //接收应答
MyI2C_Start(); //I2C重复起始
MyI2C_SendByte(DS3231_ADDRESS | 0x01); //发送从机地址,读写位为1,表示即将读取
MyI2C_ReceiveAck(); //接收应答
Data = MyI2C_ReceiveByte(); //接收指定寄存器的数据
MyI2C_SendAck(1); //发送应答,给从机非应答,终止从机的数据输出
MyI2C_Stop(); //I2C终止
return Data;
}
void DS3231_Init(void)
{
MyI2C_Init(); //先初始化底层的I2C
DS3231_WriteReg(DS3231_CONTROL_REG, 0x1C); //初始化控制寄存器
}
/**
* 函 数:BCD(8421)转DEC.//二进制转十进制
* 参 数:val:BCD码.
* 参 数:i:DEC码.
* 返 回 值:无
*/
uint8_t BCD_DEC(u8 val)
{
u8 i;
i= val&0x0f;
val >>= 4;
val &= 0x0f;
val *= 10;
i += val;
return i;
}
/**
* 函 数:DEC转BCD(8421).//十进制转二进制
* 参 数:val:DEC码.
* 参 数:k:BCD码.
* 返 回 值:无
*/
uint8_t DEC_BCD(u8 val)
{
u8 i,j,k;
i=val/10;
j=val%10;
k=j+(i<<4);
return k;
}
/**
* 函 数:时间设置
* 参 数:分别输入 年 月 日 星期 时 分 秒
* 返 回 值:无
*/
void DS3231_SetTime(u8 year,u8 mon,u8 da,u8 week,u8 hour,u8 min,u8 sec)
{
//u8 year = 0x23; //23年 0010 0011
//u8 mon = 0x10; //10月 0001 0000
//u8 data = 0x13; //13日 0001 0011
//u8 week = 0x06; //周6 0000 0110
//u8 hour = 0x08; //8时 0000 1000
//u8 min = 0x08; //8分
//u8 sec = 0x08; //8秒
DS3231_WriteReg(0x06,DEC_BCD(year));
DS3231_WriteReg(0x05,DEC_BCD(mon));
DS3231_WriteReg(0x04,DEC_BCD(da));
DS3231_WriteReg(0x03,DEC_BCD(week));
DS3231_WriteReg(0x02,DEC_BCD(hour));
DS3231_WriteReg(0x01,DEC_BCD(min));
DS3231_WriteReg(0x00,DEC_BCD(sec));
}
/**
* 函 数:DS3231获取数据
* 参 数:year--年,month--月,date--日,week--周几,hour--时,min--分,sec--秒,
使用输出参数的形式返回,范围:0-255
* 返 回 值:无
*/
void DS3231_GetTime(u8 *year, u8 *month, u8 *date, u8 *week, u8 *hour, u8 *min, u8 *sec)
{
*year=DS3231_ReadReg(0x06);
*year=BCD_DEC(*year);
*month=DS3231_ReadReg(0x05);
*month=BCD_DEC(*month);
*date=DS3231_ReadReg(0x04);
*date=BCD_DEC(*date);
*week=DS3231_ReadReg(0x03);
*week=BCD_DEC(*week);
*hour=DS3231_ReadReg(0x02);
*hour&=0x3f; //设定为12小时制的时候起作用,去除小时制设定位的读取
*hour=BCD_DEC(*hour);
*min=DS3231_ReadReg(0x01);
*min=BCD_DEC(*min);
*sec=DS3231_ReadReg(0x00);
*sec=BCD_DEC(*sec);
}
main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "DS3231.h"
#include <string.h>
#include "OLED.h"
int main(void)
{
u8 year;
u8 month;
u8 date;
u8 week;
u8 hour;
u8 min;
u8 sec;
OLED_Init();
DS3231_Init();
//DS3231_SetTime(24, 10, 15, 2, 20, 02, 50); //设置时间,首次可以手动设置时间进行校准,设置完成后,就不需要再重新设置了,模块断电后有电池持续计时
//这里设置的是24小时制
while(1)
{
OLED_ShowString(1, 4, "DS3231_RTC");
DS3231_GetTime(&year, &month, &date, &week, &hour, &min, &sec); //获取时间
//年,月,日
OLED_ShowNum(2, 4, year+2000, 4);
OLED_ShowString(2, 8, ".");
OLED_ShowNum(2, 9, month, 2);
OLED_ShowString(2, 11, ".");
OLED_ShowNum(2, 12, date, 2);
//时,分,秒
OLED_ShowNum(3, 5, hour, 2);
OLED_ShowString(3, 7, ":");
OLED_ShowNum(3, 8, min, 2);
OLED_ShowString(3, 10, ":");
OLED_ShowNum(3, 11, sec, 2);
//周
OLED_ShowString(4, 6, "week:");
OLED_ShowNum(4, 11, week, 1);
}
}
测试现象
OLED时间显示
逻辑分析仪抓取IIC数据,这里只截取了部分数据帧,是主机读取单个寄存器的完整过程,可以看到发送起始信号后,发送模块的地址等待从机响应,收到响应后再发送要读取的寄存器地址进行读取,然后结束。
总结
- 小时寄存器有12小时制的设定,所以需要在这里对取出的数据进行做处理,和0x3f做与算换后得出结果。
- 逻辑分析仪注意使用的时候,采样数和采样频率成10的整数倍进行取样。