路由联盟

27-Openwrt rtc htpdate system

2022-11-04  本文已影响0人  Creator_Ly

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

1.驱动添加


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

1.1、内核driver---》CONFIG

内核需要开启两个驱动

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>,  --》SCL
            <&pio 15 GPIO_ACTIVE_HIGH>;  --》SDA
    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";
        };
    };
}

添加成功后,如果能够识别出i2c驱动则有如下信息:

[    0.204881] gpio-423 (i2c@0): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[    0.214367] gpio-424 (i2c@0): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[    0.223928] i2c-gpio i2c@0: using lines 423 (SDA) and 424 (SCL)
[    0.230180] gpio-465 (i2c@1): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[    0.240308] gpio-464 (i2c@1): enforced open drain please flag it properly in DT/ACPI DSDT/board file
[    0.250529] i2c-gpio i2c@1: using lines 465 (SDA) and 464 (SCL)```

##### 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: -- -- -- -- -- -- -- --


手把手教你使用 i2c-tools:https://blog.csdn.net/qq_38769551/article/details/124261403

#### 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 -rfPID_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中的实现。
上一篇下一篇

猜你喜欢

热点阅读