龙芯派2k1000 SylixOS 使用io模拟iic总线功能实现

2023-02-28 • SylixOS -驱动 CGPIO I2C

Menu - 目录列表

前言 I2C总线介绍 驱动开发

前言

在我2022年进入翼辉时第一个任务是需要在龙芯派2K1000 SylixOS 上进行了 DS3231 RTC 设备驱动开发。
本篇文章针对这部分的内容进行总结记录。


DS3231 设备介绍

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);
					}
                    

RTC 设备驱动安装

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 驱动开发。



原创内容使用 知识共享 署名-非商业性使用-相同方式共享 4.0 (CC BY-NC-ND 4.0) 协议授权。