前言
因华大单片机没有单独VBAT管脚,无法使用,如果用单片机自带的RTC模块,系统断电后时间无法准确,需要重新设置,影响用户体验,说以系统加入单独的RTC芯片.
RTC时钟电路
单片机管脚定义
RTC芯片为 [BL(上海贝岭) 的 BM8563ESA 实时时钟RTC
BCD码 百度百科解释
BCD码(Binary-Coded Decimal),用4位二进制数来表示1位十进制数中的0~9这10个数码,是一种二进制的数字编码形式,用二进制编码的十进制代码。BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。
我的简单解释: 比如说02H地址寄存器中的数据表示秒 Bit7 是无效的 ,Bit0–Bit 6为有效的 . 0x00 至 0x59 ,0x00表示0秒 0x59是16进制数据,但是他表示59秒.程序中需要转换.
BM8563ESA驱动程序编写
BM8563ESA 是I2C驱动,标准的I2C驱动可以使用
#include "drvs.h"
//时钟芯片
// RTC_SCL PB05
// RTC_SDA PB04
#define RTC_SCL_COM PortB,Pin05
#define RTC_SDA_COM PortB,Pin04
#define RTC_SDA_IN() RTC_SET_SDA_IN()
#define RTC_SDA_OUT() RTC_SET_SDA_OUT()
//IO操作函数
#define RTC_IIC_SCL(v) v == Disable ? PORT_ResetBits(RTC_SCL_COM): PORT_SetBits(RTC_SCL_COM) //SCL
#define RTC_IIC_SDA(v) v == Disable ? PORT_ResetBits(RTC_SDA_COM): PORT_SetBits(RTC_SDA_COM) //SDA
#define RTC_READ_SDA PORT_GetBit(RTC_SDA_COM) //输入SDA
#define RTC_EE_DEV_ADDR 0xA2 //设备
static u8 ack = 0;
//硬件延时
void bsp_DelayUS(int us)
{
uint16_t i;
/*
CPU主频168MHz时,在内部Flash运行, MDK工程不优化。用台式示波器观测波形。
循环次数为5时,SCL频率 = 1.78MHz (读耗时: 92ms, 读写正常,但是用示波器探头碰上就读写失败。时序接近临界)
循环次数为10时,SCL频率 = 1.1MHz (读耗时: 138ms, 读速度: 118724B/s)
循环次数为30时,SCL频率 = 440KHz, SCL高电平时间1.0us,SCL低电平时间1.2us
上拉电阻选择2.2K欧时,SCL上升沿时间约0.5us,如果选4.7K欧,则上升沿约1us
实际应用选择400KHz左右的速率即可
*/
for (i = 0; i < 30*us; i++);
}
/*************************************************************************
* 函 数 名: bsp_InitI2C
* 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
* 形 参: 无
* 返 回 值: 无
**************************************************************************/
void RTC_bsp_InitI2C(void)
{
PORT_DebugPortSetting(TRST,Disable); //关闭JTDI 调试管脚
PORT_DebugPortSetting(TDO_SWO,Disable); //关闭NJTRST调试管脚
stc_port_init_t stcPortInit;
MEM_ZERO_STRUCT(stcPortInit);
stcPortInit.enPinMode = Pin_Mode_Out; //输出模式
stcPortInit.enPinOType = Pin_OType_Od; //漏极开路
stcPortInit.enExInt = Disable; //外部智力支持Disable
stcPortInit.enPullUp = Disable; //内部上拉电阻关闭
/* BL10 Port/Pin 初始化 */
PORT_Init(RTC_SCL_COM, &stcPortInit);// SCL
PORT_Init(RTC_SDA_COM, &stcPortInit);// SDA
}
void RTC_SET_SDA_IN(void)
{
// PORT_DebugPortSetting(TDO_SWO,Disable); //关闭NJTRST调试管脚
stc_port_init_t stcPortInit;
/*配置结构初始化*/
MEM_ZERO_STRUCT(stcPortInit);
//设置销模式
stcPortInit.enPinMode = Pin_Mode_In;//输入模式
stcPortInit.enExInt = Enable;//Enable//Disable
stcPortInit.enPullUp = Enable;//enPinDrv //内部上拉电阻使能
PORT_Init(RTC_SDA_COM, &stcPortInit);// SDA
}
void RTC_SET_SDA_OUT(void)
{
// PORT_DebugPortSetting(TDO_SWO,Disable); //关闭NJTRST调试管脚
stc_port_init_t stcPortInit;
/*配置结构初始化*/
MEM_ZERO_STRUCT(stcPortInit);
//设置销模式
stcPortInit.enPinMode = Pin_Mode_Out;//输入模式
stcPortInit.enPinOType = Pin_OType_Od; //漏极开路
stcPortInit.enExInt = Disable;//Enable//Disable
stcPortInit.enPullUp = Disable;//enPinDrv //内部上拉电阻使能
PORT_Init(RTC_SDA_COM, &stcPortInit);// SDA
}
//I2C驱动部分
//产生IIC起始信号
void RTC_IIC_Start(void)
{
RTC_SDA_OUT(); //sda线输出
RTC_IIC_SDA(Enable);
RTC_IIC_SCL(Enable);
bsp_DelayUS(4);
RTC_IIC_SDA(Disable);
bsp_DelayUS(4);
RTC_IIC_SCL(Disable);
}
//产生IIC停止信号
void RTC_IIC_Stop(void)
{
RTC_SDA_OUT();//sda线输出
RTC_IIC_SCL(Disable);
RTC_IIC_SDA(Disable);
bsp_DelayUS(4);
RTC_IIC_SCL(Enable);
bsp_DelayUS(4);
RTC_IIC_SDA(Enable);
bsp_DelayUS(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 RTC_IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
RTC_SDA_IN(); //SDA设置为输入
RTC_IIC_SDA(Enable);
bsp_DelayUS(1);
RTC_IIC_SCL(Enable);
bsp_DelayUS(1);
while(RTC_READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
RTC_IIC_Stop();
return 1;
}
}
RTC_IIC_SCL(Disable);//时钟输出0
return 0;
}
//产生ACK应答
void RTC_RTC_IIC_Ack(void)
{
RTC_IIC_SCL(Disable);
RTC_SDA_OUT();
RTC_IIC_SDA(Disable);
bsp_DelayUS(2);
RTC_IIC_SCL(Enable);
bsp_DelayUS(2);
RTC_IIC_SCL(Disable);
}
//不产生ACK应答
void RTC_IIC_NAck(void)
{
RTC_IIC_SCL(Disable);
RTC_SDA_OUT();
RTC_IIC_SDA(Enable);
bsp_DelayUS(2);
RTC_IIC_SCL(Enable);
bsp_DelayUS(2);
RTC_IIC_SCL(Disable);
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void RTC_IIC_Send_Byte(u8 txd)
{
u8 t;
RTC_SDA_OUT();
RTC_IIC_SCL(Disable);;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if(txd & 0x80)
{
RTC_IIC_SDA(Enable);
}
else
{
RTC_IIC_SDA(Disable);
}
bsp_DelayUS(2); //对TEA5767这三个延时都是必须的
RTC_IIC_SCL(Enable);
bsp_DelayUS(2);
RTC_IIC_SCL(Disable);
if(t == 7 )
{
RTC_IIC_SDA(Enable);
}
txd <<= 1; /* 左移一个bit */
bsp_DelayUS(2);
}
RTC_IIC_SDA(Enable);
RTC_IIC_SCL(Enable);
if(RTC_READ_SDA==1)
{
ack =0;
}
else
{
ack =1;
}
RTC_IIC_SCL(Disable);
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 RTC_IIC_Read_Byte(void)
{
unsigned char i,receive=0;
RTC_SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
RTC_IIC_SCL(Disable);
bsp_DelayUS(2);
RTC_IIC_SCL(Enable);
receive<<=1;
if(RTC_READ_SDA)receive++;
bsp_DelayUS(1);
RTC_IIC_SCL(Disable);
}
// if (!ack)
// IIC_NAck();//发送nACK
// else
// IIC_Ack(); //发送ACK
return receive;
}
BM8563ESA 驱动部分
/********************************************************************
函 数 名: GetBM8563(void)
功 能:从 BM8563 的内部寄存器(时间、状态、报警等寄存器)读取数据
说 明:该程序函数用来读取 BM8563 的内部寄存器,譬如时间,报警,状态等寄存器
采用页写的方式,设置数据的个数为 no,no 参数设置为 1 就是单字节方式
调 用:Start_I2C(),SendByte(),RcvByte(),Ack_I2C(),Stop_I2C()
入口参数:sla(BM8563 从地址), suba(BM8563 内部寄存器地址)
*s(设置读取数据存储的指针), no(传输数据的个数)
返 回 值:有,返回布尔量(bit)用来鉴定传输成功否
***********************************************************************/
u8 GetBM8563(uchar sla,uchar suba,uchar *s,uchar no)
{
uchar i;
RTC_IIC_Start();
RTC_IIC_Send_Byte(sla);
if(ack==0)return(0);
RTC_IIC_Send_Byte(suba);
if(ack==0)return(0);
RTC_IIC_Start();
RTC_IIC_Send_Byte(sla+1);
if(ack==0)return(0);
for(i=0;i<no-1;i++)
{
*s = RTC_IIC_Read_Byte();
RTC_RTC_IIC_Ack();
s++;
}
*s = RTC_IIC_Read_Byte();
RTC_RTC_IIC_Ack();
RTC_IIC_Stop();
return 1;
}
/********************************************************************
函 数 名:SetBM8563(void)
功 能:设置 BM8563 的内部寄存器(时间,报警等寄存器)
说 明:该程序函数用来设置 BM8563 的内部寄存器,譬如时间,报警,状态等寄存器
采用页写的方式,设置数据的个数为 no,no 参数设置为 1 就是单字节方式
调 用:Start_I2C(),SendByte(),Stop_I2C()
入口参数:sla(BM8563 从地址), suba(BM8563 内部寄存器地址)
*s(设置初始化数据的指针), no(传输数据的个数)
返 回 值:有,返回布尔量(bit)用来鉴定传输成功否
***********************************************************************/
u8 SetBM8563(uchar sla,uchar suba,uchar *s,uchar no)
{
uchar i;
RTC_IIC_Start();
RTC_IIC_Send_Byte(sla);
if(ack==0)return(0);
RTC_IIC_Send_Byte(suba);
if(ack==0)return(0);
for(i =0;i<no;i++)
{
RTC_IIC_Send_Byte(*s);
if(ack==0)return(0);
s++;
}
RTC_IIC_Stop();
return 1;
}
BM8563ESA 可用数据提取部分
因为我系统中至需要 年月日 时分秒
位操作宏的定义以及转换函数
#define GET_BIT(value,bit) ((value)&(1<<(bit))) //读取指定位
#define CPL_BIT(value,bit) ((value)^=(1<<(bit))) //取反指定位
#define SET0_BIT(value,bit) ((value)&=~(1<<(bit))) //把某个位置0
#define SET1_BIT(value,bit) ((value)|= (1<<(bit))) //把某个位置1
//设置数据的某些位的值
/*
value 需要设置的数据的指针
bitl 需要设置的位的低位
bith 需要设置的位的高位
data 需要设置的数据
*/
uchar SET_DATA(uchar value,unsigned int bitl,unsigned int bith,uchar data)
{
uchar v = value;
if(bitl<=bith)
{
unsigned int bcount = bith-bitl+1;
unsigned int cbit=0;
unsigned int cdata=0;
for(unsigned int i=0;i<bcount;i++)
{
cdata |=(1<<i);
cbit |=(1<<(bitl+i));
}
v &=~(cbit);
v |=((data&cdata)<<bitl);
}
return v;
}
//把BCD码转成int型
int BCD2INT(uchar data)
{
int h = (data >> 4) & 0x0F;
int l = data & 0x0F;
int s = h*10 +l;
return s;
}
//把int型转换成BCD码
uchar INT2BCD(int data)
{
int h = data / 10;
int l = data % 10;
int s = h<<4 | l;
return s;
}
u8 Rtc_data[6];//最终时间数据 //0 年,月,日,时,分,秒
int bm8533_timeFlag =0;
//每500MS获取一次时间
void R_bm8533_TimeData(void)
{
static uchar trdata[7]; /*定义数组用来存储读取的时间数据 */
if(GetBM8563(RTC_EE_DEV_ADDR,0x02,trdata,0x07))
{
Rtc_data[5] = BCD2INT(SET_DATA(trdata[0],7,7,0));//秒 //把trdata[0] 7到7位设置成0在
Rtc_data[4] = BCD2INT(SET_DATA(trdata[1],7,7,0));//分
Rtc_data[3] = BCD2INT(SET_DATA(trdata[2],6,7,0));//时
Rtc_data[2] = BCD2INT(SET_DATA(trdata[3],6,7,0));//日
Rtc_data[1] = BCD2INT(SET_DATA(trdata[5],5,7,0));//月
Rtc_data[0] = BCD2INT(trdata[6]);//年
bm8533_timeFlag =1; //接收转换完成
}
}
//RTC读时间驱动
int bm8533_r(int fd,void *buf,int len)
{
if(bm8533_timeFlag==1)
{
bm8533_timeFlag = 0;
char * data =(char *)buf;
memcpy(data,Rtc_data,sizeof(Rtc_data));
return sizeof(Rtc_data);
}
return -1;
}
//设置时间我们是一次性从00H寄存器到08H寄存器一共9个数据
uchar cs_wrdata[9];
//RTC写时间驱动
int bm8533_w(int fd,void *buf,int len)
{
int * data =(int *)buf;
for(int i =0;i<len;i++)
{
cs_wrdata[i] = INT2BCD(data[i]);//把int行年月日时分秒转换成BCD码,写入到RTC兴平
}
SetBM8563(RTC_EE_DEV_ADDR,0x00,cs_wrdata,len);
return len;
}
//驱动初始化
void RTC_Init_IIC(void)
{
RTC_bsp_InitI2C(); //初始化驱动
rxm_reg_r(DEV_RTC,&bm8533_r); //注册读时间驱动
rxm_reg_w(DEV_RTC,&bm8533_w); //注册写时间驱动
rxm_addtim(500,&R_bm8533_TimeData);//软件定时器周期调用
}
DRV_INIT(RTC_Init_IIC); //装载函数并使用
UI层调用/
//计算星期
//iY:年 iM:月 iD:日 返回当前月份
int getWeekdayByYearday(int iY, int iM, int iD)
{
int iWeekDay = -1;
if (1 == iM || 2 == iM)
{
iM += 12;
iY--;
}
iWeekDay = (iD + 1 + 2 * iM + 3 * (iM + 1) / 5 + iY + iY / 4 - iY / 100 + iY / 400) % 7;
return iWeekDay;
}
int WRtcData[9]={0,0,0,13,10,8,2,2,22};
//确定按键
void UI_OK_btn_key(int key)
{
WRtcData[8] = rxw_GetEditValue(ctl_year_val); //年
WRtcData[7] = rxw_GetEditValue(ctl_month_val); //月
WRtcData[5] = rxw_GetEditValue(ctl_day_val); //日
WRtcData[4] = rxw_GetEditValue(ctl_hour_val); //时
WRtcData[3] = rxw_GetEditValue(ctl_minute_val); //分
WRtcData[2] = rxw_GetEditValue(ctl_second_val); //秒
WRtcData[6] = getWeekdayByYearday(WRtcData[8],WRtcData[7],WRtcData[5]);//星期
rxm_w(DEV_RTC,WRtcData,9);//设置时间 //把WRtcData 数据写入驱动层
}