i2c外置RTC开发流程记录

前置条件

T40 + sd2010 外置rtc

首先要确认硬件是否正常链接,设备地址是多少,可以借助i2c-tools

 注意:要使用i2c-tools  内核需要打开 CONFIG_I2C_CHARDEV (用户空间支持使用i2c总线),/dev/下有i2c-*说明配置已打开

i2c-tools

这个是专门用来调试i2c的

编译移植:

下载地址:
Index of /pub/software/utils/i2c-tools/

这里用的是i2c-tools-3.1.1

解压之后进入目录修改Makefile(加上静态编译 和 安装路径)

make CC=mips-linux-uclibc-gnu-gcc; make install

编译成功后生成的 _install/sbin/下就是生成的工具

使用方法:

i2cdetect:用于扫描 i2c 总线上的设备,并显示地址 i2cset:向i2c设备某个寄存器写入值 i2cget:读取i2c设备某个寄存器的值 i2cdump:读取某个i2c设备所有寄存器的值 i2ctransfer:一次性读写多个字节

 可以看到从机的地址为0x32(i2c总线号问硬件工程师即可)

如果扫描不到,可能是管教复用没打开 gpio 复用i2c

不同厂商的复用方法可能不一样,具体要咨询一下厂商。

T40的复用方法

 查原理图确认RTC SDA/SCK 对应的gpio为 PB20  PB21,总线是3.

内核目录  arch/mips/boot/dts/ingenic

这里不知道是怎么个规则, 0、1、2轮流试一下。

读写rtc

查询规格书可知寄存器列表

 方法1 用户空间直接操作 i2c设备:

前面已知总线号3,地址0x32

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

typedef struct _RJ_TIME_
{
    unsigned char       sec;        ///< 秒
    unsigned char       min;        ///< 分
    unsigned char       hour;       ///< 时

    unsigned char       wday;       ///< 星期几
    unsigned char       mday;       ///< 某日
    unsigned char       mon;        ///< 某月

    unsigned short      year;       ///< 某年

    int         msec;       ///< 毫秒
}_RJ_TIME_;

static char nChipAddress = (char)0x32;

int readEx(int m_nFd, unsigned char p_Address, char p_RegNo, char* p_Value, int p_valueLen)
{
	if (ioctl(m_nFd, I2C_SLAVE_FORCE, p_Address))
	{
		printf("readEx wrong slave address!\n");
		return -1;
	}

	if (write(m_nFd, &p_RegNo, 1) < 0)
	{
		printf("write i2c error! \n");
		return -1;
	}


	if (read(m_nFd, p_Value, p_valueLen) < 0)
	{
		printf("read i2c error! \n");
		return -1;
	}

	return 0;
}

int rtc_get_time(_RJ_TIME_ *time)
{
	int ret;
	unsigned char nValue[7]={0};
	
	int m_nFd = open("/dev/i2c-3", O_RDWR);
	if (m_nFd <= 0)
	{
		printf("error open i2c Device failed!\n");
		return -1;
	}
	
	ret = readEx(m_nFd, nChipAddress, 0, (char*)nValue,sizeof(nValue));
	if (0 != ret)
	{
		close(m_nFd);
		return ret;
	}
		
	//printf("%x,%x,%x,%x,%x,%x,%x\n",nValue[0],nValue[1],nValue[2],nValue[3],nValue[4],nValue[5],nValue[6]);
	
	time->sec	=	(nValue[0]>>4) * 10 + (nValue[0]&0x0f) ;
	time->min	=	(nValue[1]>>4) * 10 + (nValue[1]&0x0f) ;
	nValue[2] &= 0x7f;
	time->hour	=	(nValue[2]>>4) * 10 + (nValue[2]&0x0f) ;
	time->mday	=	(nValue[4]>>4) * 10 + (nValue[4]&0x0f) ;
	time->mon	=	(nValue[5]>>4) * 10 + (nValue[5]&0x0f);
	time->year	=	(nValue[6]>>4) * 10 + (nValue[6]&0x0f);
	time->wday	=	(nValue[3]>>4) * 10 + (nValue[3]&0x0f) ;	
	

	
	
	close(m_nFd);
	
	return 0;
}

int rtc_write(int m_nFd, unsigned char p_Address, char p_RegNo, char p_Value)
{
	if (ioctl(m_nFd, I2C_SLAVE_FORCE, p_Address))
	{
		printf("write wrong slave address(0x%x)!\n",p_Address);
		return -1;
	}

	char mbuf[2];
	mbuf[0] = p_RegNo;
	mbuf[1] = p_Value;
	if (write(m_nFd, mbuf, 2) < 0)
	{
		printf("write i2c error\n");
		return -1;
	}
	return 0;
}

