Vyzkoušel jsem si v Zigu naprogramovat čtení hodin reálného času s obvodem DS3231 (i2c sběrnice) a spínat v zadaném čase relé. Potřebuji to k automatickému zapínání záložního počítače.

Schéma zapojení

Zapojení

IMG 20241016 082952

Hodiny reálného času DS3231 svrchu

DS3231 front

Hodiny reálného času DS3231 zespodu

DS3231 bottom

Program

Program dělá:

  • Čte údaje z hodin reálného času a v případě že čas je vhodný k sepnutí relé, zapne ho.

  • vytvoří standardní log připojený k UARTU a vypisuje nám logovací informace (tyto informace snadno zachytíme třeba pomocí sudo minicom -b 115200 -c on -D /dev/ttyUSB0 )

Na zdrojový kód se podíváme po kouskách.

Import knihoven
const std = @import("std");             (1)
const microzig = @import("microzig");   (2)
1 Importujeme standardní knihovnu Zigu.
2 Importujeme knihovnu MicroZig
Pojmenování toho, co budeme používat
const rp2040 = microzig.hal; (1)
const i2c = rp2040.i2c;      (2)
const gpio = rp2040.gpio;    (3)
const time = rp2040.time;    (4)
const peripherals = microzig.chip.peripherals;

pub const microzig_options = .{
    .log_level = .info,
    .logFn = rp2040.uart.log,
};

const uart = rp2040.uart.num(0); (5)
const i2c0 = i2c.instance.num(0);
const led  = gpio.num(25);  (6)
const rele = gpio.num(22); (7)
1 Zkrácení microzig.hal
2 Budeme používat knihovnu I2C (pro připojení hodin reálného času)
3 Budeme používat GPIO
4 Budeme používat hodiny
5 Budeme používat UART 0
6 GPIO25 je na Picu vestavěná LED.
7 Spínací relé bude přiojeni k pinu GPIO22
Funkce panika
// error function, never return
fn hodiny_nefunguji() void {

    while(true) {
        time.sleep_ms(200);
        led.put(0);
        time.sleep_ms(300);
        led.put(1);
    }
}

Funkci hodiny_nefunguji() potřebujeme na signalizaci programu při vzniku chyby hodin RTC. Bude rychle blikat ledkou.

Volby logování
pub const microzig_options = .{
    .log_level = .debug,
    .logFn = rp2040.uart.log,
};

Je stejné jako v projektu s UARTem.

