dirtwillfly 发表于 2025-9-28 13:41

国产risc-v微控制器读取DS3231

本帖最后由 dirtwillfly 于 2025-9-28 20:30 编辑

第一部分:DS3231介绍
1.1 特性
DS3231 是一款 I2C 实时时钟 (RTC),具有内置温度补偿晶体振荡器 (TCXO) 和低成本且异常精确的晶体。当模块的电源中断时,设备有电池输入并保持精确的时间。该器件的长期精度因包含晶体振荡器而得到提高。RTC 跟踪秒、分钟、小时、天、日期、月和年。对于少于 31 天的月份,月末日期会自动修改,包括闰年更正。时钟具有 AM/PM 指示,可在 24 小时或 12 小时模式下工作。包括两个可编程的时间警报,以及一个可编程的方波输出。I2C 双向总线用于串行传输地址和数据。


关键特性:
• 高精确时钟 :具有+/-2ppm(百万分之二)的精度,从0°C到+40°C。
• 温度补偿 :内置温度传感器,能够自动补偿时钟频率偏移。
• 集成备份电池 :当主电源失效时,内置锂电池自动切换,保证时钟继续运行

1.2 参数

[*]0°C至+40°C范围内精度为±2ppm
[*]-40°C至+85°C范围内精度为±3.5ppm
[*]为连续计时提供电池备份输入
[*]工作温度范围

[*]商用级:0°C至+70°C
[*]工业级:-40°C至+85°C
[*]低功耗
[*]实时时钟产生秒、分、时、星期、日期、月和年计时,并提供有效期到2100年的闰年补偿
[*]两个日历闹钟
[*]可编程方波输出
[*]高速(400kHz) I²C接口
[*]工作在3.3V
[*]数字温度传感器输出:精度为±3°C
[*]老化修正寄存器
[*]/RST输出/按钮复位去抖输入


1.3 电路原理图
使用的模块:

与ds3231相关的原理图:

其中有一点要特别注意,因为原理图中VCC经过限流电阻和二极管连接到BAT,这是一个BAT的限流充电电路。所以这里的BAT电池必须使用可充电的CR2032。

第二部分:应用开发
2.1 硬件连接方式
使用的HPM5361开发板P1接口有两组I2C接口,这里使用I2C0接口,即PB03和PB02引脚。
https://bbs.21ic.com/data/attachment/forum/202507/27/204618f5qsg8tj7s7t85mi.png.thumb.jpg


这两个引脚,在开发板上已经有了10K的上拉电阻,使用的DS3231上也有上拉电阻,这里可以共存,不需要特殊处理。
https://bbs.21ic.com/data/attachment/forum/202507/27/204903dwvkdk9wzklt1wxv.png.thumb.jpg
2.2 驱动代码

#define   DS3231_ARRD         0x68    /* slave address */

#define   REG_SEC             0x00
#define   REG_MIN             0x01
#define   REG_HOUR            0x02
#define   REG_DAY             0x03
#define   REG_WEEK            0x04
#define   REG_MON             0x05
#define   REG_YEAR            0x06
#define   REG_ALM1_SEC      0x07
#define   REG_ALM1_MIN      0x08
#define   REG_ALM1_HOUR       0x09
#define   REG_ALM1_DAY_DATE   0x0A
#define   REG_ALM2_MIN      0x0B
#define   REG_ALM2_HOUR       0x0C
#define   REG_ALM2_DAY_DATE   0x0D
#define   REG_CONTROL         0x0E
#define   REG_STATUS          0x0F
#define   REG_AGING_OFFSET    0x10
#define   REG_TEMP_MSB      0x11
#define   REG_TEMP_LSB      0x12

#define DS3231_I2C_BUS      "i2c0"      /* i2c linked */
#define DS3231_DEVICE_NAME"rtc"       /* register device name */

static struct rt_device ds3231_dev; /* ds3231 device */

static unsigned char bcd_to_hex(unsigned char data)
{
    unsigned char temp;

    temp = ((data>>4)*10 + (data&0x0f));
    return temp;
}

static unsigned char hex_to_bcd(unsigned char data)
{
    unsigned char temp;

    temp = (((data/10)<<4) + (data%10));
    return temp;
}

