STM32+BM8563时钟芯片不走时问题解决(含配置代码)

STM32+BM8563时钟芯片不走时问题解决(含配置代码)

一、寄存器

BM8563是一款低功耗CMOS实时时钟/日历芯片,它提供一个可编程的时钟输出,一个中断输出和一个掉电检测器,所有的地址和数据都通过I2C总线接口串行传递。最大总线速度为400Kbits/s,每次读写数据后,内嵌的字地址寄存器会自动递增。
BM8563有16个寄存器,其中11个是BCD格式。配置是要注意值范围,不能超出。更多具体应用请看官方手册。
在这里插入图片描述在这里插入图片描述

二、晶振

晶振选择非常重要。32.768不用说了,主要是ESR值,不能小了,也不能太大。按照厂家给出的30K肯定可以启振并稳定振荡。负载电容建议6pf,其实芯片内部已经接了一个负载电容,外面的负载电容是用来微调秒走时的准确性的。
特别提出,不要使用贴片的那种小的晶振,那种ESR才几十个欧姆,BM8563由于需要低功耗,所以输出的激励也很小,根本不能使这种低ESR的晶振启振。一定要按照官方给出的30K欧姆来选择晶振。

在这里插入图片描述

三、 典型应用图

晶振接的两个电容是调整秒针精度的。如果没有专业仪器调整,那就按官方建议的6pF和19pF,走时也不会太差。
SDA和SCL两根线必须加上拉电阻,4.7K或者10K即可。STM32对应管脚需配置成开漏输出模式。
在这里插入图片描述

四、IIC和BM8563初始化配置代码

下面代码是按照官方手册C代码进行修改来的,在STM32RCT6单片机上测试通过,
需要的可以直接复制拿去。一开始我也下载了其他代码,看起来很全面,但是却不好用,IIC部分时钟有点问题,读出数据是不对的。最后直接参照官方手册,写了下面代码。

#include "iic.h"

uint8_t twdata[9]={0x00,0x00,0x50,0x59,0x23,0x31,0x06,0x12,0x04};/*前2个数据用来设
置状态寄存器,后 7 个用来设置时间寄存器 */
uint8_t trdata[7]={0}; /*定义数组用来存储读取的时间数据 */
uint8_t asc[14]={0}; /*定义数组用来存储转换的 asc 码时间数据,供显示用 */
volatile uint8_t ack = 0;
volatile uint8_t bm_status = 0;//如果BM时钟芯片无应答,则为0,。应答正常则为1.



void IIC_SDA_SET(void)
{
	HAL_GPIO_WritePin(BM8563_SDA_GPIO_Port,BM8563_SDA_Pin,GPIO_PIN_SET);	 
}

void IIC_SDA_RESET(void)
{
	HAL_GPIO_WritePin(BM8563_SDA_GPIO_Port,BM8563_SDA_Pin,GPIO_PIN_RESET);	 
}

void IIC_SCL_SET(void)
{
	HAL_GPIO_WritePin(BM8563_SCL_GPIO_Port,BM8563_SCL_Pin,GPIO_PIN_SET);	 
}

void IIC_SCL_RESET(void)
{
	HAL_GPIO_WritePin(BM8563_SCL_GPIO_Port,BM8563_SCL_Pin,GPIO_PIN_RESET);	 
}

