有时候根据需求需要外加时钟芯片,实现掉电保存的功能,linux已经为我们实现了一系列的rtc时钟芯片,所以我们在选择的时候一般就直接选择内核里面已有的芯片。有了rtc后,需要将网络时间同步到rtc里面,目前更多使用htpdate,不适用ntp。

1.驱动添加


rtc芯片一般使用i2c方式连接,很多arm的内部i2c总是会有时序不稳定的情况,所以会使用gpio模拟i2c的形式,linux内部也已经支持该部分。

1.1、内核driver—》CONFIG

内核需要开启两个驱动

  • i2c相关
  • rtc相关

i2c选项

CONFIG_PACKAGE_kmod-i2c-core=y
CONFIG_PACKAGE_kmod-i2c-algo-bit=y
CONFIG_PACKAGE_kmod-i2c-gpio=y

rtc相关

CONFIG_RTC_DRV_PCF85063=y

1.2、内核platform—》dts/board_info

platform一般两种方式,dts和board_info,目前主流的就是只用dts的方式

1.2.1 dts方式

dtsi里面添加宏

i2c: i2c@0 {
compatible = "i2c-gpio"; ---》使用gpio模拟i2c的方式
gpios = <&pio 14 GPIO_ACTIVE_HIGH>, --》指定好gpio
<&pio 15 GPIO_ACTIVE_HIGH>;
i2c-gpio,delay-us = <3>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

dts里面开启宏,设置好pinctrl和添加rtc

&i2c {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;

pcf85063: rtc@51 {
status = "okay";
compatible = "nxp,pcf85063";
reg = <0x51>;
};
};

很多gpio是复用功能,所以需要在pinctrl里面将function设置成gpio,这个group的名称需要去看pinctrl里面对应芯片的定义。

&pio {
i2c0_pins: i2c0-pins {
mux {
function = "gpio";
group = "i2c0";
};
};
}
1.2.2 board_info方式

需要在/arch/mips/mtk/mt7621.c里面注册该i2c设备的i2c_board_info和i2c设备注册platform_device_register。

#define MT7621_GPIO_I2C_SDA 3
#define MT7621_GPIO_I2C_SCL 4

static struct i2c_gpio_platform_data mt7621_i2c_gpio_data = {
.sda_pin = MT7621_GPIO_I2C_SDA,
.scl_pin = MT7621_GPIO_I2C_SCL,
};

static struct platform_device mt7621_i2c_gpio_device = {
.name = "i2c-gpio",
.id = 0,
.dev = {
.platform_data = &mt7621_i2c_gpio_data,
}
};

static struct i2c_board_info mt7621_i2c_board_info[] __initdata = {
{
I2C_BOARD_INFO("pcf85063", 0x51),
},
};

void mt7621_common_init(void)
{
u32 gpio_mode;

gpio_mode = rt_sysc_r32(SYSC_REG_GPIO_MDOE);

/*
* i2c gpio to gpio mode; use i2c-gpio driver
*/
gpio_mode |= MT7621_GPIO_I2C_MODE;
gpio_mode &= ~MT7621_GPIO_WDT_MODE_MASK;
gpio_mode |= MT7621_GPIO_WDT_MODE;
gpio_mode &= ~MT7621_GPIO_UART2_MODE_MASK;
gpio_mode |= MT7621_GPIO_UART2_MODE;
gpio_mode &= ~MT7621_GPIO_UART3_MODE_MASK;
gpio_mode |= MT7621_GPIO_UART3_MODE;
gpio_mode |= MT7621_GPIO_JTAG_GPIO_MODE;

mt7621_gpio_init(gpio_mode);

i2c_register_board_info(0, mt7621_i2c_board_info,
ARRAY_SIZE(mt7621_i2c_board_info));

platform_device_register(&mt7621_i2c_gpio_device);
}

i2c_board_info里面将设备的型号和地址传进去,这个在/drivers/i2c/i2c-boardinfo.c里面使用到。

platform_device_register接口里面将I2C的GPIO脚,和要probe的i2c-gpio名字传进去,这个在/drivers/i2c/busses/i2c-gpio.c里面会用到。

内核调试打印流程

[    3.560000] bus: 'platform': add driver rtc_cmos
[ 3.560000] bus: 'platform': remove driver rtc_cmos
[ 3.560000] driver: 'rtc_cmos': driver_release
[ 3.560000] bus: 'i2c': add driver rtc-ds3232
[ 3.560000] bus: 'i2c': driver_probe_device: matched device 0-0068 with driver rtc-ds3232
[ 3.560000] bus: 'i2c': really_probe: probing driver rtc-ds3232 with device 0-0068
[ 3.570000] device: 'rtc0': device_add
[ 3.570000] rtc-ds3232 0-0068: rtc core: registered ds3232 as rtc0
[ 3.580000] driver: '0-0068': driver_bound: bound to device 'rtc-ds3232'
[ 3.580000] bus: 'i2c': really_probe: bound device 0-0068 to driver rtc-ds3232
[ 3.580000] i2c /dev entries driver
[ 3.590000] device class 'i2c-dev': registering
[ 3.590000] device: 'i2c-0': device_add

2.i2c应用调试


只要上面的驱动配置都正常,i2c设备的调试先要有i2c适配器设备,即在/dev/下有i2c设备

root@openwrt:/dev# ls /dev/i2c-0 
/dev/i2c-0

有了i2c驱动之后,就可以使用busybox提供的各种i2c工具直接测试i2c是否正常。

2.1、检测到adapter

使用i2c-detect工具可以检测到adapter

root@openwrt:/dev# i2cdetect -l
i2c-0 i2c 1e000000.palmbus:i2c@0 I2C adapter

2.2、寻找设备

root@openwrt:/dev# i2cdetect -r -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --


2.3、读取全部寄存器值

root@openwrt:/# i2cdump -f -y 0 0x51
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
10: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
20: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
30: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
40: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
50: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
60: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
70: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
80: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
90: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
a0: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
b0: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
c0: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
d0: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
e0: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
f0: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??.............

2.4、读取单个寄存器值

root@openwrt:/# i2cget -fy 0 0x51 0x07
0x26
root@openwrt:/# i2cget -fy 0 0x51 0x09
0x10

​http://www.atmcu.com/2341.html​

2.5、设置寄存器值

root@openwrt:/# i2cset -fy 0 0x51 0x12 0x04 b

3. “系统时间”与“硬件时间”


系统时间: 一般说来就是我们执行 date 命令看到的时间,linux系统下所有的时间调用(除了直接访问硬件时间的命令)都是使用的这个时间。

root@openwrt:/# date 
Tue May 21 15:05:26 CST 2019

时间设置

date -s "2020-03-23 11:22:50"

settimeofday

硬件时间: 外部RTC时钟,由电池供电来维持运行,所以适配器掉电了时间也可以正常运行

root@openwrt:/# hwclock 
Tue May 21 15:06:53 2019 0.000000 seconds
  • -r, --show 读取并打印硬件时钟(read hardware clock and print result)
  • -s, --hctosys 将硬件时钟同步到系统时钟(set the system time from the hardware clock)
  • -w, --systohc 将系统时钟同步到硬件时钟(set the hardware clock to the current system time)

drivers/rtc/下面有很多的时钟芯片驱动,一般用的都是Maxim/Dallas的I2C芯片,所以我们只需要添加i2c驱动即可。

原理如下

hwclock -w

    -> xioctl(RTC_SET_TIME);

      -> rtc_dev_ioctl()

        -> rtc_set_time()

hwclock是busybox下面的一个程序,内部会调用xioctl函数,改函数会对驱动设备进行写数据。

busybox-1.22.1$ vim ./libbb/rtc.c

int FAST_FUNC rtc_xopen(const char **default_rtc, int flags)
{
int rtc;

if (!*default_rtc) {
*default_rtc = "/dev/rtc";
rtc = open(*default_rtc, flags);
if (rtc >= 0)
return rtc;
*default_rtc = "/dev/rtc0";
rtc = open(*default_rtc, flags);
if (rtc >= 0)
return rtc;
*default_rtc = "/dev/misc/rtc";
}

return xopen(*default_rtc, flags);
}

rtc驱动里面会初始化字符设备信息

cdev_init(&rtc->char_dev, &rtc_dev_fops);

static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.unlocked_ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
};