int writeEx(int m_nFd, unsigned char p_Address, char p_RegNo, char* p_Value, int p_valueLen)
{
	if (ioctl(m_nFd, I2C_SLAVE_FORCE, p_Address))
	{
		printf("writeEx wrong slave address!\n");
		return -1;
	}

	unsigned char* mbuf = (unsigned char*)calloc(p_valueLen + 1, sizeof(unsigned char));
	if ( mbuf == NULL ) 
	{
		printf("Error: read buffer allocation\n");
		return -1;
	}

	mbuf[0] = p_RegNo;
	for ( int i = 0; i < p_valueLen; i++ ) 
	{
		mbuf[i + 1] = (unsigned char)p_Value[i];
	}
	if (write(m_nFd, mbuf, p_valueLen + 1) < 0)
	{
		printf("write i2c error !\n");
		free(mbuf);
		return -1;
	}
	free(mbuf);
	
	return 0;
}

int rtc_set_time(_RJ_TIME_ *p_tmTime)
{
	int ret;
	unsigned char nValue[7]={0};
	
	int m_nFd = open("/dev/i2c-3", O_RDWR);
	if (m_nFd <= 0)
	{
		printf("error open i2c Device failed!\n");
		return -1;
	}
	
	unsigned char nSeconds = p_tmTime->sec;
	unsigned char nMinutes = p_tmTime->min;
	unsigned char nHours = p_tmTime->hour;
	unsigned char nDays = p_tmTime->mday;
	unsigned char nMonths = p_tmTime->mon;
	unsigned char nYears = p_tmTime->year;
	unsigned char nWeekDay = p_tmTime->wday;
	
	nValue[0] = ((nSeconds/10)<<4)|(nSeconds%10);
	nValue[1] = ((nMinutes/10)<<4)|(nMinutes%10);
	nValue[2] = ((nHours/10)<<4)|(nHours%10);
	nValue[2] |= 0x80;//24 小时制
	nValue[3] = ((nWeekDay/10)<<4)|(nWeekDay%10);
	nValue[4] = ((nDays/10)<<4)|(nDays%10);
	nValue[5] = ((nMonths/10)<<4)|(nMonths%10);
	nValue[6] = ((nYears/10)<<4)|(nYears%10);
	
	rtc_write(m_nFd, nChipAddress, 0x10,0x80);
	rtc_write(m_nFd, nChipAddress, 0x0f,0x84);
	
	printf("nYears[%d]%x, nMonths[%d]%x, nDays[%d]%x, nWeekDay[%d]%x, nHours[%d]%x, nMinutes[%d]%x, nSeconds[%d]%x\n",\
	nYears, nValue[6],\
	nMonths, nValue[5],\
	nDays, nValue[4],\
	nWeekDay, nValue[3],\
	nHours, nValue[2],\
	nMinutes, nValue[1],\
	nSeconds, nValue[0]);
	
	writeEx(m_nFd, nChipAddress, 0, (char*)nValue,sizeof(nValue));
	
	rtc_write(m_nFd, nChipAddress, 0x0f,0x0);
	rtc_write(m_nFd, nChipAddress, 0x10,0x0);
	
	close(m_nFd);
	
	return 0;
}

void case_get_rtc()
{
	_RJ_TIME_ rtc_time;

	rtc_get_time(&rtc_time);
	
	printf("%d-%02d-%02d %d:%d:%d\n",rtc_time.year + 2000,rtc_time.mon,rtc_time.mday,
		rtc_time.hour,rtc_time.min,rtc_time.sec);
}

void case_set_rtc()
{
	time_t timep;
	struct tm *p;
	
	time(&timep);
	p=gmtime(&timep);
	
	printf("%d",p->tm_year + 1900);/*获取当前年份,从1900开始,所以要加1900*/
	printf("-%02d",1+p->tm_mon);/*获取当前月份,范围是0-11,所以要加1*/
	printf("-%02d ",p->tm_mday);/*获取当前月份日数,范围是1-31*/
	printf("%02d-",p->tm_hour);/*获取当前时,这里获取西方的时间,刚好相差八个小时*/
	printf("%02d-",p->tm_min); /*获取当前分*/	
	printf("%02d ",p->tm_sec); /*获取当前秒*/
	printf("星期 %d\n",p->tm_wday); /*获取当前星期*/
	
	_RJ_TIME_ rtc_time;
	
	rtc_time.year = p->tm_year -100; // + 1900 -2000
	if (p->tm_year +1900 < 2000)
		rtc_time.year = 0x0;
	rtc_time.mon = p->tm_mon + 1;
	rtc_time.mday = p->tm_mday;
	rtc_time.hour = p->tm_hour;
	rtc_time.min = p->tm_min;
	rtc_time.sec = p->tm_sec;
	rtc_time.wday = p->tm_wday;
	
	rtc_set_time(&rtc_time);

}

