你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【STM32C0测评】Nucleo-C092开发板驱动DS1302实验

[复制链接]
C70EH 发布时间:2025-6-23 17:36

前面几期麻烦点开头像找到分享查看,而且本期测评的视频已经发布,可以前往Blibli查看,链接如下:

https://www.bilibili.com/video/BV1NeEizrEQB

欢迎各位同志前往观看!

今天我来开源一个DS1302的模块代码,我们采用Nucleo-C092RC来驱动,那么让我们开始吧。

在这里我们先来看一下程序的现象,将代码烧到板卡中:

上电现象.jpg

这时你可以看到现在电脑开始接收到年、月、日、时、分、秒这些必要的信息,代码特意写了跨年,可以看到各个数据在跨年中的转换也是正常的。这时LD1和LD2交替闪烁,这就是程序现象了。

上电现象2.png

交替闪烁1.jpg

交替闪烁2.jpg

这段程序最开始我参考了Arduino中的库来移植出来的。源代码的缺点是它的输出是一整个字符串输出,如果你需要把它显示在屏幕或者数码管上,你需要用命令将字符串切割,并且将切割后的字符串转换成数字;而我这个程序输出直接就是数字,方便显示和使用数据,且方便你将它移植到其它需要用到的不同I/O口及不同的MCU中。

这个自定义库.H文件里面一共有十个与外部代码交互的函数,分别是DS1302的初始化,写入初始时间1(时分秒设置),写入初始时间2(年月日周设置),从DS1302中读出时分秒,年月日周的各种数据命令。

库文件如下:

/*使用STM32C092RCT6驱动DS1302的库文件,支持从DS1302中写入、读取时间,24小时制
C70E敞车  2025年5月11日移植成功。
*/

#ifndef __STM32C092_DS1302_H
#define __STM32C092_DS1302_H
#include "main.h"

/*下方宏定义为DS1302引脚信息,改动引脚在此处修改.*/
#define DS1302_DAT_Pin GPIO_PIN_11
#define DS1302_DAT_GPIO_Port GPIOC
#define DS1302_CE_Pin GPIO_PIN_12
#define DS1302_CE_GPIO_Port GPIOC
#define DS1302_CLK_Pin GPIO_PIN_10
#define DS1302_CLK_GPIO_Port GPIOC

/******************************************************************************/
/* Include files                                                              */
/******************************************************************************/
void DS1302_Init(uint8_t Freq);//DS1302初始化(当前配置单片机频率)

void DS1302_SetTime1(uint8_t Hou,uint8_t Min,uint8_t Sec);//DS1302_SetTime1(时,分,秒),写入初始时间
void DS1302_SetTime2(int Yea,uint8_t Mou,uint8_t Date,uint8_t Week);//DS1302_SetTime1(年,月,日,周),写入初始时间

uint8_t DS1302_ReadSec(void);//DS1302读出秒数据
uint8_t DS1302_ReadMin(void);//DS1302读出分数据
uint8_t DS1302_ReadHour(void);//DS1302读出时数据
uint8_t DS1302_ReadDay(void);//DS1302读出日数据
uint8_t DS1302_ReadWeek(void);//DS1302读出周数据
uint8_t DS1302_ReadMouth(void);//DS1302读出月数据
int DS1302_ReadYear(void);//DS1302读出年数据(2000年开始)

#endif

这个自定义库.C文件里定义了各个GPIO的高低电平以及初始化,以及DAT线处于输入或输出模式的初始化,以方便完成数据的收发,然后就是延时、转换及其他的相关命令代码。

库文件如下:

/*使用STM32C092RCT6驱动DS1302的库文件,支持从DS1302中写入、读取时间,24小时制
C70E敞车  2025年5月11日移植成功。
*/

#include "STM32C092_DS1302.h"

uint8_t freq;

/*下方宏定义为DS1302所有寄存器地址,增加程序可读性,下方所有寄存器地址均为写入地址,读出地址在写入地址基础上+1.
  例如DS1302_SecADD写地址为0x80,则读地址为0x81.*/