4.互联网时间htpdate


4.1 htpdate逻辑

htpdate启动参数

参数

含义

-l

-s

立即设置时间

-t

-d

开启日志

-D

后台运行

-m

请求失败间隔时间

-M

将htpdata的日志打开-d

htpdate启动脚本位于/etc/init.d/htpdate

root@openwrt:/# cat /etc/init.d/htpdate
#!/bin/sh /etc/rc.common
# Copyright (C) 2006 OpenWrt.org

START=10
STOP=91
BIN=htpdate
DEFAULT=/etc/default/$BIN
RUN_D=/var/run
PID_F=$RUN_D/$BIN.pid

EXTRA_COMMANDS='save'

start() {
local disabled

config_load htpdate
config_get_bool disabled htpdate disabled 0

htpdate_stop

[ "$disabled" -gt 0 ] || {
[ -f $DEFAULT ] && . $DEFAULT
mkdir -p $RUN_D
$BIN -l -s -t -m 300 -M 600 -D $OPTIONS
}
}

htpdate_stop() {
[ -f $PID_F ] && {
kill -9 $(cat $PID_F)
rm -rf $PID_F
}
}

stop() {
htpdate_stop
}

域名配置文件

cat /etc/default/htpdate
OPTIONS="www.baidu.com www.taobao.com www.jd.com www.youku.com"

启动后进程内容如下:

htpdate -l -s -t -d -m 300 -M 600 -D www.baidu.com www.taobao.com www.jd.com www.youku.com

打开debug日志后,logread内容大概如下

