diff --git a/drivers/display/colors.zig b/drivers/display/colors.zig index 9f9f9cd4f..a91bb1692 100644 --- a/drivers/display/colors.zig +++ b/drivers/display/colors.zig @@ -1,6 +1,8 @@ //! //! This file provides common color types found on the supported displays. //! +//! +const std = @import("std"); /// A color type encoding only black and white. pub const BlackWhite = enum(u1) { @@ -8,33 +10,29 @@ pub const BlackWhite = enum(u1) { white = 1, }; -pub const RGB565 = Color(.{ u5, u6, u5 }); -pub const RGB888 = Color(.{ u8, u8, u8 }); - -/// Provides a namespace with the default colors for the given `Color` type. -pub fn Color(comptime Ts: [3]type) type { - return packed struct { +pub fn RGB565_Generic(desired_endianness: std.builtin.Endian) type { + return struct { const Self = @This(); - r: Ts[0], - g: Ts[1], - b: Ts[2], + value: u16, - pub const black: Self = .from_rgb(0x00, 0x00, 0x00); - pub const white: Self = .from_rgb(0xFF, 0xFF, 0xFF); - pub const red: Self = .from_rgb(0xFF, 0x00, 0x00); - pub const green: Self = .from_rgb(0x00, 0xFF, 0x00); - pub const blue: Self = .from_rgb(0x00, 0x00, 0xFF); - pub const cyan: Self = .from_rgb(0x00, 0xFF, 0xFF); - pub const magenta: Self = .from_rgb(0xFF, 0x00, 0xFF); - pub const yellow: Self = .from_rgb(0xFF, 0xFF, 0x00); + pub const black: Self = from_rgb(0x00, 0x00, 0x00); + pub const white: Self = from_rgb(0xFF, 0xFF, 0xFF); + pub const red: Self = from_rgb(0xFF, 0x00, 0x00); + pub const green: Self = from_rgb(0x00, 0xFF, 0x00); + pub const blue: Self = from_rgb(0x00, 0x00, 0xFF); + pub const cyan: Self = from_rgb(0x00, 0xFF, 0xFF); + pub const magenta: Self = from_rgb(0xFF, 0x00, 0xFF); + pub const yellow: Self = from_rgb(0xFF, 0xFF, 0x00); pub fn from_rgb(r: u8, g: u8, b: u8) Self { - return Self{ - .r = @truncate(r >> (8 - @bitSizeOf(Ts[0]))), - .g = @truncate(g >> (8 - @bitSizeOf(Ts[1]))), - .b = @truncate(b >> (8 - @bitSizeOf(Ts[2]))), - }; + const r5: u16 = @as(u16, r) >> 3; + const g6: u16 = @as(u16, g) >> 2; + const b5: u16 = @as(u16, b) >> 3; + + const v: u16 = (r5 << 11) | (g6 << 5) | b5; + + return .{ .value = std.mem.nativeTo(u16, v, desired_endianness) }; } }; } diff --git a/drivers/display/st77xx.zig b/drivers/display/st77xx.zig index 10597873c..47edde558 100644 --- a/drivers/display/st77xx.zig +++ b/drivers/display/st77xx.zig @@ -1,7 +1,6 @@ //! //! Driver for the ST7735 and ST7789 for the 4-line serial protocol or 8-bit parallel interface //! -//! This driver is a port of https://github.com/adafruit/Adafruit-ST7735-Library //! //! Datasheets: //! - https://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf @@ -10,17 +9,42 @@ //! const std = @import("std"); const mdf = @import("../framework.zig"); -const Color = mdf.display.colors.RGB565; -pub const ST7735 = ST77xx_Generic(.{ - .device = .st7735, -}); +pub fn ST7735(display_cfg: Display_Config) type { + return ST77xx_Generic(.{ + .device = .st7735, + }, display_cfg); +} + +pub fn ST7789(display_cfg: Display_Config) type { + return ST77xx_Generic(.{ + .device = .st7789, + }, display_cfg); +} + +pub const Device = enum { + st7735, + st7789, +}; + +pub const Resolution = struct { + width: u16, + height: u16, +}; + +const ColorOrder = enum(u1) { + rgb = 0, + bgr = 1, +}; -pub const ST7789 = ST77xx_Generic(.{ - .device = .st7789, -}); +const Rotation = enum(u2) { + deg0, + deg90, + deg180, + deg270, +}; -pub const ST77xx_Options = struct { +pub const Driver_Config = struct { /// Which SST77xx device does the driver target? device: Device, @@ -29,158 +53,285 @@ pub const ST77xx_Options = struct { /// Which digital i/o interface should be used. Digital_IO: type = mdf.base.Digital_IO, + + /// Which color format should be used when writing pixel data. + Color: type = mdf.display.colors.RGB565_Generic(.big), }; -pub fn ST77xx_Generic(comptime options: ST77xx_Options) type { - return struct { - const Driver = @This(); - const Datagram_Device = options.Datagram_Device; - const Digital_IO = options.Digital_IO; +pub const Display_Config = struct { + const Self = @This(); + /// Native panel resolution in pixels. + resolution: Resolution, + + /// Horizontal RAM offset applied when setting the address window. + /// + /// Some ST77xx modules expose a visible area smaller than controller RAM. + /// Use this to shift drawing into the visible window. + x_offset: u16 = 0, + + /// Vertical RAM offset applied when setting the address window. + y_offset: u16 = 0, + + /// Secondary horizontal RAM offset for rotated display states. + x_offset2: u16 = 0, + + /// Secondary vertical RAM offset for rotated display states. + y_offset2: u16 = 0, + + /// Color channel order written to MADCTL (RGB or BGR). + color_order: ColorOrder = .rgb, + + /// If true, send `INVON` during initialization. + display_inversion: bool = false, + + /// Initial display rotation used to initialize MADCTL. + rotation: Rotation = .deg0, + + /// Preset for common ST7735 mini 160x80 modules that use BGR order, + /// require display inversion, and have a shifted visible window. + pub const lcd160x80bgr: Self = .{ + .resolution = .{ .width = 80, .height = 160 }, + .x_offset = 26, + .y_offset = 1, + .x_offset2 = 26, + .y_offset2 = 1, + .color_order = .bgr, + .display_inversion = true, + }; - const dev = switch (options.device) { - .st7735 => ST7735_Device, - .st7789 => ST7789_Device, - }; + /// Preset for common 240x240 modules with RGB color order and no display inversion. + pub const lcd240x240rgb: Self = .{ + .resolution = .{ .width = 240, .height = 240 }, + .x_offset = 0, + .y_offset = 0, + .x_offset2 = 0, + .y_offset2 = 80, + .color_order = .rgb, + .display_inversion = true, + }; +}; + +pub fn ST77xx_Generic(driver_cfg: Driver_Config, display_cfg: Display_Config) type { + return struct { + const Self = @This(); + const Datagram_Device = driver_cfg.Datagram_Device; + const Digital_IO = driver_cfg.Digital_IO; + pub const Color = driver_cfg.Color; dd: Datagram_Device, dev_rst: Digital_IO, dev_datcmd: Digital_IO, + madctl: MemoryDataAccessControl, - resolution: Resolution, + resolution: Resolution = display_cfg.resolution, + x_offset: u16 = display_cfg.x_offset, + y_offset: u16 = display_cfg.y_offset, pub fn init( channel: Datagram_Device, rst: Digital_IO, data_cmd: Digital_IO, - resolution: Resolution, - ) !Driver { - const dri = Driver{ + delay_ms: *const fn (ms: u32) void, + ) !Self { + var dri = Self{ .dd = channel, .dev_rst = rst, .dev_datcmd = data_cmd, - .resolution = resolution, + .madctl = .{ .rgb = display_cfg.color_order, .addr = MemoryDataAccessControl.AddressOrder.from_rotation(display_cfg.rotation) }, }; - // initSPI(freq, spiMode); - - // Run init sequence - // uint8_t numCommands, cmd, numArgs; - // uint16_t ms; - - // numCommands = pgm_read_byte(addr++); // Number of commands to follow - // while (numCommands--) { // For each command... - // cmd = pgm_read_byte(addr++); // Read command - // numArgs = pgm_read_byte(addr++); // Number of args to follow - // ms = numArgs & ST_CMD_DELAY; // If hibit set, delay follows args - // numArgs &= ~ST_CMD_DELAY; // Mask out delay bit - // sendCommand(cmd, addr, numArgs); - // addr += numArgs; - - // if (ms) { - // ms = pgm_read_byte(addr++); // Read post-command delay time (ms) - // if (ms == 255) - // ms = 500; // If 255, delay for 500 ms - // delay(ms); - // } - // } - try dri.set_spi_mode(.data); + try dri.hard_reset(delay_ms); + try dri.init_sequence(delay_ms); + return dri; } - pub fn set_address_window(dri: Driver, x: u16, y: u16, w: u16, h: u16) !void { - // x += _xstart; - // y += _ystart; + fn set_spi_mode(dri: *Self, mode: enum { data, command }) !void { + try dri.dev_datcmd.write(switch (mode) { + .command => .low, + .data => .high, + }); + } - const xa = (@as(u32, x) << 16) | (x + w - 1); - const ya = (@as(u32, y) << 16) | (y + h - 1); + fn hard_reset(dri: *Self, delay_ms: *const fn (ms: u32) void) !void { + try dri.dev_rst.write(.high); + delay_ms(200); + try dri.dev_rst.write(.low); + delay_ms(200); + try dri.dev_rst.write(.high); + delay_ms(200); + } - try dri.write_command(.caset, std.mem.asBytes(&xa)); // Column addr set - try dri.write_command(.raset, std.mem.asBytes(&ya)); // Row addr set - try dri.write_command(.ramwr, &.{}); // write to RAM + inline fn range_to_bigendian_bytes(start: u16, end: u16) [4]u8 { + return .{ @as(u8, @truncate(start >> 8)), @as(u8, @truncate(start)), @as(u8, @truncate(end >> 8)), @as(u8, @truncate(end)) }; } - pub fn set_rotation(dri: Driver, rotation: Rotation) !void { - var control_byte: u8 = madctl_rgb; + // Initialization sequences based on datasheet recommendations and the Adafruit ST7735 library: + // https://github.com/adafruit/Adafruit-ST7735-Library + inline fn init_sequence(dri: *Self, delay_ms: *const fn (ms: u32) void) !void { + switch (comptime driver_cfg.device) { + .st7735 => try dri.init_sequence_st7735(delay_ms), + .st7789 => try dri.init_sequence_st7789(delay_ms), + } + } - switch (rotation) { - .normal => { - control_byte = (madctl_mx | madctl_my | madctl_rgb); - // _xstart = _colstart; - // _ystart = _rowstart; - }, - .right90 => { - control_byte = (madctl_my | madctl_mv | madctl_rgb); - // _ystart = _colstart; - // _xstart = _rowstart; - }, - .upside_down => { - control_byte = (madctl_rgb); - // _xstart = _colstart; - // _ystart = _rowstart; - }, - .left90 => { - control_byte = (madctl_mx | madctl_mv | madctl_rgb); - // _ystart = _colstart; - // _xstart = _rowstart; - }, + fn init_sequence_st7735(dri: *Self, delay_ms: *const fn (ms: u32) void) !void { + // Reset the controller and exit sleep mode. + try dri.write_command(.swreset, &.{}); + delay_ms(150); + try dri.write_command(.slpout, &.{}); + delay_ms(500); + + // Configure frame rate and inversion behaviour for stable scanning. + try dri.write_command(.frmctr1, &.{ 0x01, 0x2C, 0x2D }); + try dri.write_command(.frmctr2, &.{ 0x01, 0x2C, 0x2D }); + try dri.write_command(.frmctr3, &.{ 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D }); + + try dri.write_command(.invctr, &.{0x07}); + + // Power control and VCOM tuning values from common reference init tables. + try dri.write_command(.pwmctr1, &.{ 0xA2, 0x02, 0x84 }); + try dri.write_command(.pwmctr2, &.{0xC5}); + try dri.write_command(.pwmctr3, &.{ 0x0A, 0x00 }); + try dri.write_command(.pwmctr4, &.{ 0x8A, 0x2A }); + try dri.write_command(.pwmctr5, &.{ 0x8A, 0xEE }); + + try dri.write_command(.vmctr1, &.{0x0E}); + try dri.write_command(.invoff, &.{}); + + // Select 16-bit pixel format and apply orientation / color order. + try dri.write_command(.madctl, &.{@bitCast(dri.madctl)}); + try dri.write_command(.colmod, &.{0x05}); + + // Expose the full configured panel area as the initial drawing window. + try dri.write_command(.caset, &range_to_bigendian_bytes(0, display_cfg.resolution.width - 1)); + try dri.write_command(.raset, &range_to_bigendian_bytes(0, display_cfg.resolution.height - 1)); + + // Some modules require display inversion for correct colours / contrast. + if (comptime display_cfg.display_inversion) { + try dri.write_command(.invon, &.{}); } - try dri.write_command(.madctl, &.{control_byte}); + // Positive and negative gamma correction curves. + try dri.write_command(.gmctrp1, &.{ + 0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, + 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10, + }); + + try dri.write_command(.gmctrn1, &.{ + 0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, + 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10, + }); + + // Switch to normal mode and enable display output. + try dri.write_command(.noron, &.{}); + delay_ms(10); + try dri.write_command(.dispon, &.{}); + delay_ms(100); } - pub fn enable_display(dri: Driver, enable: bool) !void { - try dri.write_command(if (enable) .dispon else .dispoff, &.{}); + fn init_sequence_st7789(dri: *Self, delay_ms: *const fn (ms: u32) void) !void { + // Reset the controller and exit sleep mode. + try dri.write_command(.swreset, &.{}); + delay_ms(150); + try dri.write_command(.slpout, &.{}); + delay_ms(10); + + // Select 16-bit pixel format and apply orientation / color order. + try dri.write_command(.colmod, &.{0x55}); + delay_ms(10); + try dri.write_command(.madctl, &.{@bitCast(dri.madctl)}); + + // Expose the full configured panel area as the initial drawing window. + try dri.write_command(.caset, &range_to_bigendian_bytes(0, display_cfg.resolution.width - 1)); + try dri.write_command(.raset, &range_to_bigendian_bytes(0, display_cfg.resolution.height - 1)); + + // Some modules require display inversion for correct colours / contrast. + if (comptime display_cfg.display_inversion) { + try dri.write_command(.invon, &.{}); + delay_ms(10); + } + + // Switch to normal mode and enable display output. + try dri.write_command(.noron, &.{}); + delay_ms(10); + try dri.write_command(.dispon, &.{}); + delay_ms(10); } - pub fn enable_tearing(dri: Driver, enable: bool) !void { - try dri.write_command(if (enable) .teon else .teoff, &.{}); + pub fn set_address_window(dri: *Self, x: u16, y: u16, w: u16, h: u16) !void { + const xstart = x + dri.x_offset; + const ystart = y + dri.y_offset; + const xend = xstart + (w - 1); + const yend = ystart + (h - 1); + + try dri.write_command(.caset, &range_to_bigendian_bytes(xstart, xend)); + try dri.write_command(.raset, &range_to_bigendian_bytes(ystart, yend)); + try dri.write_command(.ramwr, &.{}); } - pub fn enable_sleep(dri: Driver, enable: bool) !void { - try dri.write_command(if (enable) .slpin else .slpout, &.{}); + pub fn push_colors(dri: *Self, colors: []align(1) const Color) !void { + try dri.set_spi_mode(.data); + + try dri.dd.connect(); + defer dri.dd.disconnect(); + + try dri.dd.write(std.mem.sliceAsBytes(colors)); } - pub fn invert_display(dri: Driver, inverted: bool) !void { - try dri.write_command(if (inverted) .invon else .invoff, &.{}); + pub fn get_active_resolution(dri: *const Self) Resolution { + return dri.resolution; } - pub fn set_pixel(dri: Driver, x: u16, y: u16, color: Color) !void { - if (x >= dri.resolution.width or y >= dri.resolution.height) { - return; + pub fn set_rotation(dri: *Self, rotation: Rotation) !void { + switch (rotation) { + .deg0 => { + dri.madctl.addr = MemoryDataAccessControl.AddressOrder.from_rotation(rotation); + dri.resolution.width = display_cfg.resolution.width; + dri.resolution.height = display_cfg.resolution.height; + dri.x_offset = display_cfg.x_offset; + dri.y_offset = display_cfg.y_offset; + }, + .deg90 => { + dri.madctl.addr = MemoryDataAccessControl.AddressOrder.from_rotation(rotation); + dri.resolution.width = display_cfg.resolution.height; + dri.resolution.height = display_cfg.resolution.width; + dri.x_offset = display_cfg.y_offset; + dri.y_offset = display_cfg.x_offset2; + }, + .deg180 => { + dri.madctl.addr = MemoryDataAccessControl.AddressOrder.from_rotation(rotation); + dri.resolution.width = display_cfg.resolution.width; + dri.resolution.height = display_cfg.resolution.height; + dri.x_offset = display_cfg.x_offset2; + dri.y_offset = display_cfg.y_offset2; + }, + .deg270 => { + dri.madctl.addr = MemoryDataAccessControl.AddressOrder.from_rotation(rotation); + dri.resolution.width = display_cfg.resolution.height; + dri.resolution.height = display_cfg.resolution.width; + dri.x_offset = display_cfg.y_offset2; + dri.y_offset = display_cfg.x_offset; + }, } - try dri.set_address_window(x, y, 1, 1); - try dri.write_data(&.{color}); + + try dri.write_command(.madctl, &.{@bitCast(dri.madctl)}); } - fn write_command(dri: Driver, cmd: Command, params: []const u8) !void { + fn write_command(dri: *Self, cmd: Command, params: []const u8) !void { + try dri.set_spi_mode(.command); try dri.dd.connect(); defer dri.dd.disconnect(); - try dri.set_spi_mode(.command); try dri.dd.write(&[_]u8{@intFromEnum(cmd)}); + try dri.set_spi_mode(.data); try dri.dd.write(params); } - fn write_data(dri: Driver, data: []const Color) !void { - try dri.dd.connect(); - defer dri.dd.disconnect(); - - try dri.dd.write(std.mem.sliceAsBytes(data)); - } - - fn set_spi_mode(dri: Driver, mode: enum { data, command }) !void { - try dri.dev_datcmd.write(switch (mode) { - .command => .low, - .data => .high, - }); - } - - const cmd_delay = 0x80; // special signifier for command lists - const Command = enum(u8) { nop = 0x00, swreset = 0x01, @@ -207,288 +358,65 @@ pub fn ST77xx_Generic(comptime options: ST77xx_Options) type { madctl = 0x36, colmod = 0x3A, + frmctr1 = 0xB1, + frmctr2 = 0xB2, + frmctr3 = 0xB3, + invctr = 0xB4, + disset5 = 0xB6, + + pwmctr1 = 0xC0, + pwmctr2 = 0xC1, + pwmctr3 = 0xC2, + pwmctr4 = 0xC3, + pwmctr5 = 0xC4, + vmctr1 = 0xC5, + rdid1 = 0xDA, rdid2 = 0xDB, rdid3 = 0xDC, rdid4 = 0xDD, - }; - - const madctl_my = 0x80; - const madctl_mx = 0x40; - const madctl_mv = 0x20; - const madctl_ml = 0x10; - const madctl_rgb = 0x00; - - const ST7735_Device = struct { - // some flags for initR() :( - const INITR_GREENTAB = 0x00; - const INITR_REDTAB = 0x01; - const INITR_BLACKTAB = 0x02; - const INITR_18GREENTAB = INITR_GREENTAB; - const INITR_18REDTAB = INITR_REDTAB; - const INITR_18BLACKTAB = INITR_BLACKTAB; - const INITR_144GREENTAB = 0x01; - const INITR_MINI160x80 = 0x04; - const INITR_HALLOWING = 0x05; - const INITR_MINI160x80_PLUGIN = 0x06; - - // Some register settings - const ST7735_MADCTL_BGR = 0x08; - const ST7735_MADCTL_MH = 0x04; - - const ST7735_FRMCTR1 = 0xB1; - const ST7735_FRMCTR2 = 0xB2; - const ST7735_FRMCTR3 = 0xB3; - const ST7735_INVCTR = 0xB4; - const ST7735_DISSET5 = 0xB6; - - const ST7735_PWCTR1 = 0xC0; - const ST7735_PWCTR2 = 0xC1; - const ST7735_PWCTR3 = 0xC2; - const ST7735_PWCTR4 = 0xC3; - const ST7735_PWCTR5 = 0xC4; - const ST7735_VMCTR1 = 0xC5; - - const ST7735_PWCTR6 = 0xFC; - - const ST7735_GMCTRP1 = 0xE0; - const ST7735_GMCTRN1 = 0xE1; - - // static const uint8_t PROGMEM - // Bcmd[] = { // Init commands for 7735B screens - // 18, // 18 commands in list: - // ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, no args, w/delay - // 50, // 50 ms delay - // ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, no args, w/delay - // 255, // 255 = max (500 ms) delay - // ST77XX_COLMOD, 1+ST_CMD_DELAY, // 3: Set color mode, 1 arg + delay: - // 0x05, // 16-bit color - // 10, // 10 ms delay - // ST7735_FRMCTR1, 3+ST_CMD_DELAY, // 4: Frame rate control, 3 args + delay: - // 0x00, // fastest refresh - // 0x06, // 6 lines front porch - // 0x03, // 3 lines back porch - // 10, // 10 ms delay - // ST77XX_MADCTL, 1, // 5: Mem access ctl (directions), 1 arg: - // 0x08, // Row/col addr, bottom-top refresh - // ST7735_DISSET5, 2, // 6: Display settings #5, 2 args: - // 0x15, // 1 clk cycle nonoverlap, 2 cycle gate - // // rise, 3 cycle osc equalize - // 0x02, // Fix on VTL - // ST7735_INVCTR, 1, // 7: Display inversion control, 1 arg: - // 0x0, // Line inversion - // ST7735_PWCTR1, 2+ST_CMD_DELAY, // 8: Power control, 2 args + delay: - // 0x02, // GVDD = 4.7V - // 0x70, // 1.0uA - // 10, // 10 ms delay - // ST7735_PWCTR2, 1, // 9: Power control, 1 arg, no delay: - // 0x05, // VGH = 14.7V, VGL = -7.35V - // ST7735_PWCTR3, 2, // 10: Power control, 2 args, no delay: - // 0x01, // Opamp current small - // 0x02, // Boost frequency - // ST7735_VMCTR1, 2+ST_CMD_DELAY, // 11: Power control, 2 args + delay: - // 0x3C, // VCOMH = 4V - // 0x38, // VCOML = -1.1V - // 10, // 10 ms delay - // ST7735_PWCTR6, 2, // 12: Power control, 2 args, no delay: - // 0x11, 0x15, - // ST7735_GMCTRP1,16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay: - // 0x09, 0x16, 0x09, 0x20, // (Not entirely necessary, but provides - // 0x21, 0x1B, 0x13, 0x19, // accurate colors) - // 0x17, 0x15, 0x1E, 0x2B, - // 0x04, 0x05, 0x02, 0x0E, - // ST7735_GMCTRN1,16+ST_CMD_DELAY, // 14: Gamma Adjustments (neg. polarity), 16 args + delay: - // 0x0B, 0x14, 0x08, 0x1E, // (Not entirely necessary, but provides - // 0x22, 0x1D, 0x18, 0x1E, // accurate colors) - // 0x1B, 0x1A, 0x24, 0x2B, - // 0x06, 0x06, 0x02, 0x0F, - // 10, // 10 ms delay - // ST77XX_CASET, 4, // 15: Column addr set, 4 args, no delay: - // 0x00, 0x02, // XSTART = 2 - // 0x00, 0x81, // XEND = 129 - // ST77XX_RASET, 4, // 16: Row addr set, 4 args, no delay: - // 0x00, 0x02, // XSTART = 1 - // 0x00, 0x81, // XEND = 160 - // ST77XX_NORON, ST_CMD_DELAY, // 17: Normal display on, no args, w/delay - // 10, // 10 ms delay - // ST77XX_DISPON, ST_CMD_DELAY, // 18: Main screen turn on, no args, delay - // 255 }, // 255 = max (500 ms) delay - - // Rcmd1[] = { // 7735R init, part 1 (red or green tab) - // 15, // 15 commands in list: - // ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, 0 args, w/delay - // 150, // 150 ms delay - // ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, 0 args, w/delay - // 255, // 500 ms delay - // ST7735_FRMCTR1, 3, // 3: Framerate ctrl - normal mode, 3 arg: - // 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) - // ST7735_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args: - // 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) - // ST7735_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args: - // 0x01, 0x2C, 0x2D, // Dot inversion mode - // 0x01, 0x2C, 0x2D, // Line inversion mode - // ST7735_INVCTR, 1, // 6: Display inversion ctrl, 1 arg: - // 0x07, // No inversion - // ST7735_PWCTR1, 3, // 7: Power control, 3 args, no delay: - // 0xA2, - // 0x02, // -4.6V - // 0x84, // AUTO mode - // ST7735_PWCTR2, 1, // 8: Power control, 1 arg, no delay: - // 0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD - // ST7735_PWCTR3, 2, // 9: Power control, 2 args, no delay: - // 0x0A, // Opamp current small - // 0x00, // Boost frequency - // ST7735_PWCTR4, 2, // 10: Power control, 2 args, no delay: - // 0x8A, // BCLK/2, - // 0x2A, // opamp current small & medium low - // ST7735_PWCTR5, 2, // 11: Power control, 2 args, no delay: - // 0x8A, 0xEE, - // ST7735_VMCTR1, 1, // 12: Power control, 1 arg, no delay: - // 0x0E, - // ST77XX_INVOFF, 0, // 13: Don't invert display, no args - // ST77XX_MADCTL, 1, // 14: Mem access ctl (directions), 1 arg: - // 0xC8, // row/col addr, bottom-top refresh - // ST77XX_COLMOD, 1, // 15: set color mode, 1 arg, no delay: - // 0x05 }, // 16-bit color - - // Rcmd2green[] = { // 7735R init, part 2 (green tab only) - // 2, // 2 commands in list: - // ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: - // 0x00, 0x02, // XSTART = 0 - // 0x00, 0x7F+0x02, // XEND = 127 - // ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: - // 0x00, 0x01, // XSTART = 0 - // 0x00, 0x9F+0x01 }, // XEND = 159 - - // Rcmd2red[] = { // 7735R init, part 2 (red tab only) - // 2, // 2 commands in list: - // ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x7F, // XEND = 127 - // ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x9F }, // XEND = 159 - - // Rcmd2green144[] = { // 7735R init, part 2 (green 1.44 tab) - // 2, // 2 commands in list: - // ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x7F, // XEND = 127 - // ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x7F }, // XEND = 127 - - // Rcmd2green160x80[] = { // 7735R init, part 2 (mini 160x80) - // 2, // 2 commands in list: - // ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x4F, // XEND = 79 - // ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x9F }, // XEND = 159 - - // Rcmd2green160x80plugin[] = { // 7735R init, part 2 (mini 160x80 with plugin FPC) - // 3, // 3 commands in list: - // ST77XX_INVON, 0, // 1: Display is inverted - // ST77XX_CASET, 4, // 2: Column addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x4F, // XEND = 79 - // ST77XX_RASET, 4, // 3: Row addr set, 4 args, no delay: - // 0x00, 0x00, // XSTART = 0 - // 0x00, 0x9F }, // XEND = 159 - - // Rcmd3[] = { // 7735R init, part 3 (red or green tab) - // 4, // 4 commands in list: - // ST7735_GMCTRP1, 16 , // 1: Gamma Adjustments (pos. polarity), 16 args + delay: - // 0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides - // 0x37, 0x32, 0x29, 0x2d, // accurate colors) - // 0x29, 0x25, 0x2B, 0x39, - // 0x00, 0x01, 0x03, 0x10, - // ST7735_GMCTRN1, 16 , // 2: Gamma Adjustments (neg. polarity), 16 args + delay: - // 0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides - // 0x2E, 0x2C, 0x29, 0x2D, // accurate colors) - // 0x2E, 0x2E, 0x37, 0x3F, - // 0x00, 0x00, 0x02, 0x10, - // ST77XX_NORON, ST_CMD_DELAY, // 3: Normal display on, no args, w/delay - // 10, // 10 ms delay - // ST77XX_DISPON, ST_CMD_DELAY, // 4: Main screen turn on, no args w/delay - // 100 }; // 100 ms delay - }; - const ST7789_Device = struct { - // static const uint8_t PROGMEM - // generic_st7789[] = { // Init commands for 7789 screens - // 9, // 9 commands in list: - // ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, no args, w/delay - // 150, // ~150 ms delay - // ST77XX_SLPOUT , ST_CMD_DELAY, // 2: Out of sleep mode, no args, w/delay - // 10, // 10 ms delay - // ST77XX_COLMOD , 1+ST_CMD_DELAY, // 3: Set color mode, 1 arg + delay: - // 0x55, // 16-bit color - // 10, // 10 ms delay - // ST77XX_MADCTL , 1, // 4: Mem access ctrl (directions), 1 arg: - // 0x08, // Row/col addr, bottom-top refresh - // ST77XX_CASET , 4, // 5: Column addr set, 4 args, no delay: - // 0x00, - // 0, // XSTART = 0 - // 0, - // 240, // XEND = 240 - // ST77XX_RASET , 4, // 6: Row addr set, 4 args, no delay: - // 0x00, - // 0, // YSTART = 0 - // 320>>8, - // 320&0xFF, // YEND = 320 - // ST77XX_INVON , ST_CMD_DELAY, // 7: hack - // 10, - // ST77XX_NORON , ST_CMD_DELAY, // 8: Normal display on, no args, w/delay - // 10, // 10 ms delay - // ST77XX_DISPON , ST_CMD_DELAY, // 9: Main screen turn on, no args, delay - // 10 }; // 10 ms delay + gmctrp1 = 0xE0, + gmctrn1 = 0xE1, }; }; } -pub const Device = enum { - st7735, - st7789, -}; +const MemoryDataAccessControl = packed struct(u8) { + _reserved: u2 = 0, + mh: HorizontalRefreshOrder = .left_to_right, + rgb: ColorOrder = .rgb, + ml: VerticalRefreshOrder = .top_to_bottom, + addr: AddressOrder = .deg0, -pub const Resolution = struct { - width: u16, - height: u16, -}; + const HorizontalRefreshOrder = enum(u1) { + left_to_right = 0, + right_to_left = 1, + }; -pub const Rotation = enum(u2) { - normal, - left90, - right90, - upside_down, -}; + const VerticalRefreshOrder = enum(u1) { + top_to_bottom = 0, + bottom_to_top = 1, + }; -test { - _ = ST7735; - _ = ST7789; -} + const AddressOrder = packed struct(u3) { + const Self = @This(); + mv: u1, + mx: u1, + my: u1, + + pub fn from_rotation(rotation: Rotation) Self { + return switch (rotation) { + .deg0 => Self.deg0, + .deg90 => Self.deg90, + .deg180 => Self.deg180, + .deg270 => Self.deg270, + }; + } -test { - var channel = mdf.base.Datagram_Device.Test_Device.init_receiver_only(); - defer channel.deinit(); - - var rst_pin = mdf.base.Digital_IO.Test_Device.init(.output, .high); - var dat_pin = mdf.base.Digital_IO.Test_Device.init(.output, .high); - - var dri = try ST7735.init( - channel.datagram_device(), - rst_pin.digital_io(), - dat_pin.digital_io(), - .{ .width = 128, .height = 128 }, - ); - - try dri.set_address_window(16, 32, 48, 64); - try dri.set_rotation(.normal); - try dri.enable_display(true); - try dri.enable_tearing(false); - try dri.enable_sleep(true); - try dri.invert_display(false); - try dri.set_pixel(11, 15, Color.blue); -} + pub const deg0: Self = .{ .mv = 0, .mx = 0, .my = 0 }; + pub const deg90: Self = .{ .mv = 1, .mx = 1, .my = 0 }; + pub const deg180: Self = .{ .mv = 0, .mx = 1, .my = 1 }; + pub const deg270: Self = .{ .mv = 1, .mx = 0, .my = 1 }; + }; +}; diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 674b878b5..a99aec1ef 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -100,6 +100,7 @@ pub fn build(b: *std.Build) void { .{ .name = "ssd1306", .file = "src/ssd1306_oled.zig", .imports = &.{ .{ .name = "font8x8", .module = font8x8_dep.module("font8x8") }, } }, + .{ .name = "st7789", .file = "src/st7789_lcd.zig" }, .{ .name = "net-pong", .file = "src/net/pong.zig" }, .{ .name = "net-irq", .file = "src/net/irq.zig" }, .{ .name = "net-scan", .file = "src/net/scan.zig" }, diff --git a/examples/raspberrypi/rp2xxx/src/st7789_lcd.zig b/examples/raspberrypi/rp2xxx/src/st7789_lcd.zig new file mode 100644 index 000000000..82ad22711 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/st7789_lcd.zig @@ -0,0 +1,161 @@ +//! ST7789 Driver Demo — using WaveShare 1.3inch LCD Display Module +//! +//! WaveShare 1.3inch LCD Display Module is based on ST7789 driver and has a 240x240 pixel resolution. +//! This example demonstrates how to interface with the display using SPI and GPIO, +//! and includes basic drawing operations like clearing the screen and setting pixels. +//! +//! Pinout: +//! - CS: GPIO 9 +//! - SCK: GPIO 10 +//! - MOSI: GPIO 11 +//! - DC: GPIO 8 +//! - RST: GPIO 12 +//! - Backlight: GPIO 13 (PWM for brightness control) + +const microzig = @import("microzig"); + +const rp2xxx = microzig.hal; +const time = rp2xxx.time; +const gpio = rp2xxx.gpio; +const pwm = rp2xxx.pwm; +const drivers = rp2xxx.drivers; + +// Default pin mapping for SPI0 ST7735 bring-up on Pico style boards. +// Update these pins to match your display wiring. +const DISPLAY_CS_PIN = 9; +const DISPLAY_SCK_PIN = 10; +const DISPLAY_MOSI_PIN = 11; +const DISPLAY_DC_PIN = 8; +const DISPLAY_RST_PIN = 12; +const DISPLAY_BACKLIGHT_PIN = 13; + +const uart = rp2xxx.uart.instance.num(0); +const uart_tx_pin = gpio.num(0); +const uart_rx_pin = gpio.num(1); + +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, +}; + +const led = gpio.num(DISPLAY_BACKLIGHT_PIN); +const led_pwm = pwm.get_pwm(DISPLAY_BACKLIGHT_PIN); +const spi = rp2xxx.spi.instance.SPI1; + +// Use pre-compiled display setttings for ST7789 with a 240x240 RGB panel. +const DisplayDriver = microzig.drivers.display.st77xx.ST7789(.lcd240x240rgb); +// Framebuffer for off-screen drawing before pushing to the display. +var display_buffer: [240 * 240]DisplayDriver.Color = undefined; + +fn fill_display_buffer(buffer: []DisplayDriver.Color, color: DisplayDriver.Color) void { + @memset(buffer, color); +} + +fn fill_display_buffer_rect( + buffer: []DisplayDriver.Color, + x: usize, + y: usize, + width: usize, + height: usize, + color: DisplayDriver.Color, +) void { + const display_width: usize = 240; + const display_height: usize = 240; + + if (buffer.len < display_width * display_height) return; + if (x >= display_width or y >= display_height) return; + if (width == 0 or height == 0) return; + + const x_end = @min(x +| width, display_width); + const y_end = @min(y +| height, display_height); + + for (y..y_end) |row| { + const row_start = row * display_width + x; + @memset(buffer[row_start .. row_start + (x_end - x)], color); + } +} + +fn flush_display_buffer(display: *DisplayDriver, buffer: []DisplayDriver.Color) !void { + try display.set_address_window(0, 0, 240, 240); + try display.push_colors(buffer); +} + +pub fn main() !void { + inline for (&.{ uart_tx_pin, uart_rx_pin }) |pin| { + pin.set_function(.uart); + } + + uart.apply(.{ + .clock_config = rp2xxx.clock_config, + }); + + led.set_function(.pwm); + led.set_direction(.out); + led_pwm.slice().set_wrap(100); + led_pwm.slice().set_clk_div(50, 0); + led_pwm.set_level(1); + led_pwm.slice().enable(); + + const cs_pin = gpio.num(DISPLAY_CS_PIN); + const sck_pin = gpio.num(DISPLAY_SCK_PIN); + const mosi_pin = gpio.num(DISPLAY_MOSI_PIN); + const dc_pin = gpio.num(DISPLAY_DC_PIN); + const rst_pin = gpio.num(DISPLAY_RST_PIN); + + inline for (&.{ cs_pin, sck_pin, mosi_pin }) |pin| { + pin.set_function(.spi); + } + + try spi.apply(.{ + .clock_config = rp2xxx.clock_config, + .baud_rate = 10_000_000, // 10 MHz is a common max for ST7735, but check your display's datasheet. + }); + + var spi_dev = drivers.SPI_Device.init(spi, .{ + .chip_select = .{ .pin = cs_pin, .active_level = .low }, + }); + + dc_pin.set_function(.sio); + dc_pin.set_direction(.out); + rst_pin.set_function(.sio); + rst_pin.set_direction(.out); + + var dc_gpio = drivers.GPIO_Device.init(dc_pin); + var rst_gpio = drivers.GPIO_Device.init(rst_pin); + + var my_display = try DisplayDriver.init(spi_dev.datagram_device(), rst_gpio.digital_io(), dc_gpio.digital_io(), time.sleep_ms); + + while (true) { + time.sleep_ms(2000); + try my_display.set_rotation(.deg0); + fill_display_buffer(&display_buffer, .red); + try flush_display_buffer(&my_display, display_buffer[0..]); + + time.sleep_ms(2000); + fill_display_buffer(&display_buffer, .green); + try flush_display_buffer(&my_display, display_buffer[0..]); + + time.sleep_ms(2000); + fill_display_buffer(&display_buffer, .blue); + try flush_display_buffer(&my_display, display_buffer[0..]); + + time.sleep_ms(2000); + fill_display_buffer_rect(&display_buffer, 20, 20, 50, 50, .white); + fill_display_buffer_rect(&display_buffer, 70, 20, 50, 50, .black); + fill_display_buffer_rect(&display_buffer, 20, 70, 50, 50, .yellow); + fill_display_buffer_rect(&display_buffer, 70, 70, 50, 50, .cyan); + try flush_display_buffer(&my_display, display_buffer[0..]); + + time.sleep_ms(2000); + try my_display.set_rotation(.deg90); + try flush_display_buffer(&my_display, display_buffer[0..]); + + time.sleep_ms(2000); + try my_display.set_rotation(.deg180); + try flush_display_buffer(&my_display, display_buffer[0..]); + + time.sleep_ms(2000); + try my_display.set_rotation(.deg270); + try flush_display_buffer(&my_display, display_buffer[0..]); + } +}