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í



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.
const std = @import("std"); (1)
const microzig = @import("microzig"); (2)
| 1 | Importujeme standardní knihovnu Zigu. |
| 2 | Importujeme knihovnu MicroZig |
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 |
// 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.
pub const microzig_options = .{
.log_level = .debug,
.logFn = rp2040.uart.log,
};
Je stejné jako v projektu s UARTem.
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. |

Časová značka v hranatých závorkách je počet sekund od spuštění Pica.
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.