int main(int argc, char *argv[])
{
	
	int result;
	
	opterr = 0;//使getopt不行stderr输出错误信息
	
	while( (result = getopt(argc, argv, "rwh")) != -1 )
	{
		switch(result)
		{
			case 'r':
			//printf("[rtc] Get hw time.\n");
			case_get_rtc();
			break;
			case 'w':
			printf("[rtc] Set hw time.\n");
			case_set_rtc();
			break;
			case 'h':
			printf("[Usage: hwclock [-r|--show][-w|--systohc]\n");
			printf("\t-r	Show hardware clock time\n");
			printf("\t-w	Set hardware clock from system time\n");
			case '?':
			printf("[rtc] Unknow cmd.\n");
			break;
			default:
			printf("[rtc] error.\n");
			break;
		}
	}
	
	return 0;
}

方法2 加载rtc驱动 后台通过hwclock 读写时间:

内核 drivers/rtc目录下有很多厂商已经实现好的驱动, 找一下有没有对应的型号,如果刚好没有,那么我们就自己写驱动。

如果对驱动完全一窍不通,先看下这位博主的系列文章

(一)初识Linux驱动_hanp_linux的博客-CSDN博客 (bing.com) 

驱动编译的时候如果有报错 undefine 大概率是内核对应的配置没有打开,此时虽然能生成ko,但是加载的时候是加载不上去的。

我自己遇到了rtc_device_register undefine 。原因是CONFIG_RTC_CLASS没有打开

这篇文章写得已经很详细了

 (2条消息) Linux驱动开发: Linux下RTC实时时钟驱动_DS小龙哥的博客-CSDN博客_rtc驱动

补充自己写得针对sd2010的rtc驱动

rtc相关操作接口遇到问题可以先尝试搜一下 rtc 厂商的官网,看有没有一些错误解释 或者demo

device端

rtc_device.c

#include "linux/module.h"
#include "linux/init.h"
#include <linux/platform_device.h>
/*
 * device  设备端
 */
 
//释放平台总线
static void pdev_release(struct device *dev)
{
	printk("rtc_pdev:the rtc_pdev is close!!!\n");
}
 
/*设备端结构体*/
struct platform_device  rtc_pdev= /*设备结构体,设备名字很重要!*/
{
	.name = "sd2010rtc",  /*设备名*/
	.id = -1,         /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/
	.dev =            /*驱动卸载时调用*/
	{
		.release = pdev_release,/*释放资源*/
	},
};
 
 
/*平台设备端入口函数*/
static int __init plat_dev_init(void)
{
	printk("rtc device init\n");
	platform_device_register(&rtc_pdev);/*注册平台设备端*/
	return 0;
}
 
/*平台设备端出口函数*/
static void __exit plat_dev_exit(void)
{
	platform_device_unregister(&rtc_pdev);/*注销平台设备端*/
}
 
module_init(plat_dev_init);
module_exit(plat_dev_exit);
MODULE_LICENSE("GPL");

Makefile

CROSS_COMPILE ?= mips-linux-gnu-

ISVP_ENV_KERNEL_DIR = $(PWD)/../../../../kernel-4.4.94

KDIR := ${ISVP_ENV_KERNEL_DIR}
MODULE_NAME := rtc_device_sd2010

all: modules

.PHONY: modules clean

$(MODULE_NAME)-objs := rtc_device.o
obj-m := $(MODULE_NAME).o

modules:
	@$(MAKE) -C $(KDIR) M=$(shell pwd) $@
clean:
	@rm -rf *.o *~ .depend .*.cmd  *.mod.c .tmp_versions *.ko *.symvers modules.order

drver端 (设置接口未完成)

#include <linux/module.h>             /*驱动模块相关*/
#include <linux/init.h>
#include <linux/fs.h>                 /*文件操作集合*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>          /*中断相关头文件*/
#include <linux/irq.h>                /*中断相关头文件*/
#include <linux/gpio.h>               /*硬件相关->定义了寄存器名字与地址*/
#include <linux/wait.h>              
#include <linux/sched.h>
#include <linux/timer.h>              /*内核定时器*/
#include <asm-generic/poll.h>         
#include <linux/poll.h>               /* poll机制*/
#include <linux/platform_device.h>    /* 平台设备驱动相关头文件*/
#include <linux/rtc.h>
#include <linux/i2c.h>


