From b8e03a574fff37fdf631a0cefa52afeed8264b31 Mon Sep 17 00:00:00 2001 From: "Guilherme S. Schultz" <65524156+RecursiveError@users.noreply.github.com> Date: Sat, 7 Dec 2024 14:37:55 -0300 Subject: [PATCH] add hd44780 driver + rppico example (#301) * add hd44780 driver + rppico example * Add PCF8574 Driver + rppico example * update hd44780 Driver to use PCF8574 driver --- drivers/build.zig.zon | 1 + drivers/display/hd44780.zig | 318 ++++++++++++++++++ drivers/framework.zig | 10 + drivers/io_expander/pcf8574.zig | 96 ++++++ examples/raspberrypi/rp2xxx/build.zig | 2 + .../rp2xxx/src/rp2040_only/hd44780.zig | 63 ++++ .../rp2xxx/src/rp2040_only/pcf8574.zig | 40 +++ port/raspberrypi/rp2xxx/src/hal/drivers.zig | 4 +- 8 files changed, 532 insertions(+), 2 deletions(-) create mode 100644 drivers/display/hd44780.zig create mode 100644 drivers/io_expander/pcf8574.zig create mode 100644 examples/raspberrypi/rp2xxx/src/rp2040_only/hd44780.zig create mode 100644 examples/raspberrypi/rp2xxx/src/rp2040_only/pcf8574.zig diff --git a/drivers/build.zig.zon b/drivers/build.zig.zon index 3ee0d71d..b5fec340 100644 --- a/drivers/build.zig.zon +++ b/drivers/build.zig.zon @@ -9,5 +9,6 @@ "base", "display", "input", + "io_expander", }, } diff --git a/drivers/display/hd44780.zig b/drivers/display/hd44780.zig new file mode 100644 index 00000000..af0f0a92 --- /dev/null +++ b/drivers/display/hd44780.zig @@ -0,0 +1,318 @@ +const mdf = @import("../framework.zig"); +const DigitalIO = mdf.base.Digital_IO; +pub const delayus_callback = fn (delay: u32) void; + +pub const LCD_Commands = enum(u8) { + clear = 0x01, + reset = 0x02, + shift_cursor_left = 0x10, + shift_cursor_right = 0x14, + shift_display_left = 0x18, + shift_display_right = 0x1C, +}; + +//lcd "function set" enums +const FunctionSet = enum(u8) { + line = 0x8, + bus = 0x10, + char = 0x4, +}; + +pub const Lines = enum { one, two }; +pub const BusSize = enum { four, eight }; +pub const CharSize = enum { @"5x8", @"5x10" }; + +//lcd "entry mode" enums +const EntryMode = enum(u8) { shift_mode = 0x1, shift_dir = 0x2 }; +pub const State = enum { + off, + on, +}; + +pub const ShiftDirection = enum { + dec, + inc, +}; + +//lcd "display control" enums +const DisplayControl = enum(u8) { + blink = 0x01, + cursor = 0x02, + display = 0x04, +}; + +pub const DeviceConfig = struct { + lines: Lines = .two, + bus: BusSize = .four, + char_size: CharSize = .@"5x8", + shift_direction: ShiftDirection = .inc, + display_shift: State = .off, + cursor: State = .off, + cursor_blink: State = .off, + display: State = .on, + skip_begin_delay: bool = false, +}; + +pub const HD44780_Config = struct { + high_pins: type = DigitalIO, + lower_pins: type = DigitalIO, + RS: type = DigitalIO, + EN1: type = DigitalIO, + EN2: type = DigitalIO, + BK: type = DigitalIO, +}; + +pub const EnableSet = enum { + EN1, + EN2, + All, +}; + +pub fn HD44780(comptime config: HD44780_Config) type { + return struct { + const Self = @This(); + pub const pins_struct = struct { + high_pins: [4]config.high_pins, + lower_pins: ?[4]config.lower_pins = null, + RS: config.RS, + EN1: config.EN1, + EN2: ?config.EN2 = null, + BK: ?config.BK = null, + }; + + //internal Deviceconfig vars + function_set: u6 = 0, + entry_mode: u3 = 0, + display_control: u4 = 0, + enable_set: EnableSet = .EN1, + full_bus: bool = false, + internal_delay: *const delayus_callback, + io_interface: pins_struct, + + //LCD functions + fn set_rs_pin(lcd: *Self, value: u1) !void { + try lcd.io_interface.RS.write(@enumFromInt(value)); + } + + fn pulse_en_pin(lcd: *Self) !void { + switch (lcd.enable_set) { + .EN1 => { + try lcd.io_interface.EN1.write(.high); + lcd.internal_delay(1); + try lcd.io_interface.EN1.write(.low); + lcd.internal_delay(1); + }, + .EN2 => { + //en2 is check on select_lcd + try lcd.io_interface.EN2.?.write(.high); + lcd.internal_delay(1); + try lcd.io_interface.EN2.?.write(.low); + lcd.internal_delay(1); + }, + .All => { + try lcd.io_interface.EN1.write(.high); + try lcd.io_interface.EN2.?.write(.high); + lcd.internal_delay(1); + try lcd.io_interface.EN1.write(.low); + try lcd.io_interface.EN2.?.write(.low); + lcd.internal_delay(1); + }, + } + } + + fn load_data(lcd: *Self, data: u8) !void { + //load high-bits first + for (0..4) |index| { + const pin_bit: u3 = @as(u3, @intCast(index)) + 4; + const value: u1 = if (data & (@as(u8, 1) << pin_bit) != 0) 1 else 0; + try lcd.io_interface.high_pins[index].write(@enumFromInt(value)); + } + + if (lcd.full_bus) { + if (lcd.io_interface.lower_pins) |pins| { + //load lower-bits + for (0..4) |index| { + const value: u1 = if (data & (@as(u8, 1) << @intCast(index)) != 0) 1 else 0; + try pins[index].write(@enumFromInt(value)); + } + } else { + return error.UnsupportedBus; + } + } + } + + fn send8bits(lcd: *Self, data: u8, rs_state: u1) !void { + try lcd.set_rs_pin(rs_state); + try lcd.load_data(data); + try lcd.pulse_en_pin(); + } + + fn send4bits(lcd: *Self, data: u8, rs_state: u1) !void { + const high_nibble: u8 = data & 0xF0; + const low_nibble: u8 = data << 4; + try lcd.send8bits(high_nibble, rs_state); + lcd.internal_delay(1); + try lcd.send8bits(low_nibble, rs_state); + } + + //Low level sendfunction + pub fn send(lcd: *Self, data: u8, rs_state: u1) !void { + if (lcd.full_bus) { + try lcd.send8bits(data, rs_state); + } else { + try lcd.send4bits(data, rs_state); + } + + if (rs_state == 0) { + lcd.internal_delay(40); + } else { + lcd.internal_delay(2); + } + } + + //========== commands functions ========== + + //low level command function + pub inline fn command(lcd: *Self, cmd: LCD_Commands) !void { + try lcd.send(@intFromEnum(cmd), 0); + } + + pub fn screen_clear(lcd: *Self) !void { + try lcd.command(.clear); + lcd.internal_delay(1600); //clear and reset need to delay >1.6ms + } + + pub fn reset_cursor(lcd: *Self) !void { + try lcd.command(.reset); + lcd.internal_delay(1600); //clear and reset need to delay >1.6ms + } + + pub fn shift_cursor_left(lcd: *Self) !void { + try lcd.command(.shift_cursor_left); + } + + pub fn shift_cursor_right(lcd: *Self) !void { + try lcd.command(.shift_cursor_right); + } + + pub fn shift_display_left(lcd: *Self) !void { + try lcd.command(.shift_display_left); + } + + pub fn shift_display_right(lcd: *Self) !void { + try lcd.command(.shift_display_right); + } + + //control functions + + pub fn shift_control(lcd: *Self, st: State) !void { + switch (st) { + .on => lcd.entryMode |= EntryMode.shift_mode, + .off => lcd.entryMode &= ~EntryMode.shift_mode, + } + try lcd.send(lcd.entryMode, 0); + } + + pub fn shift_mode(lcd: *Self, mode: ShiftDirection) !void { + switch (mode) { + .inc => lcd.entryMode |= EntryMode.shift_dir, + .dec => lcd.entryMode &= ~EntryMode.shift_dir, + } + try lcd.send(lcd.entryMode, 0); + } + + pub fn display_set(lcd: *Self, st: State) !void { + switch (st) { + .on => lcd.displayControl |= DisplayControl.display, + .off => lcd.displayControl &= ~DisplayControl.display, + } + try lcd.send(lcd.displayControl, 0); + } + + pub fn cursor_control(lcd: *Self, st: State) !void { + switch (st) { + .on => lcd.displayControl |= DisplayControl.cursor, + .off => lcd.displayControl &= ~DisplayControl.cursor, + } + try lcd.send(lcd.displayControl, 0); + } + + pub fn cursor_blink_control(lcd: *Self, st: State) !void { + switch (st) { + .on => lcd.displayControl |= DisplayControl.blink, + .off => lcd.displayControl &= ~DisplayControl.blink, + } + try lcd.send(lcd.displayControl, 0); + } + + pub fn select_lcd(lcd: *Self, en: EnableSet) !void { + switch (en) { + .EN2, .All => { + if (lcd.io_interface.lower_pins == null) { + return error.InvlaidEnablePin; + } + }, + else => {}, + } + lcd.enable_set = en; + } + + pub fn set_cursor(lcd: *Self, line: u8, col: u8) !void { + const addrs = [_]u8{ 0x80, 0xC0 }; + if ((line < 2) and (col < 40)) { + try lcd.send(addrs[line] | col, 0); + } + } + + pub fn set_backlight(lcd: *Self, value: u1) !void { + if (lcd.io_interface.BK) |bk| { + try bk.write(@enumFromInt(value)); + return; + } + return error.NoBacklight; + } + + pub fn create_custom_char(lcd: *Self, new_char: [8]u8, mem_addr: u3) !void { + const mem_aux = (@as(u8, mem_addr) << 3) | 0x40; + try lcd.send(mem_aux, 0); + for (new_char) |line| { + try lcd.send(line, 1); + } + } + + pub fn write(lcd: *Self, text: []const u8) !void { + for (text) |char| { + try lcd.send(char, 1); + } + } + + pub fn init_device(lcd: *Self, device_config: DeviceConfig) !void { + lcd.function_set = (1 << 5) + (@as(u6, @intFromEnum(device_config.bus)) << 4) + (@as(u6, @intFromEnum(device_config.lines)) << 3) + (@as(u6, @intFromEnum(device_config.char_size)) << 2); + lcd.entry_mode = (1 << 2) + (@as(u3, @intFromEnum(device_config.shift_direction)) << 1) + @intFromEnum(device_config.display_shift); + lcd.display_control = (1 << 3) + (@as(u4, @intFromEnum(device_config.display)) << 2) + (@as(u4, @intFromEnum(device_config.cursor)) << 1) + @intFromEnum(device_config.cursor_blink); + lcd.full_bus = !(device_config.bus == .four); + if (!device_config.skip_begin_delay) { + lcd.internal_delay(55000); //wait time = power on time + init time (datasheet: power up time = >40ms | begin time = >15ms) + } + try lcd.send8bits(0x30, 0); + lcd.internal_delay(4100); + try lcd.send8bits(0x30, 0); + lcd.internal_delay(100); + try lcd.send8bits(0x30, 0); + lcd.internal_delay(100); + try lcd.send8bits(0x20, 0); + try lcd.send(lcd.function_set, 0); + try lcd.screen_clear(); + try lcd.reset_cursor(); + try lcd.send(lcd.entry_mode, 0); + try lcd.send(lcd.display_control, 0); + } + + pub fn init(io_pins: pins_struct, delay_callback: *const delayus_callback) Self { + return .{ + .internal_delay = delay_callback, + .io_interface = io_pins, + }; + } + }; +} diff --git a/drivers/framework.zig b/drivers/framework.zig index 1790e86e..39154671 100644 --- a/drivers/framework.zig +++ b/drivers/framework.zig @@ -5,11 +5,13 @@ pub const display = struct { pub const ssd1306 = @import("display/ssd1306.zig"); pub const st77xx = @import("display/st77xx.zig"); + pub const hd44780 = @import("display/hd44780.zig"); // Export generic drivers: pub const SSD1306_I2C = ssd1306.SSD1306_I2C; pub const ST7735 = st77xx.ST7735; pub const ST7789 = st77xx.ST7789; + pub const HD44780 = hd44780.HD44780; // Export color types: pub const colors = @import("display/colors.zig"); @@ -31,6 +33,11 @@ pub const input = struct { }; }; +pub const IO_expander = struct { + pub const pcf8574 = @import("io_expander/pcf8574.zig"); + pub const PCF8574 = pcf8574.PCF8574; +}; + pub const wireless = struct { // pub const sx1278 = @import("wireless/sx1278.zig"); }; @@ -44,11 +51,14 @@ pub const base = struct { test { _ = display.ssd1306; _ = display.st77xx; + _ = display.HD44780; _ = input.keyboard_matrix; _ = input.debounced_button; _ = input.rotary_encoder; + _ = IO_expander.pcf8574; + _ = base.Datagram_Device; _ = base.Stream_Device; _ = base.Digital_IO; diff --git a/drivers/io_expander/pcf8574.zig b/drivers/io_expander/pcf8574.zig new file mode 100644 index 00000000..9e7d49a0 --- /dev/null +++ b/drivers/io_expander/pcf8574.zig @@ -0,0 +1,96 @@ +const mdf = @import("../framework.zig"); +const Digital_IO = mdf.base.Digital_IO; +const State = Digital_IO.State; +const Vtable = Digital_IO.VTable; +const ReadError = Digital_IO.ReadError; +const SetBiasError = Digital_IO.SetBiasError; +const SetDirError = Digital_IO.SetDirError; +const WriteError = Digital_IO.WriteError; + +pub const PCF8574_Config = struct { + Datagram_Device: type = mdf.base.Datagram_Device, +}; + +pub fn PCF8574(comptime config: PCF8574_Config) type { + return struct { + const Self = @This(); + interface: config.Datagram_Device, + pins: u8 = 0, + pin_arr: [8]u8 = undefined, + + inline fn update(self: *const Self) !void { + try self.interface.write(&[1]u8{self.pins}); + } + + pub fn put(self: *Self, pin: u3, st: State) !void { + const mask: u8 = @as(u8, 1) << pin; + switch (st) { + .high => self.pins |= mask, + .low => self.pins &= ~mask, + } + try self.update(); + } + + pub fn toggle(self: *Self, pin: u3) !void { + const mask: u8 = @as(u8, 1) << pin; + self.pins ^= mask; + try self.update(); + } + + ///The PCF8574 does not have an internal configuration for output/input + ///every read requires pullup enabled, otherwise it always returns 0 regardless of the signal on the pin + pub fn read(self: *Self, pin: u3) !State { + const mask: u8 = @as(u8, 1) << pin; + var pkg: [1]u8 = .{mask | self.pins}; + const read_value = try self.interface.read(&pkg); //keep pullup enable until next call + return if (read_value & mask != 0) State.high else State.low; + } + + pub fn init(datagram: config.Datagram_Device) Self { + var obj = Self{ .interface = datagram }; + for (0..obj.pin_arr.len) |index| { + obj.pin_arr[index] = @intCast(index); + } + return obj; + } + + const vtable = Vtable{ + .read_fn = read_fn, + .set_bias_fn = set_bias_fn, + .set_direction_fn = set_dir_fn, + .write_fn = write_fn, + }; + + fn read_fn(ctx: *anyopaque) ReadError!State { + const pin_ref: *u8 = @alignCast(@ptrCast(ctx)); + const index = pin_ref.*; + const pin_base: *[8]u8 = @ptrFromInt(@intFromPtr(pin_ref) - index); //just 1 byte + var PCF_base: *Self = @fieldParentPtr("pin_arr", pin_base); + const state = PCF_base.read(@intCast(index)) catch return ReadError.IoError; + return state; + } + + fn write_fn(ctx: *anyopaque, state: State) WriteError!void { + const pin_ref: *u8 = @alignCast(@ptrCast(ctx)); + const index = pin_ref.*; + const pin_base: *[8]u8 = @ptrFromInt(@intFromPtr(pin_ref) - index); //just 1 byte + var PCF_base: *Self = @fieldParentPtr("pin_arr", pin_base); + PCF_base.put(@intCast(index), state) catch return WriteError.IoError; + } + + fn set_bias_fn(_: *anyopaque, _: ?State) SetBiasError!void { + return SetBiasError.Unsupported; + } + + fn set_dir_fn(_: *anyopaque, _: Digital_IO.Direction) SetDirError!void { + return SetDirError.Unsupported; + } + + pub fn digital_IO(self: *Self, pin: u3) Digital_IO { + return Digital_IO{ + .object = &self.pin_arr[pin], + .vtable = &vtable, + }; + } + }; +} diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 1ee455fe..d4adf7c9 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -25,6 +25,8 @@ pub fn build(b: *std.Build) void { .{ .target = mb.ports.rp2xxx.boards.raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, .{ .target = mb.ports.rp2xxx.boards.raspberrypi.pico, .name = "pico_usb-cdc", .file = "src/rp2040_only/usb_cdc.zig" }, .{ .target = mb.ports.rp2xxx.boards.raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, + .{ .target = mb.ports.rp2xxx.boards.raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, + .{ .target = mb.ports.rp2xxx.boards.raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, // WaveShare Boards: .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040-matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/hd44780.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/hd44780.zig new file mode 100644 index 00000000..cb928d11 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/hd44780.zig @@ -0,0 +1,63 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const drivers = microzig.drivers; +const lcd_driver = drivers.display.hd44780; +const lcd = drivers.display.HD44780; +const PCF8574 = drivers.IO_expander.PCF8574; +const State = drivers.base.Digital_IO.State; + +const rp2040 = microzig.hal; +const i2c = rp2040.i2c; +const I2C_Device = rp2040.drivers.I2C_Device; +const gpio = rp2040.gpio; +const peripherals = microzig.chip.peripherals; +const timer = rp2040.time; + +const i2c0 = i2c.instance.num(0); + +const i2c_device = I2C_Device.init(i2c0, @enumFromInt(0x27)); + +pub fn delay_us(time_delay: u32) void { + timer.sleep_us(time_delay); +} + +const msg = "hello world - From Zig"; + +pub fn main() !void { + const scl_pin = gpio.num(5); + const sda_pin = gpio.num(4); + 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 expander = PCF8574(.{ .Datagram_Device = I2C_Device }).init(i2c_device); + const pins_config = lcd(.{}).pins_struct{ + .high_pins = .{ + expander.digital_IO(4), + expander.digital_IO(5), + expander.digital_IO(6), + expander.digital_IO(7), + }, + .BK = expander.digital_IO(3), + .RS = expander.digital_IO(0), + .EN1 = expander.digital_IO(2), + }; + var my_lcd = lcd(.{}).init( + pins_config, + delay_us, + ); + + try my_lcd.init_device(.{}); + try my_lcd.set_backlight(1); + try my_lcd.write(msg); + + while (true) { + try my_lcd.shift_display_left(); + timer.sleep_ms(300); + } +} diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/pcf8574.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/pcf8574.zig new file mode 100644 index 00000000..08f1f85c --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/pcf8574.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const microzig = @import("microzig"); + +//Driver imports +const drivers = microzig.drivers; +const PCF8574 = drivers.IO_expander.PCF8574; +const State = drivers.base.Digital_IO.State; + +//Hal imports +const rp2040 = microzig.hal; +const i2c = rp2040.i2c; +const I2C_Device = rp2040.drivers.I2C_Device; +const gpio = rp2040.gpio; +const peripherals = microzig.chip.peripherals; +const timer = rp2040.time; + +const i2c0 = i2c.instance.num(0); + +const i2c_device = I2C_Device.init(i2c0, @enumFromInt(0x27)); +pub fn main() !void { + const scl_pin = gpio.num(5); + const sda_pin = gpio.num(4); + 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 expander = PCF8574(.{ .Datagram_Device = I2C_Device }).init(i2c_device); + var led = expander.digital_IO(3); + while (true) { + try led.write(State.high); + timer.sleep_ms(500); + try led.write(State.low); + timer.sleep_ms(500); + } +} diff --git a/port/raspberrypi/rp2xxx/src/hal/drivers.zig b/port/raspberrypi/rp2xxx/src/hal/drivers.zig index 8c7d4aab..d47a83eb 100644 --- a/port/raspberrypi/rp2xxx/src/hal/drivers.zig +++ b/port/raspberrypi/rp2xxx/src/hal/drivers.zig @@ -48,7 +48,7 @@ pub const I2C_Device = struct { _ = dev; } - pub fn write(dev: I2C_Device, datagram: []const []const u8) !void { + pub fn write(dev: I2C_Device, datagram: []const u8) !void { try dev.bus.write_blocking(dev.address, datagram, null); } @@ -56,7 +56,7 @@ pub const I2C_Device = struct { try dev.bus.writev_blocking(dev.address, datagrams, null); } - pub fn read(dev: I2C_Device, datagram: []const u8) !usize { + pub fn read(dev: I2C_Device, datagram: []u8) !usize { try dev.bus.read_blocking(dev.address, datagram, null); return datagram.len; }