华大单片机 HC32F460 驱动BM8563ESA RTC芯片

前言

因华大单片机没有单独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 数据写入驱动层
}

总结:

驱动层负责数据的转换和时间读写,UI层负载显示需要的时间和设置时间,不需要计算.

猜你喜欢

转载自blog.csdn.net/weixin_42839808/article/details/124375270