STM32+BM8563时钟芯片不走时问题解决(含配置代码)_bm8563esa stm32 代码-程序员宅基地

技术标签: stm32  STM32系列  单片机  

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晶振。
遇到什么问题,一定要仔细阅读官方手册。也欢迎大家交流。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ydgd118/article/details/111401943

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法