uint8_t IN_SDA(void)
{
	if(HAL_GPIO_ReadPin(BM8563_SDA_GPIO_Port,BM8563_SDA_Pin))
	{
		return 1;
	}
	else
	{
		return 0;	
	}
}
/******************************************************************************
* Function Name --> IIC启动
* Description   --> SCL高电平期间,SDA由高电平突变到低电平时启动总线
*                   SCL: __________
*                                  \__________
*                   SDA: _____
*                             \_______________
* Input         --> none
* Output        --> none
* Reaturn       --> none 
******************************************************************************/
void IIC_Start(void)
{
	IIC_SDA_SET();	//为SDA下降启动做准备
	Delay_1us();
	IIC_SCL_SET();	//在SCL高电平时,SDA为下降沿时候总线启动	
	Delay_Nus(10);
	IIC_SDA_RESET();	//突变,总线启动
	Delay_Nus(10);
	IIC_SCL_RESET();	
	Delay_Nus(2);
}
/******************************************************************************
* Function Name --> IIC停止
* Description   --> SCL高电平期间,SDA由低电平突变到高电平时停止总线
*                   SCL: ____________________
*                                  __________
*                   SDA: _________/
* Input         --> none
* Output        --> none
* Reaturn       --> none 
******************************************************************************/
void IIC_Stop(void)
{
	IIC_SCL_RESET();
	Delay_1us();
	IIC_SCL_SET();	//在SCL高电平时,SDA为上升沿时候总线停止
	Delay_Nus(10);
	IIC_SDA_SET();	//突变,总线停止
	Delay_Nus(10);
}
/******************************************************************************
* Function Name --> 主机向从机发送应答信号
* Description   --> 每从 BM8563 读取一个字节数据后都要发送应答信号
* Input         --> a:应答信号
*                      0:应答信号
*                      1:非应答信号
* Output        --> none
* Reaturn       --> none 
******************************************************************************/
void IIC_Ack(uint8_t a)
{
	if(a)	IIC_SDA_SET();	//放上应答信号电平
	else	IIC_SDA_RESET();
	Delay_Nus(10);
	IIC_SCL_SET();	//为SCL下降做准备
	Delay_Nus(10);
	IIC_SCL_RESET();	//突变,将应答信号发送过去
	Delay_Nus(2);
}
/******************************************************************************
* Function Name --> 向IIC总线发送一个字节数据
* Description   --> 向 BM8563 写一个字节的数据
* Input         --> dat:要发送的数据
* Output        --> none
* Reaturn       --> ack:返回应答信号
******************************************************************************/
void IIC_Write_Byte(uint8_t dat)
{
	uint8_t i;
	for(i=0;i<8;i++)
	{
		if((dat<<i)&0x80)
		{
			IIC_SDA_SET();	//判断发送位,先发送高位
		}
		else
		{
			IIC_SDA_RESET();
		}
		Delay_1us();
		IIC_SCL_SET();	//为SCL下降做准备
		Delay_Nus(10);
		IIC_SCL_RESET();	//突变,将数据位发送过去
	}	//字节发送完成,开始接收应答信号
	Delay_Nus(2);
	IIC_SDA_SET();	//释放数据线
	Delay_Nus(2);
	IIC_SCL_SET();	//为SCL下降做准备
	Delay_Nus(4);
	if(IN_SDA())//读取应答信号
	{
		ack = 0;
	}
	else
	{
		ack = 1;	
	}
	IIC_SCL_RESET();	
	Delay_Nus(2);
}

/******************************************************************************
* Function Name --> 从IIC总线上读取一个字节数据
* Description   --> none
* Input         --> none
* Output        --> none
* Reaturn       --> x:读取到的数据
******************************************************************************/
uint8_t IIC_Read_Byte(void)
{
	uint8_t i;
	uint8_t rect=0;

	IIC_SDA_SET();	//首先置数据线为高电平

	for(i=0;i<8;i++)
	{
		Delay_1us();		
		IIC_SCL_RESET();	
		Delay_Nus(10);			
		IIC_SCL_SET();	
		Delay_Nus(2);			
		rect=rect<<1;
		if(IN_SDA())
		{
			rect=rect+1;
		}
		Delay_Nus(2);	
	}
	IIC_SCL_RESET();	
	Delay_Nus(2);	
	return rect;	//返回读取到的数据
}


/********************************************************************
函 数 名: GetBM8563(void)
功 能:从 BM8563 的内部寄存器(时间、状态、报警等寄存器)读取数据
说 明:该程序函数用来读取 BM8563 的内部寄存器,譬如时间,报警,状态等寄存器
采用页写的方式,设置数据的个数为 no,no 参数设置为 1 就是单字节方式
调 用:Start_I2C(),SendByte(),RcvByte(),Ack_I2C(),Stop_I2C()
入口参数:sla(BM8563 从地址), suba(BM8563 内部寄存器地址)
*s(设置读取数据存储的指针), no(传输数据的个数)
返 回 值:有,用来鉴定传输成功否
***********************************************************************/
uint8_t GetBM8563(uint8_t sla,uint8_t suba,uint8_t *s,uint8_t no)
{
	uint8_t i;
	IIC_Start();
	IIC_Write_Byte(sla);
	if(ack==0)return(0);
	IIC_Write_Byte(suba);
	if(ack==0)return(0);
	IIC_Start();
	IIC_Write_Byte(sla+1);
	if(ack==0)return(0);
	for(i=0;i<no-1;i++)
	{
		*s=IIC_Read_Byte();
		IIC_Ack(0);
		s++;
	}
	*s=IIC_Read_Byte();
	IIC_Ack(1);
	IIC_Stop();//除最后一个字节外,其他都要从 MASTER 发应答。
	return(1);
}