Funkce main
pub fn main() !void {
    // Program zacina bezet a LEDka 3x blikne
    led.set_function(.sio); (1)
    led.set_direction(.out);
    led.put(1);
    // nasaveni rele
    rele.set_function(.sio); (2)
    rele.set_direction(.out);
    rele.put(1); // rele musim vypnout, ma na sobě trantistor, který invertuje signál na GP22
    time.sleep_ms(500);
    led.put(0);
    time.sleep_ms(500);
    led.put(1);
    time.sleep_ms(500);
    led.put(0);
    time.sleep_ms(500);
    led.put(1);
    time.sleep_ms(500);
    led.put(0);
    // time.sleep_ms(500);


    uart.apply(.{                   (3)
        .baud_rate = 115200,
        .tx_pin = gpio.num(0),
        .rx_pin = gpio.num(1),
        .clock_config = rp2040.clock_config,
    });
    rp2040.uart.init_logger(uart);

    const scl_pin = gpio.num(4);    (4)
    const sda_pin = gpio.num(5);    (5)
    inline for (&.{ scl_pin, sda_pin }) |pin| { (6)
        pin.set_slew_rate(.slow);
        pin.set_schmitt_trigger(.enabled);
        pin.set_function(.i2c);
    }

    try i2c0.apply(.{
        .clock_config = rp2040.clock_config,
    });

    // testování přítomnosti hodin DS3231, v zásadě to zde být nemusí
    // projedeme všechny I2C adresy a čucháme, zda je něco přítomno
    // program najde hodiny na adrese 0x68 a EEPROM na adrese 0x54, tu používat nebudu
    for (0..1) |_| { (7)
	    led.put(1);
	    for (0..std.math.maxInt(u7)) |addr| {
    	    const a: i2c.Address = @enumFromInt(addr);

    	    // Skip over any reserved addresses.
    	    if (a.is_reserved()) continue;

    	    var rx_data: [1]u8 = undefined;
    	    _ = i2c0.read_blocking(a, &rx_data, rp2040.time.Duration.from_ms(100)) catch continue;

    	    std.log.info("I2C device found at address {X}.", .{addr});
	    }
	    time.sleep_ms(1000);
	    led.put(0);
	    time.sleep_ms(1000);
    }
    // I2C adresa hodin DS3231 je 0x68
    const hodiny: i2c.Address = @enumFromInt(0x68);
    var hdata: [7]u8 = undefined;
    var z: [1]u8 = .{ 0x00 };

    while (true) { (8)
	    time.sleep_ms(1000);
        if( i2c0.write_then_read_blocking(hodiny, &z, &hdata, rp2040.time.Duration.from_ms(100)) ) {
//            std.log.info("Hodiny fungují.",.{});
        }
        else |err| switch (err) {       (9)
            error.TargetAddressReserved => {
                std.log.err("Hodiny nefungují: {s} konec.", .{"Target address reserved"} );
                rele.put(1); (10)
                hodiny_nefunguji();
            },
            error.NoData => {
                std.log.err("Hodiny nefungují: {s} konec", .{"No data"});
                rele.put(1); //<10
                hodiny_nefunguji();
            },
            error.Timeout => {
                std.log.err("Hodiny nefungují: {s}", .{"Timeout"});
                rele.put(1); (10)
                led.put(0);
                time.sleep_ms(100);
                led.put(1);
                time.sleep_ms(300);
                led.put(0);
                continue;
            },
            error.DeviceNotPresent => {
                std.log.err("Hodiny nefungují: {s}", .{"Device not present"});
                rele.put(1); (10)
                hodiny_nefunguji();
            },
            error.NoAcknowledge => {
                std.log.err("Hodiny nefungují: {s}", .{"No acknowledge"});
                rele.put(1); (10)
                continue;
            },
            error.TxFifoFlushed => {
                std.log.err("Hodiny nefungují: {s}", .{"Tx FIFO flushed"});
                rele.put(1); (10)
                continue;
            },
            error.UnknownAbort => {
                std.log.err("Hodiny nefungují: {s}", .{"Unknown abort"});
                rele.put(1); (10)
                hodiny_nefunguji();
            },

        }
        // zpracování přečtených dat z hodin
        const seconds = 10 * ((hdata[0] & 0x70) >> 4) + (hdata[0] & 0x0F);
        const minutes = 10 * ((hdata[1] & 0x70) >> 4) + (hdata[1] & 0x0F);
// jedeme ve 24 hodinovem modu, mám tak hodiny nastaveny, AM-PM vynechám
//    if(rtc->am_pm_mode) {  // tohle je z céčka
//        data->hours   = 10 * ((raw_data[2] & 0x10) >> 4) + (raw_data[2] & 0x0F);
//        data->am_pm = ((raw_data[2] & 0x20) >> 5);
//    } else {
//        data->hours   = 10 * ((raw_data[2] & 0x30) >> 4) + (raw_data[2] & 0x0F);
//    }


        const hours   = 10 * ((hdata[2] & 0x30) >> 4) + (hdata[2] & 0x0F);
        const day     = (hdata[3] & (0x07));                                // nazev dne v tydnu
        const date    = 10 * ((hdata[4] & 0x30) >> 4) + (hdata[4] & 0x0F);  // den v mesici
        const month   = 10 * ((hdata[5] & 0x10) >> 4) + (hdata[5] & 0x0F);
        const century = (hdata[5] & (0x01 << 7)) >> 7;
        const year    = 10 * ((hdata[6] & 0xF0) >> 4) + (hdata[6] & 0x0F);
        var rok: u16 = 0;
        if(century == 1 ) {
            rok   = 2000 + @as(u16,year);
        } else {
            rok = 1900 + @as(u16,year);
        }
        const nazev_dne: [] const u8 = switch(day) {
            1 => "pondeli",
            2 => "utery",
            3 => "streda",
            4 => "ctvrtek",
            5 => "patek",
            6 => "sobota",
            7 => "nedele",
            else => "nevim",
        };
        std.log.info("datum {d: >2}.{d: >2}.{d} cas {d: >2}:{d:0>2}:{d:0>2} den {s}", .{date, month, rok, hours, minutes, seconds, nazev_dne});

        // zapinani a vypinani rele, zatím jenom v každou celou minutu zapneme na 3 sekundy
        // tady může být naprogramováno cokoliv, třeba něco na způsob cronu
        if(seconds == 0 ) { // zapiname
            rele.put(0);
	        time.sleep_ms(3000);
            rele.put(1);
            }
        else { // vypiname
            rele.put(1);
            }
    }
}
1 Nastavíme GPIO pro fungování interní LED. Interní LED bude sloužit jako indikace inicializace a potom i havárie.
2 Nastavíme GPIO pro relé. Protože modul relé má v sobě tranzistor, který inveruje signál, hodnota 1 znamená vypnuté relé a hodnota 0 znamená zapnuté relé
3 Nastavení pro logovací UART.
4 Nastavení SCL linky pro I2C — pin GPIO4
5 Nastavení SDA linky pro I2C — pin GPIO5
6 Nastavení pinů 4 a 5 pro funkci I2C
7 Testování sběrnice I2C na adresy přítomných zařízení
8 Nekonečná smyčka čtení hodin a zpracování údajů z nich
9 Ošetření chyb za běhu
10 Pokud vypadnou hodiny, je potřeba shodit relé, aby nebylo v sepnutém stavu.
Výstup programu do terminálu počítače (minicom)