static rt_err_tds3231_read_reg(rt_device_t dev, rt_uint8_t reg,rt_uint8_t *data,rt_uint8_t data_size)
{
    struct rt_i2c_msg msg;
    struct rt_i2c_bus_device *i2c_bus = RT_NULL;

    RT_ASSERT(dev != RT_NULL);

    i2c_bus = (struct rt_i2c_bus_device*)dev->user_data;
    msg.addr= DS3231_ARRD;
    msg.flags = RT_I2C_WR;
    msg.len   = 1;
    msg.buf   = &reg;
    msg.addr= DS3231_ARRD;
    msg.flags = RT_I2C_RD;
    msg.len   = data_size;
    msg.buf   = data;

    if(rt_i2c_transfer(i2c_bus, msg, 2) == 2)
    {
      return RT_EOK;
    }
    else
    {
      LOG_E("i2c bus read failed!\r\n");
      return -RT_ERROR;
    }
}

static rt_err_tds3231_write_reg(rt_device_t dev, rt_uint8_t reg, rt_uint8_t *data, rt_uint8_t data_size)
{
    struct rt_i2c_msg msg;
    struct rt_i2c_bus_device *i2c_bus = RT_NULL;

    RT_ASSERT(dev != RT_NULL);

    i2c_bus = (struct rt_i2c_bus_device*)dev->user_data;
    msg.addr   = DS3231_ARRD;
    msg.flags    = RT_I2C_WR;
    msg.len      = 1;
    msg.buf      = &reg;
    msg.addr   = DS3231_ARRD;
    msg.flags    = RT_I2C_WR | RT_I2C_NO_START;
    msg.len      = data_size;
    msg.buf      = data;
    if(rt_i2c_transfer(i2c_bus, msg, 2) == 2)
    {
      return RT_EOK;
    }
    else
    {
      LOG_E("i2c bus write failed!\r\n");
      return -RT_ERROR;
    }
}

static rt_err_t rt_ds3231_open(rt_device_t dev, rt_uint16_t flag)
{
    if (dev->rx_indicate != RT_NULL)
    {
      /* open interrupt */
    }

    return RT_EOK;
}

static rt_size_t rt_ds3231_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    return RT_EOK;
}

static rt_err_t rt_ds3231_control(rt_device_t dev, int cmd, void *args)
{
    rt_err_t    ret = RT_EOK;
    time_t      *time;
    struct tm   time_temp;
    rt_uint8_tbuff;

    RT_ASSERT(dev != RT_NULL);
    rt_memset(&time_temp, 0, sizeof(struct tm));

    switch (cmd)
    {
      /* read time */
      case RT_DEVICE_CTRL_RTC_GET_TIME:
            time = (time_t *)args;
            ret = ds3231_read_reg(dev, REG_SEC,buff,7);
            if(ret == RT_EOK)
            {
                time_temp.tm_year= bcd_to_hex(buff) + 2000 - 1900;
                time_temp.tm_mon   = bcd_to_hex(buff&0x7f) - 1;
                time_temp.tm_mday= bcd_to_hex(buff);
                time_temp.tm_hour= bcd_to_hex(buff);
                time_temp.tm_min   = bcd_to_hex(buff);
                time_temp.tm_sec   = bcd_to_hex(buff);
                *time = mktime(&time_temp);
            }
      break;

      /* set time */
      case RT_DEVICE_CTRL_RTC_SET_TIME:
      {
            struct tm *time_new;

            time = (time_t *)args;
            time_new = localtime(time);
            buff = hex_to_bcd(time_new->tm_year + 1900 - 2000);
            buff = hex_to_bcd(time_new->tm_mon + 1);
            buff = hex_to_bcd(time_new->tm_mday);
            buff = hex_to_bcd(time_new->tm_wday+1);
            buff = hex_to_bcd(time_new->tm_hour);
            buff = hex_to_bcd(time_new->tm_min);
            buff = hex_to_bcd(time_new->tm_sec);
            ret = ds3231_write_reg(dev, REG_SEC, buff, 7);
      }
      break;
    #ifdef RT_USING_ALARM
      /* get alarm time */
      case RT_DEVICE_CTRL_RTC_GET_ALARM:
      {
            struct rt_rtc_wkalarm *alm_time;

            ret = ds3231_read_reg(dev, REG_ALM1_SEC, buff, 4);
            if(ret == RT_EOK)
            {
                alm_time = (struct rt_rtc_wkalarm *)args;
                alm_time->tm_hour= bcd_to_hex(buff);
                alm_time->tm_min   = bcd_to_hex(buff);
                alm_time->tm_sec   = bcd_to_hex(buff);
            }
      }
      break;

      /* set alarm time */
      case RT_DEVICE_CTRL_RTC_SET_ALARM:
      {
            struct rt_rtc_wkalarm *alm_time;

            alm_time = (struct rt_rtc_wkalarm *)args;
            buff = 0x80; /* enable, alarm when hours, minutes, and seconds match */
            buff = hex_to_bcd(alm_time->tm_hour);
            buff = hex_to_bcd(alm_time->tm_min);
            buff = hex_to_bcd(alm_time->tm_sec);
            ret = ds3231_write_reg(dev, REG_ALM1_SEC, buff, 4);
      }
      break;
    #endif
      default:
      break;
    }
    return ret;
}