#define sd2010_sec_add			0x00		//秒数据地址
#define sd2010_min_add			0x01		//分数据地址
#define sd2010_hr_add			0x02		//时数据地址
#define sd2010_day_add			0x03		//星期数据地址
#define sd2010_date_add			0x04		//日数据地址
#define sd2010_month_add		0x05		//月数据地址
#define sd2010_year_add			0x06		//年数据地址

#define REG_CTRL_1      0x0F
#define REG_CTRL_2      0x10
#define REG_CTRL_3      0x11

#define RTC_I2C_NUM     3
#define RTC_ADDR        0x32

#define I2C_WRITE 0
#define I2C_READ  1

static struct i2c_board_info sd2010_info = {
    I2C_BOARD_INFO("sd2010", RTC_ADDR),
};

static struct i2c_client *sd2010_client;


static int sd2010_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
	printk("从RTC底层获取时间成功!\n");

	//struct i2c_client *client = to_i2c_client(dev);

    unsigned char addr = sd2010_sec_add;
    unsigned char nValue[7] = {0};

    struct i2c_msg msgs[1] = {
		[0] = {
                .addr  = sd2010_client->addr,
                .flags = I2C_READ,
                .len = 7,
                .buf = nValue,
        },   /* read time/date */
    };


	/* read time/date registers */
    if ((i2c_transfer(sd2010_client->adapter, &msgs[0], 1)) != 1) {
        dev_err(&sd2010_client->dev, "%s: read error\n", __func__);
        return -EIO;
    }

	printk("%x,%x,%x,%x,%x,%x,%x\n",nValue[0],nValue[1],nValue[2],nValue[3],nValue[4],nValue[5],nValue[6]);
	
	rtc_tm->tm_year=2018-1900;   //年 
	rtc_tm->tm_mon=8-1;         //月 
	rtc_tm->tm_mday=18;         //日 
	rtc_tm->tm_hour=18;		   //时 
	rtc_tm->tm_min=18;		//分 
	rtc_tm->tm_sec=18;//秒
	
	
	rtc_tm->tm_sec	=	(nValue[0]>>4) * 10 + (nValue[0]&0x0f) ;
	rtc_tm->tm_min	=	(nValue[1]>>4) * 10 + (nValue[1]&0x0f) ;
	nValue[2] &= 0x7f;
	rtc_tm->tm_hour	=	(nValue[2]>>4) * 10 + (nValue[2]&0x0f) ;
	rtc_tm->tm_mday	=	(nValue[4]>>4) * 10 + (nValue[4]&0x0f) ;
	rtc_tm->tm_mon	=	(nValue[5]>>4) * 10 + (nValue[5]&0x0f);
	rtc_tm->tm_year	=	(nValue[6]>>4) * 10 + (nValue[6]&0x0f) + 100;
	
	return 0;
}
 
static int sd2010_rtc_settime(struct device *dev, struct rtc_time *tm)
{
	printk("RTC收到的时间为:%d-%d-%d %d-%d-%d\n",1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
		 tm->tm_hour, tm->tm_min, tm->tm_sec);

	//struct i2c_client *client = to_i2c_client(dev);
  
	//printk("-------------1\n");
	
	unsigned char bufdata[8] = {0};



	unsigned char nSeconds = 10;
	unsigned char nMinutes = 10;
	unsigned char nHours = 10;
	unsigned char nDays = 10;
	unsigned char nMonths = 10;
	unsigned char nYears = 10;
	
	bufdata[0] = sd2010_sec_add;
	bufdata[1] = ((nSeconds/10)<<4)|(nSeconds%10);
	bufdata[2] = ((nMinutes/10)<<4)|(nMinutes%10);
	bufdata[3] = ((nHours/10)<<4)|(nHours%10);
	bufdata[5] |= 0x80;//24 小时制
	//nValue[4] = ((nWeekDay/10)<<4)|(nWeekDay%10);
	bufdata[5] = ((nDays/10)<<4)|(nDays%10);
	bufdata[6] = ((nMonths/10)<<4)|(nMonths%10);
	bufdata[7] = ((nYears/10)<<4)|(nYears%10);
	

	
	//printk("-------------2\n");
	
	unsigned char bufoff1[2] = {0};
	unsigned char bufoff2[2] = {0};
	
	bufoff1[0] = REG_CTRL_1;
	bufoff1[1] = 0x0;
	bufoff2[0] = REG_CTRL_2;
	bufoff2[1] = 0x0;
	
	
	
	printk("-------------3\n");
	
	return 0;	
}
 
