在我2022年进入翼辉时第一个任务是需要在龙芯派2K1000 SylixOS 上进行了 DS3231 RTC 设备驱动开发。
本篇文章针对这部分的内容进行总结记录。
DS3231是一种低成本,极其精确的I2C实时时钟(RTC)与集成温度补偿晶体振荡器(TCXO)和晶体。该设备包含一个电池输入,并保持准确的时间,当设备的主电源中断。
晶体谐振器的集成提高了设备的长期精度,并减少了生产线中的零件数量。RTC维护秒、分钟、小时、日、日、月和年信息。
月末的日期在少于31天的月份中自动调整,包括闰年的修正。此时钟采用24小时或12小时制,并附有上午/下午指示。提供两个可编程的一天时间报警和一个可编程的方波输出。
地址和数据通过I2C双向总线串行传输。
关于DS3231各个寄存器信息如下:
/********************************************************************************************************* DS3231寄存器地址 *********************************************************************************************************/ #define DS3231_ADDR (0b1101000) #define DS3231_I2C_NAME ("/bus/i2c/0") #define DS3231_DEV_NAME ("/dev/ds3231") #define rSEC (0x00) /* Second */ #define rHOUR (0x02) /* Hour */ #define rMIN (0x01) /* Minute */ #define rDAY (0x03) /* Day */ #define rDATE (0x04) /* Date */ #define rMONCENTURY (0x05) /* Month/century */ #define rYEAR (0x06) /* Year */ #define rALM1SEC (0x07) /* Alarm1 second */ #define rALM1MIN (0x08) /* Alarm1 minute */ #define rALM1HOUR (0x09) /* Alarm1 hour */ #define rALM1DAYDATE (0x0A) /* Alarm1 day&date */ #define rALM2MIN (0x0B) /* Alarm2 minute */ #define rALM2HOUR (0x0C) /* Alarm2 hour */ #define rALM2DAYDATE (0x0D) /* Alarm2 day&date */ #define rCON (0x0E) /* Control */ #define rSTATUS (0x0F) /* Control/Status */ #define rAGING (0x10) /* Aging Offse */ #define rTEMP1 (0x11) /* MSB of Temp */ #define rTEMP2 (0x12) /* LSB of Temp */
DS3231 的数据传输依赖于 I2C 总线,对于其设备结构体定义如下:
/********************************************************************************************************* DS3231 设备结构 *********************************************************************************************************/ typedef struct ds3231 { LW_DEV_HDR ds_devhdr; PLW_I2C_DEVICE ds_i2cdev; /* I2C 设备 */ UINT8 ds_ucChildAddr; /* DS3231 设备子地址 */ time_t ds_timeCreate; /* 设备创建时间 */ INT ds_iCmdWord; /* 命令 */ } DS3231;
其中 PLW_I2C_DEVICE 为 SylixOS 对于 I2C 设备的抽象。其中包括了设备地址,读写标志,所挂载的设配器等信息。
对于 DS3231 对于设备的驱动首先需要读写相关寄存器数据,然后根据读写后的数据进行时间格式转换以完成相关功能的实现。
SylixOS 对于 I2C 消息的设备地址,读写标志,数据缓冲区等相关结构信息封装为LW_I2C_MESSAGE结构体,DS3231 以8位 BCD 码存取数据。
设置缓冲区,填入需要写入寄存器地址以及写入内容,设置 I2C 消息结构。调用API_I2cDeviceTransfer以写入寄存器。
/********************************************************************************************************* ** 函数名称: __DS3231SetReg ** 功能描述: 写入 DS3231 寄存器 ** 输 入 : pi2cdev 设备结构体 ** ucRegAddr 寄存器地址 ** ucData 数据 ** 输 出 : 操作的 pi2cmsg 数量, 错误返回 -1 ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT __DS3231SetReg (PLW_I2C_DEVICE pi2cdev, UINT8 ucRegAddr, UINT8 ucData) { INT iRet; UINT8 pucRxBuf[2]; if (!pi2cdev) { return (PX_ERROR); } pucRxBuf[0] = ucRegAddr; pucRxBuf[1] = ucData; LW_I2C_MESSAGE msgs[] = { { .I2CMSG_usAddr = pi2cdev->I2CDEV_usAddr, .I2CMSG_usFlag = 0, .I2CMSG_usLen = 2, .I2CMSG_pucBuffer = pucRxBuf, } }; iRet = API_I2cDeviceTransfer(pi2cdev, msgs, 1); if (iRet < 0) { printk("write wrong %d",iRet); return (PX_ERROR); } return (iRet - 1); }
读取DS3231各个寄存器相比较于写入。需要先写入需要读取的寄存器地址,之后再进行读取操作。这就需要俩个 I2C 消息来实现。
/********************************************************************************************************* ** 函数名称: __DS3231GetReg ** 功能描述: 读取 DS3231 寄存器 ** 输 入 : pi2cdev 设备结构体 ** ucRegAddr 寄存器地址 ** pucData 数据 ** 输 出 : 操作的 pi2cmsg 数量, 错误返回 -1 ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT __DS3231GetReg (PLW_I2C_DEVICE pi2cdev, UINT8 ucRegAddr, UINT8 *pucData) { INT iRet; if (!pi2cdev) { return (PX_ERROR); } LW_I2C_MESSAGE msgs[] = { { .I2CMSG_usAddr = pi2cdev->I2CDEV_usAddr, .I2CMSG_usFlag = 0, .I2CMSG_usLen = 1, .I2CMSG_pucBuffer = &ucRegAddr, },{ .I2CMSG_usAddr = pi2cdev->I2CDEV_usAddr, .I2CMSG_usFlag = LW_I2C_M_RD, .I2CMSG_usLen = 1, .I2CMSG_pucBuffer = pucData, } }; iRet = API_I2cDeviceTransfer(pi2cdev,msgs,2); if (iRet < 0) { printk("read wrong%d",iRet); return (PX_ERROR); } return (iRet - 1); }
在读取出8位BCD码后,需要进行时间格式转化。将读出的数据按照DS3231手册内容进行转换填入时间相关结构体tm中,最后转换为time_t格式放入读取缓冲区。
/********************************************************************************************************* ** 函数名称: __DS3231GetTime ** 功能描述: 获取 RTC 时间 ** 输 入 : prtcfuncs 时间操作函数集 ** pds3231 设备 ** ptimeNow 时间 ** 输 出 : PX_ERROR OR ERROR_NONE ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT __DS3231GetTime (PLW_RTC_FUNCS prtcfuncs, DS3231 *pds3231, time_t *ptimeNow) { struct tm tmNow; UINT8 pvBuf; INT iLen = 0; INT iHigh; /* BCD 码高位 */ INT iLow; /* BCD 码低位 */ pds3231->ds_ucChildAddr = rSEC; iLen = __DS3231GetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr, &pvBuf); iHigh = ((pvBuf & 0xF0) >> 4); iLow = (pvBuf & 0x0F); tmNow.tm_sec = (unsigned char)(iHigh * 10 + iLow); pvBuf =0; if (iLen < 0) { printk("read wrong"); return (PX_ERROR); } pds3231->ds_ucChildAddr = rMIN; iLen = __DS3231GetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr, &pvBuf); iHigh = ((pvBuf & 0xF0) >> 4); iLow = (pvBuf & 0x0F); tmNow.tm_min = (unsigned char)(iHigh * 10 + iLow); pvBuf =0; if (iLen < 0) { printk("read wrong"); return (PX_ERROR); } pds3231->ds_ucChildAddr = rHOUR; iLen = __DS3231GetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr, &pvBuf); if (pvBuf & 0x10){ pvBuf &= 0x3f; iHigh = ((pvBuf & 0xF0) >> 4); iLow = (pvBuf & 0x0F); tmNow.tm_hour = (unsigned char)(iHigh * 10 + iLow); } pvBuf =0; if (iLen < 0) { printk("read wrong"); return (PX_ERROR); } pds3231->ds_ucChildAddr = rDATE; iLen = __DS3231GetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr, &pvBuf); iHigh = ((pvBuf & 0xF0) >> 4); iLow = (pvBuf & 0x0F); tmNow.tm_mday = (unsigned char)(iHigh * 10 + iLow); pvBuf = 0; if (iLen < 0) { printk("read wrong"); return (PX_ERROR); } pds3231->ds_ucChildAddr = rMONCENTURY; iLen = __DS3231GetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr, &pvBuf); if (pvBuf & 0x10){ pvBuf &= 0x1f; iHigh = ((pvBuf & 0xF0) >> 4); iLow = (pvBuf & 0x0F); tmNow.tm_mon = (unsigned char)(iHigh * 10 + iLow); } pvBuf =0; if (iLen < 0) { printk("read wrong"); return (PX_ERROR); } pds3231->ds_ucChildAddr = rYEAR; iLen = __DS3231GetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr, &pvBuf); iHigh = ((pvBuf & 0xF0) >> 4); iLow = (pvBuf & 0x0F); tmNow.tm_year = (unsigned char)(iHigh * 10 + iLow + 2000); pvBuf = 0; if (iLen < 0) { return (PX_ERROR); printk("read wrong"); } pds3231->ds_ucChildAddr = rDAY; iLen = __DS3231GetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr, &pvBuf); tmNow.tm_wday = (unsigned char)(pvBuf); if (iLen < 0) { printk("read wrong"); return (PX_ERROR); } if (ptimeNow) { *ptimeNow = timegm(&tmNow); } return (ERROR_NONE); }
写入时将 time_t 类型数据转换为 tm 格式,然后根据寄存器进行转化写入相应寄存器当中。
/********************************************************************************************************* ** 函数名称: __DS3231SetTime ** 功能描述: 设置 RTC 时间 ** 输 入 : prtcfuncs 时间操作函数集 ** pds3231 设备 ** ptimeNow 时间 ** 输 出 : PX_ERROR OR ERROR_NONE ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT __DS3231SetTime (PLW_RTC_FUNCS prtcfuncs, DS3231 *pds3231, time_t *ptimeNow) { struct tm tmNow; UINT8 pvBuf; INT iHigh; /* BCD 码高位 */ INT iLen; INT iLow; /* BCD 码低位 */ if (!pds3231) { _ErrorHandle(EINVAL); return (PX_ERROR); } gmtime_r(ptimeNow, &tmNow); /* 转换成 tm 时间格式 */ iHigh = tmNow.tm_sec / 10; iLow = tmNow.tm_sec % 10; pvBuf = (unsigned char)((iHigh << 4) + iLow); /* 秒寄存器 */ pds3231->ds_ucChildAddr = rSEC; iLen = __DS3231SetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr,pvBuf); if (iLen){ printk("write wrong"); return (PX_ERROR); } iHigh = tmNow.tm_min / 10; iLow = tmNow.tm_min % 10; pvBuf = (unsigned char)((iHigh << 4) + iLow); /* 分寄存器 */ pds3231->ds_ucChildAddr = rMIN; iLen = __DS3231SetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr,pvBuf); if (iLen){ printk("write wrong"); return (PX_ERROR); } iHigh = tmNow.tm_hour / 10; iLow = tmNow.tm_hour % 10; pvBuf = (unsigned char)((iHigh << 4) + iLow); /* 小时寄存器 */ pds3231->ds_ucChildAddr = rHOUR; iLen = __DS3231SetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr,pvBuf); if (iLen){ printk("write wrong"); return (PX_ERROR); } iHigh = tmNow.tm_mday / 10; iLow = tmNow.tm_mday % 10; pvBuf = (unsigned char)((iHigh << 4) + iLow); /* 日期寄存器 */ pds3231->ds_ucChildAddr = rDATE; iLen = __DS3231SetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr,pvBuf); if (iLen){ printk("write wrong"); return (PX_ERROR); } iHigh = tmNow.tm_mon / 10; iLow = tmNow.tm_mon % 10; pvBuf = (unsigned char)((iHigh << 4) + iLow); /* 月份寄存器 */ pds3231->ds_ucChildAddr = rMONCENTURY; iLen = __DS3231SetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr,pvBuf); if (iLen){ printk("write wrong"); return (PX_ERROR); } iHigh = (tmNow.tm_year - 2000) / 10; iLow = (tmNow.tm_year - 2000) % 10; pvBuf = (unsigned char)((iHigh << 4) + iLow); /* 年寄存器 */ pds3231->ds_ucChildAddr = rYEAR; iLen = __DS3231SetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr,pvBuf); if (iLen){ printk("write wrong"); return (PX_ERROR); } pvBuf = (unsigned char)(tmNow.tm_wday); /* 日期寄存器 */ pds3231->ds_ucChildAddr = rDAY; iLen = __DS3231SetReg(pds3231->ds_i2cdev,pds3231->ds_ucChildAddr,pvBuf); if (iLen){ printk("write wrong"); return (PX_ERROR); } return (ERROR_NONE); }
SylixOS 中设定了 RTC 操作函数集 LW_RTC_FUNCS,将之前实现的函数__DS3231SetTime,__DS3231GetTime 填入操作函数集中。
/********************************************************************************************************* ** 函数名称: rtcGetFuncs ** 功能描述: RTC 函数操作集获取 ** 输 入 : VOID ** 输 出 : rtcfuncs RTC 操作函数集 ** 全局变量: ** 调用模块: *********************************************************************************************************/ PLW_RTC_FUNCS rtcGetFuncs (VOID) { static LW_RTC_FUNCS rtcfuncs = {LW_NULL, __DS3231SetTime, __DS3231GetTime, LW_NULL}; return (&rtcfuncs); }
以内核模块装载的方式,创建 RTC 设备以及驱动安装。
/********************************************************************************************************* ** 函数名称: module_init ** 功能描述: DS3231 驱动安装 ** 输 入 : VOID ** 输 出 : 错误号 ** 全局变量: ** 调用模块: *********************************************************************************************************/ INT module_init (void) { INT iRet; PLW_RTC_FUNCS prtcfuncs = rtcGetFuncs(); iRet = rtcDrv(); if (iRet != ERROR_NONE) { return (PX_ERROR); } iRet = rtcDevCreate(prtcfuncs); if (iRet != ERROR_NONE) { return (PX_ERROR); } return (ERROR_NONE); }
至此完成 RTC 设备 DS3231 驱动开发。