#define DS1302_SecADD 0x80    //秒寄存器地址
#define DS1302_MinADD 0x82    //分寄存器地址
#define DS1302_HourADD 0x84   //时寄存器地址
#define DS1302_DayADD 0x86    //日寄存器地址
#define DS1302_MouADD 0x88    //月寄存器地址
#define DS1302_WeekADD 0x8A   //周寄存器地址
#define DS1302_YearADD 0x8C   //年寄存器地址
#define DS1302_WriteProtect 0x8E//写保护寄存器地址
#define DS1302_Bat 0x90       //涓流充电寄存器地址

/*CLK,CE脚高电平/低电平*/
#define DS1302_CLK_1  HAL_GPIO_WritePin(DS1302_CLK_GPIO_Port, DS1302_CLK_Pin, GPIO_PIN_SET); //定义CLK线高电平
#define DS1302_CLK_0  HAL_GPIO_WritePin(DS1302_CLK_GPIO_Port, DS1302_CLK_Pin, GPIO_PIN_RESET);  //定义CLK线低电平
#define DS1302_CE_1  HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET);   //定义使能线高电平
#define DS1302_CE_0  HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET);     //定义使能线低电平

/*【无返回值无参数】DS1302配置GPIO初始化.*/
void DS1302_GPIO_Init(void){
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOC_CLK_ENABLE();
    HAL_GPIO_WritePin(GPIOC, DS1302_DAT_Pin|DS1302_CE_Pin|DS1302_CLK_Pin, GPIO_PIN_SET);
    GPIO_InitStruct.Pin = DS1302_DAT_Pin|DS1302_CE_Pin|DS1302_CLK_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}

/*【无返回值无参数】DAT线配置成输出模式.*/
void DS1302_DAT_OUT(void){
    GPIO_InitTypeDef GPIO_InitStruct = {0};  
     __HAL_RCC_GPIOC_CLK_ENABLE(); // 使能GPIO时钟
  GPIO_InitStruct.Pin = DS1302_DAT_Pin;                    
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;       
  GPIO_InitStruct.Pull = GPIO_NOPULL;               
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;     
  HAL_GPIO_Init(DS1302_DAT_GPIO_Port, &GPIO_InitStruct);   
}

/*【无返回值无参数】DAT线配置成输入模式.*/
void DS1302_DAT_IN(void){
     GPIO_InitTypeDef GPIO_InitStruct = {0}; 
 __HAL_RCC_GPIOC_CLK_ENABLE(); // 使能GPIO时钟   
  GPIO_InitStruct.Pin = DS1302_DAT_Pin;                    
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;       
  GPIO_InitStruct.Pull = GPIO_NOPULL;               
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;     
  HAL_GPIO_Init(DS1302_DAT_GPIO_Port, &GPIO_InitStruct);     
}
/*【无返回值无参数】配置微秒延时.*/
void Delay_us(uint32_t  __us) {
     __us*=freq/4;
    while(__us){
        __NOP();
        __us--;
   }
    }

