首页 > 代码库 > SylixOS中RTC设备驱动
SylixOS中RTC设备驱动
1、概述
本文档基于SylixOS-EVB-i.MX6Q验证平台,介绍SylixOS中RTC设备驱动实现过程,可作为在SylixOS集成开发环境下进行字符设备驱动开发的参考。
2、RTC设备驱动
2.1硬件原理
实时时钟(RTC)的主要功能是在系统掉电的情况下,利用备用电源使时钟继续运行,保证不会丢失时间信息。
i.MX6Q验证平台上使用的是外置实时时钟集成电路ISL1208。硬件接线如图 2.1所示。
图 2.1 RTC硬件接线
图中,X1和X2为内部反向放大器的输入和输出引脚,要求外置一个32.768kHz的晶体振荡器以提供振荡源;VBAT为备用电源提供端,当VDD电源失效时,VBAT端的电源立即工作,保证在外部供电中断的情况下,内部的时钟信号产生电路依旧正常工作;SDA和SCL为连接到I2C总线的两个引脚,用于传输串行数据信号和时钟信号,最高传输速率达到400kHz;nRQ/FOUT是一个多功能引脚,可以将其配置为中断输出或固定频率输出端;VDD和GND分别为电源端和地,ISL1208的工作电压为2.0~5.5V。
2.2驱动实现
2.2.1安装RTC设备驱动程序
参照上一章节字符设备驱动模型,调用API_RtcDrvInstall内核函数,注册RTC设备驱动程序,包括RTC设备的创建、删除、打开、关闭、读写及IO控制等驱动函数,注册的过称即是在内核维护的驱动程序表中,找到空闲位置,然后将驱动函数填入该空闲驱动程序表。注册RTC设备驱动流程如程序清单 2.1所示,函数返回驱动函数索引号_G_iRtcDrvNum——驱动函数在驱动程序表中的位置。
程序清单 2.1安装RTC设备驱动程序
LW_API INT API_RtcDrvInstall (VOID) { if (_G_iRtcDrvNum > 0) { return (ERROR_NONE); } _G_iRtcDrvNum = iosDrvInstall(__rtcOpen, (FUNCPTR)LW_NULL, __rtcOpen, __rtcClose, LW_NULL, LW_NULL, __rtcIoctl); DRIVER_LICENSE(_G_iRtcDrvNum, "GPL->Ver 2.0"); DRIVER_AUTHOR(_G_iRtcDrvNum, "Han.hui"); DRIVER_DESCRIPTION(_G_iRtcDrvNum, "hardware rtc."); return ((_G_iRtcDrvNum > 0) ? (ERROR_NONE) : (PX_ERROR)); }
2.2.2创建设备
1. ISL1208 RTC驱动程序
ISL1208驱动程序主要包括RTC初始化、时间设置、时间获取。如程序清单 2.2所示。
程序清单 2.2 ISL1208 RTC驱动程序集
static LW_RTC_FUNCS _G_isl1208RtcFuncs = { isl1208RtcInit, isl1208RtcSetTime, isl1208RtcGetTime, LW_NULL };
1)isl1208RtcInit——RTC初始化
由于ISL1208 RTC通过I2C总线进行数据传输,所以需要在RTC初始化函数中调用API_I2cDeviceCreate内核函数创建RTC设备,将其作为从设备挂接在I2C总线上(前提是已实现验证平台上的I2C驱动并创建I2C适配器),这样,RTC进行数据传输时即可根据适配器名查找到对应的I2C适配器,并调用I2C适配器的操作函数。具体实现如程序清单 2.3所示。
程序清单 2.3 RTC初始化
static VOID isl1208RtcInit (VOID) { __PISL1208_RTC_CONTROLER pRtcControler = &_G_isl1208RtcControlers[0]; if (!pRtcControler->RTCC_pI2cDevice) { pRtcControler->RTCC_pI2cDevice = API_I2cDeviceCreate(pRtcControler->RTCC_pcI2cBusName, "/dev/rtc", 0x6F, /* ISL 芯片的设备地址 */ 0); if (pRtcControler->RTCC_pI2cDevice) { isl1208RtcHwInit(); } } }
2)isl1208RtcSetTime——RTC时间设置
RTC的时间设置过程:
将待设置时间进行转码并填入数据buf;
封装I2C总线传输控制消息,消息中包含RTC从设备地址、消息长度、传输控制标志(读/写)、数据buf;
发送I2C控制消息,控制RTC设备写使能;
发送I2C控制消息,填充RTC对应时间设置寄存器;
发送I2C控制消息,控制RTC设备写失能。
RTC时间设置过程如程序清单 2.4所示,其中isl1208ReadReg和isl1208SetRegs即为封装I2C总线传输控制消息并调用总线传输控制函数进行消息传输的过程,这里不做过多描述。
程序清单 2.4 RTC时间设置
static INT isl1208RtcSetTime (PLW_RTC_FUNCS pRtcFuncs, time_t *pTimeNow) { __PISL1208_RTC_CONTROLER pRtcControler = &_G_isl1208RtcControlers[0]; struct tm tmNow; UINT8 aucBuffer[ISL1208_RTC_SECTION_LEN] = { 0, }; UINT8 ucCtlDat[1], ucRegDat[1]; gmtime_r(pTimeNow, &tmNow); /* 转换成 tm 时间格式 */ /* * 将待设置时间进行转码并填入数据buf */ aucBuffer[ISL1208_REG_YR] = rtcBinToBcd(tmNow.tm_year % 100); aucBuffer[ISL1208_REG_MO] = rtcBinToBcd(tmNow.tm_mon + 1); aucBuffer[ISL1208_REG_DT] = rtcBinToBcd(tmNow.tm_mday); aucBuffer[ISL1208_REG_DW] = rtcBinToBcd(tmNow.tm_wday & 7); aucBuffer[ISL1208_REG_HR] = rtcBinToBcd(tmNow.tm_hour); aucBuffer[ISL1208_REG_MN] = rtcBinToBcd(tmNow.tm_min); aucBuffer[ISL1208_REG_SC] = rtcBinToBcd(tmNow.tm_sec); /* * 读取 RTC 的状态值并设置 WRTC, 控制 RTC 写使能 */ isl1208ReadReg(pRtcControler->RTCC_pI2cDevice, ISL1208_REG_SR, ucRegDat, 1); ucCtlDat[0] = ucRegDat[0] | ISL1208_REG_SR_WRTC; isl1208SetRegs(pRtcControler->RTCC_pI2cDevice, ISL1208_REG_SR, ucCtlDat, 1); /* * 写入待设置时间 */ isl1208SetRegs(pRtcControler->RTCC_pI2cDevice, 0, aucBuffer, ISL1208_RTC_SECTION_LEN); /* * 设置 WRTC, 控制 RTC 写失能 */ ucCtlDat[0] = ucRegDat[0] & ~ISL1208_REG_SR_WRTC; isl1208SetRegs(pRtcControler->RTCC_pI2cDevice, ISL1208_REG_SR, ucCtlDat, 1); return (ERROR_NONE); }
3)isl1208RtcGetTime——RTC时间获取
RTC时间获取就是调用isl1208ReadReg函数,读取RTC设备存放时间信息的寄存器,然后将获取的时间进行转码,获得当前年、月、日、时、分、秒等信息,填入相应结构体。具体实现过程如程序清单 2.5所示。
程序清单 2.5 RTC时间获取
static INT isl1208RtcGetTime (PLW_RTC_FUNCS pRtcFuncs, time_t *pTimeNow) { __PISL1208_RTC_CONTROLER pRtcControler = &_G_isl1208RtcControlers[0]; struct tm tmNow; UINT8 ucValue; UINT8 aucBuffer[ISL1208_RTC_SECTION_LEN] = { 0, }; /* * 读取RTC设备存放时间信息的寄存器 */ isl1208ReadReg(pRtcControler->RTCC_pI2cDevice, 0, aucBuffer, ISL1208_RTC_SECTION_LEN); /* * 对获取的时间信息进行转码并填入相应的结构体 */ tmNow.tm_sec = rtcBcdToBin(aucBuffer[ISL1208_REG_SC]); tmNow.tm_min = rtcBcdToBin(aucBuffer[ISL1208_REG_MN]); ucValue = aucBuffer[ISL1208_REG_HR]; if (ucValue & ISL1208_REG_HR_MIL) { /* 24h 格式 */ tmNow.tm_hour = rtcBcdToBin(ucValue & 0x3F); } else { /* 12h 格式 */ if (ucValue & ISL1208_REG_HR_PM) { /* PM 标志设置 */ tmNow.tm_hour = 12 + rtcBcdToBin(ucValue & 0x1F); } else { tmNow.tm_hour = rtcBcdToBin(ucValue & 0x1F); } } tmNow.tm_wday = rtcBcdToBin(aucBuffer[ISL1208_REG_DW]); tmNow.tm_mday = rtcBcdToBin(aucBuffer[ISL1208_REG_DT]); tmNow.tm_mon = rtcBcdToBin(aucBuffer[ISL1208_REG_MO]) - 1; tmNow.tm_year = rtcBcdToBin(aucBuffer[ISL1208_REG_YR]) + 100; tmNow.tm_yday = 0; tmNow.tm_isdst = 0; if (pTimeNow) { *pTimeNow = timegm(&tmNow); } return (ERROR_NONE); }
2. 创建RTC设备
调用API_RtcDevCreate创建RTC设备,设备结构体prtcdev中保存的操作函数集就是函数入参——ISL1208 RTC驱动程序_G_isl1208RtcFuncs。然后再调用API_IosDevAddEx内核函数,向系统中添加一个设备,该函数中主要就是填充设备结构体中的另一个成员——设备头,设备头中保存着设备名称、设备驱动索引号、设备类型、打开次数等。最后,将该设备头添加入设备头管理链表进行管理。RTC设备的创建过程如程序清单 2.6所示。
程序清单 2.6 RTC设备创建
LW_API INT API_RtcDevCreate (PLW_RTC_FUNCS prtcfuncs) { PLW_RTC_DEV prtcdev; if (prtcfuncs == LW_NULL) { _ErrorHandle(EINVAL); return (PX_ERROR); } if (_G_iRtcDrvNum <= 0) { _DebugHandle(__ERRORMESSAGE_LEVEL, "no driver.\r\n"); _ErrorHandle(ERROR_IO_NO_DRIVER); return (PX_ERROR); } /* * 为设备结构体分配空间 */ prtcdev = (PLW_RTC_DEV)__SHEAP_ALLOC(sizeof(LW_RTC_DEV)); if (prtcdev == LW_NULL) { _DebugHandle(__ERRORMESSAGE_LEVEL, "system low memory.\r\n"); _ErrorHandle(ERROR_SYSTEM_LOW_MEMORY); return (PX_ERROR); } lib_bzero(prtcdev, sizeof(LW_RTC_DEV)); /* * 保存RTC设备驱动程序 */ prtcdev->RTCDEV_prtcfuncs = prtcfuncs; /* * 向系统中添加一个设备,将设备头链入链表进行管理 */ if (iosDevAddEx(&prtcdev->RTCDEV_devhdr, __LW_RTC_DEV_NAME, _G_iRtcDrvNum, DT_CHR) != ERROR_NONE) { __SHEAP_FREE((PVOID)prtcdev); return (PX_ERROR); } /* * 初始化硬件 */ if (prtcfuncs->RTC_pfuncInit) { prtcfuncs->RTC_pfuncInit(); } return (ERROR_NONE); }
3、RTC时间同步
RTC设备创建完成并安装完设备驱动程序后,即可调用API_RtcToSys内核函数进行时间同步。该函数调用API_RtcGet函数,打开已安装的RTC设备,通过ioctl命令控制,获取当前RTC时间,最终调用lib_clock_settime,将获取的时间设置为系统时间。具体实现过程如程序清单 3.1所示。
程序清单 3.1系统时间同步
LW_API INT API_RtcToSys (VOID) { struct timespec tv; /* * 获取当前RTC时间 */ if (API_RtcGet(&tv.tv_sec) < 0) { return (PX_ERROR); } tv.tv_nsec = 0; /* * 将获取的时间设置为系统时间 */ return (lib_clock_settime(CLOCK_REALTIME, &tv)); }
SylixOS中RTC设备驱动