Tue Oct 25 16:00:56 2022 user.info : htpdate version 1.1.1 started
Tue Oct 25 16:00:56 2022 user.info : burst: 1 try: 1 when: 200000
Tue Oct 25 16:00:57 2022 user.info : www.baidu.com 25 Oct 2022 08:00:57 GMT (0.055) => 0
Tue Oct 25 16:02:12 2022 user.info : burst: 1 try: 1 when: 400000
Tue Oct 25 16:02:12 2022 user.info : www.taobao.com 25 Oct 2022 08:02:13 GMT (0.048) => 1
Tue Oct 25 16:02:12 2022 user.info : burst: 1 try: 2 when: 400000
Tue Oct 25 16:02:13 2022 user.info : www.taobao.com 25 Oct 2022 08:02:14 GMT (0.047) => 1
Tue Oct 25 16:02:13 2022 user.info : burst: 1 try: 1 when: 600000
Tue Oct 25 16:02:13 2022 user.info : www.jd.com 25 Oct 2022 08:02:14 GMT (0.053) => 1
Tue Oct 25 16:02:13 2022 user.info : burst: 1 try: 2 when: 600000
Tue Oct 25 16:02:14 2022 user.info : www.jd.com 25 Oct 2022 08:02:15 GMT (0.054) => 1
Tue Oct 25 16:02:14 2022 user.info : burst: 1 try: 1 when: 800000
Tue Oct 25 16:02:14 2022 user.info : www.youku.com 25 Oct 2022 08:02:15 GMT (0.057) => 1
Tue Oct 25 16:02:14 2022 user.info : burst: 1 try: 2 when: 800000
Tue Oct 25 16:02:15 2022 user.info : www.youku.com 25 Oct 2022 08:02:16 GMT (0.059) => 1
Tue Oct 25 16:02:15 2022 user.info : #: 4 mean: 1 average: 0.750
Tue Oct 25 16:02:15 2022 user.info : Timezone: GMT+8 (CST,)
Tue Oct 25 16:02:15 2022 user.info : Setting 0.750 seconds
Tue Oct 25 16:02:16 2022 user.info : Set: Tue Oct 25 16:02:16 2022

htpdate逻辑1:默认如果时间同步成功后,会等待30min后再次请求

/* Sleep for 30 minutes after a time adjust or set */
sleep( DEFAULT_MIN_SLEEP );

30分钟后再次请求:

Tue Oct 25 16:32:16 2022 user.info : burst: 1 try: 1 when: 200000
Tue Oct 25 16:32:16 2022 user.info : www.baidu.com 25 Oct 2022 08:32:16 GMT (0.053) => 0
Tue Oct 25 16:33:31 2022 user.info : burst: 1 try: 1 when: 400000
Tue Oct 25 16:33:31 2022 user.info : www.taobao.com 25 Oct 2022 08:33:31 GMT (0.046) => 0
Tue Oct 25 16:34:46 2022 user.info : burst: 1 try: 1 when: 600000
Tue Oct 25 16:34:46 2022 user.info : www.jd.com 25 Oct 2022 08:34:47 GMT (0.054) => 1
Tue Oct 25 16:34:46 2022 user.info : burst: 1 try: 2 when: 600000
Tue Oct 25 16:34:47 2022 user.info : www.jd.com 25 Oct 2022 08:34:48 GMT (0.053) => 1
Tue Oct 25 16:34:47 2022 user.info : burst: 1 try: 1 when: 800000
Tue Oct 25 16:34:47 2022 user.info : www.youku.com 25 Oct 2022 08:34:48 GMT (0.056) => 1
Tue Oct 25 16:34:47 2022 user.info : burst: 1 try: 2 when: 800000
Tue Oct 25 16:34:48 2022 user.info : www.youku.com 25 Oct 2022 08:34:49 GMT (0.056) => 1
Tue Oct 25 16:34:48 2022 user.info : #: 4 mean: 1 average: 0.500
Tue Oct 25 16:34:48 2022 user.info : Timezone: GMT+8 (CST,)
Tue Oct 25 16:34:48 2022 user.info : Adjusting 0.500 seconds
Tue Oct 25 16:34:48 2022 user.info : Drift 256.15 PPM, 22.13 s/day

htpdate逻辑2:就算入参有-s立即设置时间,但是这个也只生效一次,第二次就变成adjust time了

/* After first poll cycle do not step through time, only adjust */
if ( setmode != 3 ) {
setmode = 1;
}

设置时间用​​asctime()​​​函数,调整时间用​​adjtime()​​​函数,还有一个调整内核时间​​adjtimex()​​函数

4.2 htp时间同步到rtc

修改htpdate的源码,在htpdate更新时间的位置,添加调用时间同步到rtc脚本,如下:

static void htpdate_save() {
system("/etc/init.d/htpdate save 0");
}

脚本内如也位于/etc/init.d/htpdate,内容如下:

save() 
{
local local_time=$(date '+%s')

date -k
# set to rtc
hwclock -w

uci set htpdate.htpdate.sync_time=$local_time
uci commit htpdate
if [ "$1" != "1" ]; then
# call hotplug
time-hotplug sync
fi
}

完成htp时间同步到rtc中的实现。