/*【有返回值有参数】十进制转换BCD.*/
int dec_bcd(int dec)
{
   return ((dec / 10) << 4 | (dec % 10));
}
/*【有返回值有参数】BCD转换十进制.*/
int bcd_dec(int bcd)
{
  return ((bcd & 0xF0) >> 4) * 10 + (bcd & 0x0F);
}
/*【有参数无返回值】DS1302写入一个字节*/
void DS1302_WriteByte(uint8_t dat){  
    DS1302_CLK_0;//初始时钟线置位0
  for (uint8_t i = 0; i < 8; i++){ //开始传输8个字节的数据
    DS1302_DAT_OUT();//把DAT脚配置成输出模式
        if(dat & 0x01){
            HAL_GPIO_WritePin(DS1302_DAT_GPIO_Port, DS1302_DAT_Pin, GPIO_PIN_SET); 
        }
        else{
            HAL_GPIO_WritePin(DS1302_DAT_GPIO_Port, DS1302_DAT_Pin, GPIO_PIN_RESET); 
        }
    //digitalWrite(DS1302_DAT, dat & 0x01);
    dat=dat>>1; //数据右移一位,准备传输下一位数据
    Delay_us(2); 
    DS1302_CLK_1; //时钟线拉高,制造上升沿,SDA的数据被传输
    Delay_us(2);
    DS1302_CLK_0; //时钟线拉低,为下一个上升沿做准备
  }
}
/*【无参数有返回值】DS1302读取一个字节*/
int DS1302_ReadByte(){
int dat=0;
for(uint8_t i=0;i<8;i++){  //开始传输8个字节的数据
DS1302_DAT_IN();  //把DAT引脚配置成输入模式
dat=dat>>1;//要返回的数据右移一位
if(HAL_GPIO_ReadPin(DS1302_DAT_GPIO_Port,DS1302_DAT_Pin)==1){//当数据线为高时,证明该位数据为 1
  dat=dat|0x80;//要传输数据的当前值置为1
}
  DS1302_CLK_1;//拉高时钟线
  Delay_us(2);
  DS1302_CLK_0;//制造下降沿
  Delay_us(2);
}
return dat;//返回读取出的数据
}
/*【有参数无返回值】DS1302写入字节*/
void DS1302_Write(uint8_t add,uint8_t dat_Write){
  DS1302_CE_0;                //初始使能线置0
  DS1302_CLK_0;               //初始时钟线置为0
  DS1302_CE_1;                //初始使能线置1,传输开始
  Delay_us(2);
  DS1302_WriteByte(add);      //写入需要写入的地址信息
  DS1302_WriteByte(dat_Write);//写入需要写入的数据(BCD码格式写入)
  DS1302_CLK_1;               //传输结束,CLK线置1
  DS1302_CE_0;                //传输结束,使能线置0
}
/*【有参数有返回值】DS1302读取字节*/
int DS1302_Read(uint8_t add){
  int dat_Read=0; 
  DS1302_CE_0;               //初始使能线置0
  DS1302_CLK_0;              //初始时钟线置为0
  DS1302_CE_1;               //初始使能线置1,传输开始
  Delay_us(2);
  DS1302_WriteByte(add+1);     //写入需要读取的地址
  dat_Read=DS1302_ReadByte();  //把DS1302_ReadByte()命令中返回的值赋值给dat_Read
  DS1302_CLK_1;              //传输结束,CLK线置1
  DS1302_CE_0;               //传输结束,使能线置0
  return dat_Read;           //返回读取出的dat_Read数值
}
/*【无参数有返回值】DS1302去除写保护*/
void DS1302_ResetProtect(){
 DS1302_Write(DS1302_WriteProtect,0x00); //写入地址为1302写保护位,写入数据为0000 0000,去除写保护
}
/*【无参数有返回值】DS1302加上写保护*/
void DS1302_SetProtect(){
 DS1302_Write(DS1302_WriteProtect,0x80); //写入地址为1302写保护位,写入数据为1000 0000,加上写保护
}
/*【无参数无返回值】DS1302写入禁用涓流充电,模组上CR2032为不可充电电池,需要禁止充电功能*/
void DS1302_NotBat(){
  //pinMode(DS1302_CLK,OUTPUT);
  //pinMode(DS1302_CE,OUTPUT);
  DS1302_ResetProtect();        //复位写保护
  DS1302_Write(DS1302_Bat,0x00);//写入地址为DS1302涓流充电控制,写入数据为0000 0000,禁止涓流充电功能
  DS1302_SetProtect();          //置位写保护
}
/*【无参数无返回值】DS1302初始化*/
void DS1302_Init(uint8_t Freq){
    DS1302_GPIO_Init();//初始化DS1302引脚
    freq=Freq;//给微秒延时一个主频频率数
    DS1302_NotBat();//关掉DS1302涓流充电
}
/*【有参数无返回值】DS1302写入时 分 秒数据*/
void DS1302_SetTime1(uint8_t Hou,uint8_t Min,uint8_t Sec){  //DS1302_SetTime1(时,分,秒),写入初始时间
  Hou = dec_bcd(Hou);
  Min=dec_bcd(Min);//把分钟从十进制转换成BCD格式并赋值给分钟
  Sec=dec_bcd(Sec);//把秒从十进制转换成BCD格式并赋值给秒
  DS1302_ResetProtect(); //复位写保护
  DS1302_Write(DS1302_HourADD,Hou);//写入地址为1302小时位,写入数据为转换成BCD码格式的小时
  DS1302_Write(DS1302_MinADD,Min); //写入地址为1302分钟位,写入数据为转换成BCD码格式的分钟
  DS1302_Write(DS1302_SecADD,Sec); //写入地址为1302秒位,写入数据为转换成BCD码格式的秒
  DS1302_SetProtect();  //置位写保护
 }
