• 27-Openwrt rtc htpdate system


    有时候根据需求需要外加时钟芯片,实现掉电保存的功能,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
    
    • 1
    • 2
    • 3

    rtc相关

    CONFIG_RTC_DRV_PCF85063=y
    
    • 1
    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";
     };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    &i2c {
    	status = "okay";
    	pinctrl-names = "default";
    	pinctrl-0 = <&i2c0_pins>;
    
    	pcf85063: rtc@51 {
    		status = "okay";
    		compatible = "nxp,pcf85063";
    		reg = <0x51>;
    	};
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    &pio {
        i2c0_pins: i2c0-pins {
    		mux {
    			function = "gpio";
    			group = "i2c0";
    		};
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.i2c应用调试


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

    root@openwrt:/dev# ls /dev/i2c-0 
    /dev/i2c-0
    
    • 1
    • 2

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

    2.1、检测到adapter

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

    root@openwrt:/dev# i2cdetect -l
    i2c-0   i2c             1e000000.palmbus:i2c@0                  I2C adapter
    
    • 1
    • 2
    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: -- -- -- -- -- -- -- --
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    手把手教你使用 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    ??.............
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    2.4、读取单个寄存器值
    root@openwrt:/# i2cget -fy 0 0x51 0x07
    0x26
    root@openwrt:/# i2cget -fy 0 0x51 0x09
    0x10
    
    • 1
    • 2
    • 3
    • 4

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

    2.5、设置寄存器值
    root@openwrt:/# i2cset -fy 0 0x51 0x12 0x04 b
    
    • 1

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


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

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

    时间设置

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

    settimeofday

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

    root@openwrt:/# hwclock 
    Tue May 21 15:06:53 2019  0.000000 seconds
    
    • 1
    • 2
    • -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()  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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,
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    域名配置文件

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

    启动后进程内容如下:

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

    打开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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

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

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

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

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

    4.2 htp时间同步到rtc

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

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

    脚本内如也位于/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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

  • 相关阅读:
    C中的strtol()函数
    MySQL基础总结合集
    基于javam和vue的酒店管理系统2021计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
    代码随想录-Day25
    1200*D. Same Differences(数学&推公式)
    Sentinel系列之SlotChain、NodeSelectorSlot、ClusterBuilderSlot分析
    【第八章】文件与文件系统的压缩、打包与备份
    Day23:算法之分支定界
    数据库管理-第152期 Oracle Vector DB & AI-04(20240220)
    9.3DDD之集成事件
  • 原文地址:https://blog.csdn.net/Creator_Ly/article/details/127705376