minicom

Časová značka v hranatých závorkách je počet sekund od spuštění Pica.

Celý program hodiny_rele.zig
const std = @import("std");
const microzig = @import("microzig");

const rp2040 = microzig.hal;
const i2c = rp2040.i2c;
const gpio = rp2040.gpio;
const time = rp2040.time;
const peripherals = microzig.chip.peripherals;

pub const microzig_options = .{
    .log_level = .info,
    .logFn = rp2040.uart.log,
};

const uart = rp2040.uart.num(0);
const i2c0 = i2c.instance.num(0);
const led  = gpio.num(25);
const rele = gpio.num(22);

const DS3231_SECONDS: u8 = 0x0;

// error function, never return
fn hodiny_nefunguji() void {

    while(true) {
        time.sleep_ms(200);
        led.put(0);
        time.sleep_ms(300);
        led.put(1);
    }
}


pub fn main() !void {
    // Program zacina bezet a LEDka 3x blikne
    led.set_function(.sio);
    led.set_direction(.out);
    led.put(1);
    // nasaveni rele
    rele.set_function(.sio);
    rele.set_direction(.out);
    rele.put(1);                // pin 22 ve stavu 1 znamená vypnute rele (0 je zapnute rele)
    time.sleep_ms(500);
    led.put(0);
    time.sleep_ms(500);
    led.put(1);
    time.sleep_ms(500);
    led.put(0);
    time.sleep_ms(500);
    led.put(1);
    time.sleep_ms(500);
    led.put(0);
    time.sleep_ms(500);


    uart.apply(.{
        .baud_rate = 115200,
        .tx_pin = gpio.num(0),
        .rx_pin = gpio.num(1),
        .clock_config = rp2040.clock_config,
    });
    rp2040.uart.init_logger(uart);

    const scl_pin = gpio.num(4);
    const sda_pin = gpio.num(5);
    inline for (&.{ scl_pin, sda_pin }) |pin| {
        pin.set_slew_rate(.slow);
        pin.set_schmitt_trigger(.enabled);
        pin.set_function(.i2c);
    }

    try i2c0.apply(.{
        .clock_config = rp2040.clock_config,
    });

    // var i: u32 = 0;
    for (0..1) |_| {
	    led.put(1);
	    for (0..std.math.maxInt(u7)) |addr| {
    	    const a: i2c.Address = @enumFromInt(addr);

    	    // Skip over any reserved addresses.
    	    if (a.is_reserved()) continue;

    	    var rx_data: [1]u8 = undefined;
    	    _ = i2c0.read_blocking(a, &rx_data, rp2040.time.Duration.from_ms(100)) catch continue;

    	    std.log.info("I2C device found at address {X}.", .{addr});
	    }
	    time.sleep_ms(1000);
	    led.put(0);
	    time.sleep_ms(1000);
    }
    // adresa je 0x68
    const hodiny: i2c.Address = @enumFromInt(0x68);
    var hdata: [7]u8 = undefined;
    var z: [1]u8 = .{ 0x00 };

    while (true) {
	    time.sleep_ms(1000);
        if( i2c0.write_then_read_blocking(hodiny, &z, &hdata, rp2040.time.Duration.from_ms(100)) ) {
//            std.log.info("Hodiny fungují.",.{});
        }
        else |err| switch (err) {
            error.TargetAddressReserved => {
                std.log.err("Hodiny nefungují: {s} konec.", .{"Target address reserved"} );
                rele.put(1);
                hodiny_nefunguji();
            },
            error.NoData => {
                std.log.err("Hodiny nefungují: {s} konec", .{"No data"});
                rele.put(1);
                hodiny_nefunguji();
            },
            error.Timeout => {
                std.log.err("Hodiny nefungují: {s}", .{"Timeout"});
                rele.put(1);
                led.put(0);
                time.sleep_ms(100);
                led.put(1);
                time.sleep_ms(300);
                led.put(0);
                continue;
            },
            error.DeviceNotPresent => {
                std.log.err("Hodiny nefungují: {s}", .{"Device not present"});
                rele.put(1);
                hodiny_nefunguji();
            },
            error.NoAcknowledge => {
                std.log.err("Hodiny nefungují: {s}", .{"No acknowledge"});
                rele.put(1);
                continue;
            },
            error.TxFifoFlushed => {
                std.log.err("Hodiny nefungují: {s}", .{"Tx FIFO flushed"});
                rele.put(1);
                continue;
            },
            error.UnknownAbort => {
                std.log.err("Hodiny nefungují: {s}", .{"Unknown abort"});
                rele.put(1);
                hodiny_nefunguji();
            },

        }
        const seconds = 10 * ((hdata[0] & 0x70) >> 4) + (hdata[0] & 0x0F);
        const minutes = 10 * ((hdata[1] & 0x70) >> 4) + (hdata[1] & 0x0F);
// jedeme ve 24 hodinovem modu
//    if(rtc->am_pm_mode) {
//        data->hours   = 10 * ((raw_data[2] & 0x10) >> 4) + (raw_data[2] & 0x0F);
//        data->am_pm = ((raw_data[2] & 0x20) >> 5);
//    } else {
//        data->hours   = 10 * ((raw_data[2] & 0x30) >> 4) + (raw_data[2] & 0x0F);
//    }


        const hours   = 10 * ((hdata[2] & 0x30) >> 4) + (hdata[2] & 0x0F);
        const day     = (hdata[3] & (0x07));                                // nazev dne v tydnu
        const date    = 10 * ((hdata[4] & 0x30) >> 4) + (hdata[4] & 0x0F);  // den v mesici
        const month   = 10 * ((hdata[5] & 0x10) >> 4) + (hdata[5] & 0x0F);
        const century = (hdata[5] & (0x01 << 7)) >> 7;
        const year    = 10 * ((hdata[6] & 0xF0) >> 4) + (hdata[6] & 0x0F);
        var rok: u16 = 0;
        if(century == 1 ) {
            rok   = 2000 + @as(u16,year);
        } else {
            rok = 1900 + @as(u16,year);
        }
        const nazev_dne: [] const u8 = switch(day) {
            1 => "pondeli",
            2 => "utery",
            3 => "streda",
            4 => "ctvrtek",
            5 => "patek",
            6 => "sobota",
            7 => "nedele",
            else => "nevim",
        };
        std.log.info("datum {d: >2}.{d: >2}.{d} cas {d: >2}:{d:0>2}:{d:0>2} den {s}", .{date, month, rok, hours, minutes, seconds, nazev_dne});
        if(seconds == 0 ) { // zapiname
            rele.put(0);
	        time.sleep_ms(3000);
            rele.put(1);
            }
        else { // vypiname
            rele.put(1);
            }
    }

}

Program je ještě trochu prasečina, Zig se učím za pochodu a čtení hodin by mohlo vypadat mnohem lépe. Zatím je hodně syrové. Program ale funguje, umí se i vypořádat s mimořádnými stavy, jako že vypadnou hodiny. Testoval jsem přerušení SDA linky I2c a program zahlásí chybu Timeout, po zapojení zpět se zotaví z chyby a pokračuje. U jiných chyb program skončí a začne zuřivě blikat diodou.

Zdroje a odkazy