float ds3231_get_temperature(void)
{
    rt_int8_t buff;
    float temp = 0.0f;

    ds3231_read_reg(&ds3231_dev, REG_TEMP_MSB, (rt_uint8_t*)buff, 2);
    if(buff&0x80)
    {/* negative temperature */
      temp = buff;
      temp -= (buff>>6)*0.25;/* 0.25C resolution */
    }
    else
    {/* positive temperature */
      temp = buff;
      temp += ((buff>>6)&0x03)*0.25;
    }

    return temp;
}

int rt_hw_ds3231_init(void)
{
    struct rt_i2c_bus_device *i2c_device;
    uint8_t data;

    i2c_device = rt_i2c_bus_device_find(DS3231_I2C_BUS);
    if (i2c_device == RT_NULL)
    {
      LOG_E("i2c bus device %s not found!\r\n", DS3231_I2C_BUS);
      return -RT_ERROR;
    }

    /* register rtc device */
    ds3231_dev.type         = RT_Device_Class_RTC;
    ds3231_dev.init         = RT_NULL;
    ds3231_dev.open         = rt_ds3231_open;
    ds3231_dev.close      = RT_NULL;
    ds3231_dev.read         = rt_ds3231_read;
    ds3231_dev.write      = RT_NULL;
    ds3231_dev.control      = rt_ds3231_control;
    ds3231_dev.user_data    = (void*)i2c_device;    /* save i2cbus */;
    rt_device_register(&ds3231_dev, DS3231_DEVICE_NAME, RT_DEVICE_FLAG_RDWR);

    /* init ds3231 */
    data = 0x04;    /* close clock out */
    ds3231_write_reg(&ds3231_dev, REG_CONTROL, &data, 1);
    LOG_D("the rtc of ds3231 init succeed!");

    return 0;
}
//INIT_DEVICE_EXPORT(rt_hw_ds3231_init);
INIT_COMPONENT_EXPORT(rt_hw_ds3231_init);

#ifdef RT_USING_FINSH
#include <finsh.h>

void list_ds31_temp(void)
{
    float temp = 0.0f;

    temp = ds3231_get_temperature();

    rt_kprintf("ds3231 temperature: [%d.%dC] \n", (int)temp, (int)(temp * 10) % 10);
}
FINSH_FUNCTION_EXPORT(list_ds31_temp, list ds3231 temperature.)
#endif /* RT_USING_FINSH */



2.3 效果验证

经过验证,可以从ds3231上读取到温度值,说明读取寄存器时正常的。但使用date命令回复的时间没有变化,说明rtthread的rtc框架和ds3231驱动间的衔接有问题。经过排查,发现rtthread的系统代码让先楫改的面目全非,不再支持外部rtc了。
解决方案,不使用rtc框架,直接读取DS3231的寄存器就可以解决。



dirtwillfly 发表于 2025-9-28 13:41

备用
页: [1]
查看完整版本: 国产risc-v微控制器读取DS3231