/*【有参数无返回值】DS1302写入年 月 日 周数据*/
void DS1302_SetTime2(int Yea,uint8_t Mou,uint8_t Date,uint8_t Week){//DS1302_SetTime1(年,月,日,周),写入初始时间
    Yea = dec_bcd(Yea - 2000); //把年从十进制转换成BCD码并赋值给年
    Mou = dec_bcd(Mou);      //把月从十进制转换成BCD码并赋值给月
  Date = dec_bcd(Date);    //把日从十进制转换成BCD码并赋值给日
  Week = dec_bcd(Week);    //把周从十进制转换成BCD码并赋值给周
  DS1302_ResetProtect();
  DS1302_Write(DS1302_YearADD,Yea);  
  DS1302_Write(DS1302_MouADD,Mou);
  DS1302_Write(DS1302_DayADD,Date);
  DS1302_Write(DS1302_WeekADD,Week);
  DS1302_SetProtect(); 
}
/*【无参数有返回值】DS1302读出秒数据*/
uint8_t DS1302_ReadSec(void){
 int dat=bcd_dec(DS1302_Read(DS1302_SecADD));
 return dat;
}
/*【无参数有返回值】DS1302读出分数据*/
uint8_t DS1302_ReadMin(void){
 int dat=bcd_dec(DS1302_Read(DS1302_MinADD));
 return dat;
}
/*【无参数有返回值】DS1302读出时数据*/
uint8_t DS1302_ReadHour(void){
 uint8_t dat = DS1302_Read(DS1302_HourADD);
  if (dat >= 0x80) {
    dat = dat - 0x80;
    if (dat >= 0x20) {
      dat = dat - 0x20;
    }
  }
  dat = bcd_dec(dat);
  return dat;
}

/*【无参数有返回值】DS1302读出日数据*/
uint8_t DS1302_ReadDay(void) {
  uint8_t dat = bcd_dec(DS1302_Read(DS1302_DayADD));
  return dat;
}
/*【无参数有返回值】DS1302读出周数据*/
uint8_t DS1302_ReadWeek(void) {
  uint8_t dat = bcd_dec(DS1302_Read(DS1302_WeekADD));
  return dat;
}
/*【无参数有返回值】DS1302读出月数据*/
uint8_t DS1302_ReadMouth(void) {
  uint8_t dat = bcd_dec(DS1302_Read(DS1302_MouADD));
  return dat;
}
/*【无参数有返回值】DS1302读出年数据*/
int DS1302_ReadYear(void) {
  int dat = bcd_dec(DS1302_Read(DS1302_YearADD)) + 2000;
  return dat;
}

建议在DS1302的硬件部分加上上拉电阻,以保证DS1302通信稳定性——我手上的这一片DS1302是兼容片,它可以在5V时正常工作,但只要用3.3V供电,它就会通信错误。但是我有一次无意之中实验中发现,在CLK对地接一个差不多20到22PF的电容,在3.3V的情况下又可以工作了。

封面.jpg

所以我在这个板子上预留了电容位置。如果说你的1302的模组不存在这个问题,就可以不用加电容,如果存在的话,你可以尝试一下我的方法,或许你通信错误的问题就能得到解决;还有就是我对这个晶振也进行了一下处理,晶振电路我没有无脑铺铜,我之前做过DS1302的模组,铺铜产生的寄生电容会把晶振的振荡频率拖慢,宏观上看就是RTC在一天之后就慢个两三秒钟,DS1302如果说你要是校准的好的话,是可以校的比较准的。

