一年前开始做STM32的RTC,到现在才开始整理,战线拖得有点长啦。
先说说为啥要做RTC吧,公司有个产品要定时启动语音提示,本来是想使用STM32内部RTC的,后来发现STM32内部RTC时间有些快,产品上的时间,一个月快了3分钟多,一年那就是半个小时还多。在网上查了资料说,可以软件校准STM32的RTC,这个兴奋啊!
原文:
“
实现RTC 校准的核心之一是库文件Stm321f0x_bkp.c中的void BKP_SetRTCCalibrationValue (uint8_t CalibrationValue) 函数。谈到RTC校准的相关参考文档包括AN2604.pdf,AN2821.pdf和AN2821.zip。这三个文档都可以从STM32官方网站下载。按照AN2604.pdf描述的原理,RTC 的校准值应在0-127之间。可实现的校准误差对应为0-121ppm。相当于每30天跑快的秒数为0-314s。
”
这个方法,我也不知道有没有人做过,不过我真的把这些资料认真研读,并且做了实验,不过可惜的是,这个方法是让RTC跑慢了下来,但是效果还是不理想,一个月大概快105S的样子(因为这个时间是推算出来的,两天快7,实验做了5天),所以不得不换方法。
后来,有网友建议使用DS1337,这次我也做了实验,还好这次实验比前面的效果好很多,4天快7S的样子,一个月也有一分钟的误差,这个误差说大不大,说小也不小,可能是有强迫症,然后有重新找方法,看了好多关于DS3231的资料,这次直接买了一个DS3231的模块,实验比较了一下,三天的时间没什么误差,后来买了芯片搞起来。效果真的不错,一个月的误差在2S的范围内,这次果断用了。具体的做法是,使用STM32模拟IIC与DS3231进行通信,读取和设置相关参数;然后再通过模拟IIC与OLED进行通信,将实时时间数据显示在OLED上。以下是实验代码:
OLED.h
#ifndef __OLED_H
#define __OLED_H
#include "sys.h"
#include "stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif
/*************Pin Define***************/
#define SCL_HIGH GPIO_SetBits(GPIOB, GPIO_Pin_3) //LED1点亮//P1OUT|=BIT0 //SCL P1.0
#define SCL_LOW GPIO_ResetBits(GPIOB, GPIO_Pin_3) //P1OUT&=~BIT0
#define SDA_HIGH GPIO_SetBits(GPIOB, GPIO_Pin_4) //P1OUT|=BIT1 //SDA P1.1
#define SDA_LOW GPIO_ResetBits(GPIOB, GPIO_Pin_4) //P1OUT&=~BIT1
/****************************************************/
void OLED_Initial(void);
void IIC_Start(void);
void IIC_Stop(void);
void Write_IIC_Command(unsigned char IIC_Command);
void Write_IIC_Data(unsigned char IIC_Data);
void Write_IIC_Byte(unsigned char IIC_Byte);
void LCD_Set_Pos(unsigned char x, unsigned char y);
void LCD_CLS(void);
void LCD_P8x16Str(unsigned char x,unsigned char y,unsigned char ch[]);
void LCD_P16x16Ch(unsigned char x,unsigned char y,unsigned char N);
void LCD_P6x8Str(unsigned char x,unsigned char y,unsigned char ch[]);
void Draw_BMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[]);
#ifdef __cplusplus
}
#endif
#endif
OLED.c
#include "stm32f10x_i2c.h"
#include "oled.h"
#include "delay.h"
#include "sys.h"
#define XLevelL 0x00
#define XLevelH 0x10
#define XLevel ((XLevelH&0x0F)*16+XLevelL)
#define Max_Column 128
#define Max_Row 64
#define Brightness 0xCF
#define X_WIDTH 128
#define Y_WIDTH 64
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void OLED_Initial()
{
GPIO_InitTypeDef GPIO_InitStructure;
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
/* PB8,9 SCL and SDA */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
delay_ms(100); //延时
Write_IIC_Command(0xAE); //display off
Write_IIC_Command(0x20); //Set Memory Addressing Mode
Write_IIC_Command(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
Write_IIC_Command(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
Write_IIC_Command(0xc8); //Set COM Output Scan Direction
Write_IIC_Command(0x00);//---set low column address
Write_IIC_Command(0x10);//---set high column address
Write_IIC_Command(0x40);//--set start line address
Write_IIC_Command(0x81);//--set contrast control register
Write_IIC_Command(0x7f);
Write_IIC_Command(0xa1);//--set segment re-map 0 to 127
Write_IIC_Command(0xa6);//--set normal display
Write_IIC_Command(0xa8);//--set multiplex ratio(1 to 64)
Write_IIC_Command(0x3F);//
Write_IIC_Command(0xa4);//0xa4,Output follows RAM content;0xa5,Output ignores RAM content
Write_IIC_Command(0xd3);//-set display offset
Write_IIC_Command(0x00);//-not offset
Write_IIC_Command(0xd5);//--set display clock divide ratio/oscillator frequency
Write_IIC_Command(0xf0);//--set divide ratio
Write_IIC_Command(0xd9);//--set pre-charge period
Write_IIC_Command(0x22); //
Write_IIC_Command(0xda);//--set com pins hardware configuration
Write_IIC_Command(0x12);
Write_IIC_Command(0xdb);//--set vcomh
Write_IIC_Command(0x20);//0x20,0.77xVcc
Write_IIC_Command(0x8d);//--set DC-DC enable
Write_IIC_Command(0x14);//
Write_IIC_Command(0xaf);//--turn on oled panel
LCD_CLS();
LCD_Set_Pos(0,0);
}
/**********************************************
//IIC Start
**********************************************/
void IIC_Start()
{
SCL_HIGH;
SDA_HIGH;
SDA_LOW;
SCL_LOW;
}
/**********************************************
//IIC Stop
**********************************************/
void IIC_Stop()
{
SCL_LOW;
SDA_LOW;
SCL_HIGH;
SDA_HIGH;
}
/**********************************************
// IIC Write byte
**********************************************/
void Write_IIC_Byte(unsigned char IIC_Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
if(IIC_Byte&0x80) //1?0?
SDA_HIGH;
else
SDA_LOW;
SCL_HIGH;
SCL_LOW;
IIC_Byte<<=1; //loop
}
SDA_HIGH;
SCL_HIGH;
SCL_LOW;
}
/**********************************************
// IIC Write Command
**********************************************/
void Write_IIC_Command(unsigned char IIC_Command)
{
IIC_Start();
Write_IIC_Byte(0x78); //Slave address,SA0=0
Write_IIC_Byte(0x00); //write command
Write_IIC_Byte(IIC_Command);
IIC_Stop();
}
/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{
IIC_Start();
Write_IIC_Byte(0x78);
Write_IIC_Byte(0x40); //write data
Write_IIC_Byte(IIC_Data);
IIC_Stop();
}
/*********************LCD 设置坐标************************************/
void LCD_Set_Pos(unsigned char x, unsigned char y)
{
Write_IIC_Command(0xb0+y);
Write_IIC_Command(((x&0xf0)>>4)|0x10);
Write_IIC_Command((x&0x0f)|0x01);
}
/*********************LCD复位************************************/
void LCD_CLS(void)
{
unsigned char y,x;
for(y=0;y<8;y++)
{
Write_IIC_Command(0xb0+y);
Write_IIC_Command(0x01);
Write_IIC_Command(0x10);
for(x=0;x<X_WIDTH;x++)
Write_IIC_Data(0);
}
}
/***************功能描述:显示6*8一组标准ASCII字符串 显示的坐标(x,y),y为页范围0~7****************/
void LCD_P6x8Str(unsigned char x,unsigned char y,unsigned char ch[])
{
unsigned char c=0,i=0,j=0;
while (ch[j]!='\0')
{
c =ch[j]-32;
if(x>126){x=0;y++;}
LCD_Set_Pos(x,y);
for(i=0;i<6;i++)
Write_IIC_Data(F6x8[c][i]);
x+=6;
j++;
}
}
/*******************功能描述:显示8*16一组标准ASCII字符串 显示的坐标(x,y),y为页范围0~7****************/
void LCD_P8x16Str(unsigned char x,unsigned char y,unsigned char ch[])
{
unsigned char c=0,i=0,j=0;
while (ch[j]!='\0')
{
c =ch[j]-32;
if(x>120){x=0;y++;}
LCD_Set_Pos(x,y);
for(i=0;i<8;i++)
Write_IIC_Data(F8X16[c*16+i]);
LCD_Set_Pos(x,y+1);
for(i=0;i<8;i++)
Write_IIC_Data(F8X16[c*16+i+8]);
x+=8;
j++;
}
}
/*****************功能描述:显示16*16点阵 显示的坐标(x,y),y为页范围0~7****************************/
void LCD_P16x16Ch(unsigned char x,unsigned char y,unsigned char N)
{
unsigned char wm=0;
unsigned int adder=32*N; //
LCD_Set_Pos(x , y);
for(wm = 0;wm < 16;wm++) //
{
Write_IIC_Data(F16x16[adder]);
adder += 1;
}
LCD_Set_Pos(x,y + 1);
for(wm = 0;wm < 16;wm++) //
{
Write_IIC_Data(F16x16[adder]);
adder += 1;
}
}
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
void Draw_BMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0) y=y1/8;
else y=y1/8+1;
for(y=y0;y<y1;y++)
{
LCD_Set_Pos(x0,y);
for(x=x0;x<x1;x++)
{
Write_IIC_Data(BMP[j++]);
}
}
}
IIC.h
#ifndef __DSIIC_H
#define __DSIIC_H
#include <stm32f10x.h>
#include "stm32f10x_i2c.h"
#include "delay.h"
#include "public.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操作函数**********************************************/
#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];
/*************************************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 hou,u8 min,u8 sec);
void get_show_time(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)
extern u8 year,month,date,hour,minute,second;
#endif
IIC.c
#include "DS_IIC.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)
{
year=I2cByteRead(0xd0,0x06); //年
month=I2cByteRead(0xd0,0x05); //月
date=I2cByteRead(0xd0,0x04); //日
hour=I2cByteRead(0xd0,0x02); //时
minute=I2cByteRead(0xd0,0x01);//分
second=I2cByteRead(0xd0,0x00);//秒
}
/***********************************************************************************************
** 函数名称:ModifyTime
** 输入参数:yea,mon,da,hou,min,sec分别代表:年,月,日,时,分,秒
** 输出参数:无
** 功能描述:修改DS3231相关寄存器中时间数据
***********************************************************************************************/
void ModifyTime(u8 yea,u8 mon,u8 da,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(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)
{
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);
}
main.c
int main(void)
{
delay_init(24); //延时初始化函数
RCC_Configuration();
SPI2_GPIO_Config();
Alarm_GPIO_Config();
NVIC_Configuration();
OLED_Initial();
DS_IIC_Init_Func();
KEY_EXTI();
while(1)
{
if(G_un8TestFlag)
{
LED_1 = 1;
LED_2 = 1;
get_show_time();
G_un32WandRNum = date;
itoafunc(G_un32WandRNum);
LCD_P8x16Str(80,3,&NumStr[1]);
LCD_P8x16Str(68,3,":");
G_un32WandRNum = month;
itoafunc(G_un32WandRNum);
LCD_P8x16Str(52,3,&NumStr[1]);
LCD_P8x16Str(44,3,":");
G_un32WandRNum = year;
itoafunc(G_un32WandRNum);
LCD_P8x16Str(24,3,&NumStr[1]);
G_un32WandRNum = second;
itoafunc(G_un32WandRNum);
LCD_P8x16Str(80,6,&NumStr[1]);
LCD_P8x16Str(68,6,":");
G_un32WandRNum = minute;
itoafunc(G_un32WandRNum);
LCD_P8x16Str(52,6,&NumStr[1]);
LCD_P8x16Str(44,6,":");
G_un32WandRNum = hour;
itoafunc(G_un32WandRNum);
LCD_P8x16Str(24,6,&NumStr[1]);
}
else
{
LED_1 = 0;
LED_2 = 0;
if(G_un32DelayTime == 100)
OLED_Initial();
G_un32DelayTime++;
LCD_P8x16Str(4,0,"RTC Testing...");
}
sleep(1);
}
}