static int sd2010_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
	alrm->enabled=0;  //默认闹钟处于关闭状态
    alrm->time.tm_year=2018-1900;  //年 
	alrm->time.tm_mon=8-1;    //月 
	alrm->time.tm_mday=18;  //日 
	alrm->time.tm_hour=18;	//时 
	alrm->time.tm_min=18;	//分 
	alrm->time.tm_sec=18;	//秒
	printk("从RTC底层获取闹钟时间成功!\n");

	return 0;	
}
 
static int sd2010_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
	printk("RTC闹钟设置成功\n");
	return 0;	
}
	
static int sd2010_rtc_proc(struct device *dev, struct seq_file *seq)
{
	printk("proc调用成功\n");
	return 0;	
}
 
static int sd2010_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
	printk("alarm_irq_enable调用成功\n");
	return 0;	
}
 
static int sd2010_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg)
{
	printk("ioctl调用成功\n");
	return 0;
}
 
 
/*RTC文件操作*/
static const struct rtc_class_ops sd2010_rtcops = {
	.read_time	= sd2010_rtc_gettime,
	.set_time	= sd2010_rtc_settime,
	.read_alarm	= sd2010_rtc_getalarm,
	.set_alarm	= sd2010_rtc_setalarm,
	.proc		= sd2010_rtc_proc,
	.alarm_irq_enable = sd2010_rtc_alarm_irq_enable,
	.ioctl		= sd2010_rtc_ioctl,
};
 
struct rtc_device *rtc;

/*当设备匹配成功执行的函数-资源探查函数*/
static int drv_probe(struct platform_device *pdev)
{	

	struct i2c_adapter *i2c_adap;

	i2c_adap = i2c_get_adapter(RTC_I2C_NUM);
	sd2010_client = i2c_new_device(i2c_adap, &sd2010_info);
    i2c_put_adapter(i2c_adap);
   	
	unsigned char buf[2];
    struct i2c_msg msg = {
        RTC_ADDR, I2C_WRITE, 2, buf,
    };

	buf[0] = REG_CTRL_1;
	buf[1] = 0;

#if 0
    if ((i2c_transfer(sd2010_client->adapter, &msg, 1)) != 1) {
        dev_err(&sd2010_client->dev, "%s: write error\n", __func__);
		printk("i2c_transfer faild!!!\n");
        return -EIO;
    }
	
#endif

	rtc = rtc_device_register("sd2010rtc",&pdev->dev, &sd2010_rtcops,THIS_MODULE);
	if(rtc==NULL)
		printk("RTC驱动注册失败\n");
	else
  		printk("RTC驱动注册成功\n");

	return 0;
}
 
static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/
{
	rtc_device_unregister(rtc);
	printk("RTC驱动卸载成功\n");
	return 0;
}
 
 
/*平台设备驱动端结构体-包含和probe匹配的设备名字*/
struct platform_driver  drv= 
{
	.probe = drv_probe,    /*需要创建一个probe函数,这个函数是对设备进行操作*/
	.remove = drv_remove,  /*创建一个remove函数,用于设备退出*/
	.driver = 
	{
		.name = "sd2010rtc",    /*设备名称,用来与设备端匹配(非常重要)*/
	},
};
 
/*平台驱动端的入口函数*/
static int __init plat_drv_init(void)
{
	printk("rtc_drver init !!!\n");
	platform_driver_register(&drv);/*注册平台驱动*/	
	return 0;
}
 
/*平台驱动端的出口函数*/
static void __exit plat_drv_exit(void)
{
	platform_driver_unregister(&drv);/*释放平台驱动*/
}
 
module_init(plat_drv_init);  /*驱动模块的入口*/
module_exit(plat_drv_exit);  /*驱动模块的出口*/
MODULE_AUTHOR("SHJ");
MODULE_LICENSE("GPL");

Makefile

CROSS_COMPILE ?= mips-linux-gnu-

ISVP_ENV_KERNEL_DIR = $(PWD)/../../../../kernel-4.4.94

KDIR := ${ISVP_ENV_KERNEL_DIR}
MODULE_NAME := rtc_drver_sd2010

all: modules

.PHONY: modules clean

$(MODULE_NAME)-objs := rtc_drver.o
obj-m := $(MODULE_NAME).o

modules:
	@$(MAKE) -C $(KDIR) M=$(shell pwd) $@
clean:
	@rm -rf *.o *~ .depend .*.cmd  *.mod.c .tmp_versions *.ko *.symvers modules.order

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值