以下是main函数中的代码:

/*
  *作者:下行路轨上的C70E 论坛ID钟详
    *Nucleo-C092 demo程序任务11:使用DS1302 RTC模组设置时间后读取当前片内时间,将收到的数据发出给串口助手,同时控制LED交替闪烁。
    *采用24小时制来进行计时。
    *使用意法半导体Nucleo板卡,配置外置晶体48M,波特率9600

    *前往龙安的列车即将到达,请站在黄线外排队
*/

/*包含所需头文件*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

#include "STM32C092_DS1302.h"
#include "stdio.h"
/*函数声明*/
void SystemClock_Config(void);
/*主函数*/

int main(void)
{
    HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART2_UART_Init();

    DS1302_Init(48);//DS1302初始化

/*打印作者信息*/
    printf("\r\nST Chinese Forum Evaluation Plan\r\n");
    printf("\r\nBoard Mode:Nucleo-C092\r\n");
    printf("\r\nDemo11:DS1302 RTC Test \r\n");
    printf("\r\nReviewer:Xiang Zhong Bli:C70E\r\n");
    DS1302_SetTime1(23,59,57);//设置时间23时59分57秒
    DS1302_SetTime2(2025,5,11,7);//设置2025年5月11日周天
  /*进入循环体*/
  while (1){
    HAL_GPIO_TogglePin(User_LED1_GPIO_Port,User_LED1_Pin);
        printf("Year: %d\r\n", DS1302_ReadYear());
        printf("Mouth: %d\r\n", DS1302_ReadMouth());
        printf("Day: %d\r\n", DS1302_ReadDay());
        printf("Week: %d\r\n", DS1302_ReadWeek());
      printf("Time: %d:%d:%d\r\n", DS1302_ReadHour(),DS1302_ReadMin(),DS1302_ReadSec());
        HAL_GPIO_TogglePin(User_LED2_GPIO_Port,User_LED2_Pin);
        HAL_Delay(980);
  }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
}

/*printf函数支持(阻塞法打印数据)*/
int fputc(int ch,FILE *f)
{
  HAL_UART_Transmit(&huart2,(uint8_t *)&ch,1,0xFFFF);//阻塞方式打印
  return ch;
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

这里简单说明DS1302的校准原理,就是当你这个模组做出来之后,你通电让它运行一天,你看一下一天之内它是快了还是慢了,如果说你的时钟快了,那说明你晶振频率较高,你就需要加负载电容;但是如果如果说你的时钟慢了,就证明你的负载电容+杂散电容的总和稍微大了。当然也不可能完全校准,这是一种大致判断负载电容是否合适的方法。DS1302适配的是精度为±5PPM,负载电容为6PF的32768晶振。如果说你用的是精度为±5PPM,负载电容为12.5PF的32768晶振,那你就需要加在外部加两个负载电容,让外部补偿掉是6.5PF左右,以获得准确的时间。不过值得注意的是,使用负载电容后DS1302启动一定会被拖慢一点点。

这就是我对D1302的理解,希望对使用这个模块的同志们能有所帮助。不过像这种外置晶振RTC时钟来说的话它的精度确实不是那么好。但是它很便宜!不管是在某宝还是某创商城都差不多1.5-1.7就能买到一片。在精度要求不高或者可以联网获取时间的场合,你可以用它来计时,但是需要经常性的对时,用以消除它的误差。

以下是Nucleo板卡的CubeMX中的配置部分基本参考前面分享的几期,如下:

GPIO配置.png

串口配置.png

时钟树.png

其实,这些代码和视频你可以看出,5月就搞好了,但是那时候很忙,就草草发布了视频到B站,没空更新。最近我们的上岗证考试刚刚复审结束,我前段时间都在忙考试的事情,咕咕咕了,现在闲下来,就给它更新掉。

收藏 评论0 发布时间:2025-6-23 17:36

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版