/********************************************************************
函 数 名:SetBM8563(void)
功 能:设置 BM8563 的内部寄存器(时间,报警等寄存器)
说 明:该程序函数用来设置 BM8563 的内部寄存器,譬如时间,报警,状态等寄存器
采用页写的方式,设置数据的个数为 no,no 参数设置为 1 就是单字节方式
调 用:Start_I2C(),SendByte(),Stop_I2C()
入口参数:sla(BM8563 从地址), suba(BM8563 内部寄存器地址)
*s(设置初始化数据的指针), no(传输数据的个数)
返 回 值:有,用来鉴定传输成功否
***********************************************************************/
uint8_t SetBM8563(uint8_t sla,uint8_t suba,uint8_t *s,uint8_t no)
{
	uint8_t i;
	IIC_Start();
	IIC_Write_Byte(sla);
	if(ack==0)return(0);
	IIC_Write_Byte(suba);
	if(ack==0)return(0);
	for(i=0;i<no;i++)
	{
		IIC_Write_Byte(*s);
		if(ack==0)return(0);
		s++;
	}
	IIC_Stop();
	return(1);
}

/********************************************************************
函 数 名:void Bcd2asc(void)
功 能:bcd 码转换成 asc 码,供液晶显示用
说 明:
调 用:
入口参数:
返 回 值:无
***********************************************************************/
void Bcd2asc(void)
{
	uint8_t i,j;
	for (j=0,i=0; i<7; i++)
	{
		asc[j++] =(trdata[i]&0xf0)>>4|0x30 ;/*格式为: 秒 分 时 日 月 星期 年 */
		asc[j++] =(trdata[i]&0x0f)|0x30;
	}
}

/********************************************************************
函 数 名:datajust(void)
功 能:将读出的时间数据的无关位屏蔽掉
说 明:BM8563 时钟寄存器中有些是无关位,可以将无效位屏蔽掉
调 用:
入口参数:
返 回 值:无
***********************************************************************/
void datajust(void)
{
	trdata[0] = trdata[0]&0x7f;
	trdata[1] = trdata[1]&0x7f;
	trdata[2] = trdata[2]&0x3f;
	trdata[3] = trdata[3]&0x3f;
	trdata[4] = trdata[4]&0x07;
	trdata[5] = trdata[5]&0x1f;
	trdata[6] = trdata[6]&0xff;
}
/********************************************************************
函 数 名:Set_Start_BM8563(void)
功 能:配置启动BM8563
说 明:
调 用:
入口参数:
返 回 值:无
***********************************************************************/

void Set_Start_BM8563(void)
{
	printf_sz_hex(twdata,9);
	do
	{
		bm_status = SetBM8563(0xa2,0x00,twdata,0x09);//设置时间和日期
		printf("设置时间\r\n");
		printf_sz_hex(twdata,9);
	}
	while(bm_status==0);
	printf("设置成功\r\n");
}

下面是printf重定向到串口,串口输出十六进制数组函数。

/*重定义Printf函数到串口3*/
int fputc(int ch,FILE *f)
{
	HAL_UART_Transmit(&huart3,(uint8_t *)&ch,1,0xFFFF);
    return ch;
}


/*以十六进制格式输出数组*/
void printf_sz_hex(uint8_t *pdata,uint32_t len)
{
	printf("\r\n");
	for(uint32_t i=0;i<len;i++)
	{
		printf("%02X ", *(pdata+i));//十六进制格式输出,两位,高位没有则补0,X字母大写,x字母小写
	}
	printf("\r\n");
}

然后在main函数里调用void Set_Start_BM8563(void)即可。

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  HAL_Delay(1000);
  Set_Start_BM8563();	//设置时间、日期
  while (1)
  {
	  HAL_Delay(1000);
	  HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);	  
	do{
		bm_status=GetBM8563(0xa2,0x02,trdata,0x07);//测试读取时间、日期
	  }
	while(bm_status==0);
	datajust();
	Bcd2asc();
	printf_sz_hex(asc,14);	//打印时间、日期
  }
}

五、测试结果

串口助手每秒打印输出一次时间日期。可以看出,读出来的秒是走的。开始时用贴片3225封装的32.768K晶振,怎么弄也不走,最后查了手册需要30K欧ESR的晶振,然后又采购换上了这种圆柱体的,测试很稳定。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述最后,如果晶振不起振,IIC一样可以读写寄存器,读写寄存器不依赖32.768K晶振。
遇到什么问题,一定要仔细阅读官方手册。也欢迎大家交流。

  • 3
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydgd118

您的鼓励是我最大的动力!谢赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值