From b7ec4e257f4e0e0e652470b7afb80e79fda0df31 Mon Sep 17 00:00:00 2001 From: Windwoes <26417650+Windwoes@users.noreply.github.com> Date: Sat, 19 Jul 2025 11:14:12 -0400 Subject: [PATCH 1/2] Improve I2C LCD example --- i2c/lcd_1602_i2c/CMakeLists.txt | 3 +- i2c/lcd_1602_i2c/i2c_lcd.c | 255 ++++++++++++++++++++++++++++++++ i2c/lcd_1602_i2c/i2c_lcd.h | 26 ++++ i2c/lcd_1602_i2c/lcd_1602_i2c.c | 167 --------------------- i2c/lcd_1602_i2c/main.c | 64 ++++++++ 5 files changed, 347 insertions(+), 168 deletions(-) create mode 100644 i2c/lcd_1602_i2c/i2c_lcd.c create mode 100644 i2c/lcd_1602_i2c/i2c_lcd.h delete mode 100644 i2c/lcd_1602_i2c/lcd_1602_i2c.c create mode 100644 i2c/lcd_1602_i2c/main.c diff --git a/i2c/lcd_1602_i2c/CMakeLists.txt b/i2c/lcd_1602_i2c/CMakeLists.txt index c6aad7ae1..86867d26f 100644 --- a/i2c/lcd_1602_i2c/CMakeLists.txt +++ b/i2c/lcd_1602_i2c/CMakeLists.txt @@ -1,5 +1,6 @@ add_executable(lcd_1602_i2c - lcd_1602_i2c.c + main.c + i2c_lcd.c ) # pull in common dependencies and additional i2c hardware support diff --git a/i2c/lcd_1602_i2c/i2c_lcd.c b/i2c/lcd_1602_i2c/i2c_lcd.c new file mode 100644 index 000000000..8eab12216 --- /dev/null +++ b/i2c/lcd_1602_i2c/i2c_lcd.c @@ -0,0 +1,255 @@ +/** +* Copyright (c) 2025 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "i2c_lcd.h" + +#include +#include +#include "hardware/i2c.h" +#include +#include +#include + +#define INSTRUCTION_FUNCTION_SET (1 << 5) +#define FUNCTION_SET_DATALENGTH_8BIT (1 << 4) // 4-bit if not set +#define FUNCTION_SET_USE_2_LINES (1 << 3) // 1 line if not set +#define FUNCTION_SET_FONT_5x10 (1 << 2) // 5x7 if not set + +#define INSTRUCTION_DISPLAY_CTRL (1 << 3) +#define DISPLAY_CTRL_DISPLAY_ON (1 << 2) +#define DISPLAY_CTRL_CURSOR_ON (1 << 1) +#define DISPLAY_CTRL_CURSOR_BLINK (1 << 0) + +#define INSTRUCTION_CLEAR_DISPLAY (1 << 0) + +#define INSTRUCTION_ENTRY_MODE_SET (1 << 2) +#define ENTRY_MODE_INCREMENT (1 << 1) // decrement if not set +#define ENTRY_MODE_SHIFT (1 << 0) // don't shift if not set + +#define INSTRUCTION_GO_HOME (1 << 1) + +#define INSTRUCTION_SET_RAM_ADDR (1 << 7) + +#define N_ROWS 2 +#define N_COLS 16 +#define LINEBUF_LEN (N_ROWS * N_COLS + 1) + +static const uint8_t ROW_OFFSETS[] = { 0x00, 0x40, 0x14, 0x54 }; // in display memory + +typedef union +{ + uint8_t dat; + struct + { + bool RS : 1; // bit 0 + bool RW : 1; // bit 1 + bool EN : 1; // bit 2 + bool BACKLIGHT : 1; // bit 3 + bool D4 : 1; // bit 4 + bool D5 : 1; // bit 5 + bool D6 : 1; // bit 6 + bool D7 : 1; // bit 7 + }; +} PortExpanderData_t; + +struct i2c_lcd +{ + PortExpanderData_t portExpanderDat; + bool cursor; + bool cursorBlink; + bool displayEnabled; + i2c_inst_t* i2cHandle; + uint8_t address; +}; + +static void writeExpanderData(i2c_lcd_handle inst, bool clockDisplayController) +{ + if (clockDisplayController) + { + // Assert enable signal + inst->portExpanderDat.EN = 1; + + // Send to hardware + i2c_write_blocking(inst->i2cHandle, inst->address, &inst->portExpanderDat.dat, 1, false); + sleep_us(1); + + // De-assert enable + inst->portExpanderDat.EN = 0; + + // Send to hardware + i2c_write_blocking(inst->i2cHandle, inst->address, &inst->portExpanderDat.dat, 1, false); + sleep_us(50); + } + else + { + i2c_write_blocking(inst->i2cHandle, inst->address, &inst->portExpanderDat.dat, 1, false); + } +} + +static void write4bits(i2c_lcd_handle inst, uint8_t bits) +{ + // Load in the bits + inst->portExpanderDat.D4 = (bits & (1 << 0)) != 0; + inst->portExpanderDat.D5 = (bits & (1 << 1)) != 0; + inst->portExpanderDat.D6 = (bits & (1 << 2)) != 0; + inst->portExpanderDat.D7 = (bits & (1 << 3)) != 0; + + writeExpanderData(inst, true); +} + +static void writeByte(i2c_lcd_handle inst, uint8_t data, bool dstControlReg) +{ + inst->portExpanderDat.RS = !dstControlReg; + + // Write more significant nibble first + write4bits(inst, data >> 4); + + // Then write less significant nibble + write4bits(inst, data); +} + +static void executeInstruction(i2c_lcd_handle inst, uint8_t instruction, uint8_t flags) +{ + writeByte(inst, instruction | flags, true); + + switch (instruction) + { + case INSTRUCTION_FUNCTION_SET: + case INSTRUCTION_DISPLAY_CTRL: + case INSTRUCTION_ENTRY_MODE_SET: + case INSTRUCTION_SET_RAM_ADDR: + sleep_us(53); + break; + + case INSTRUCTION_CLEAR_DISPLAY: + sleep_us(3000); + break; + + case INSTRUCTION_GO_HOME: + sleep_us(2000); + break; + } +} + +// https://web.alfredstate.edu/faculty/weimandn/lcd/lcd_initialization/lcd_initialization_index.html +i2c_lcd_handle i2c_lcd_init(i2c_inst_t* handle, uint8_t address) +{ + i2c_lcd_handle inst = calloc(sizeof(struct i2c_lcd), 1); + inst->i2cHandle = handle; + inst->address = address; + + // Special case of function set + write4bits(inst, 0b0011); + sleep_us(4100); + write4bits(inst, 0b0011); + sleep_us(100); + write4bits(inst, 0b0011); + sleep_us(100); + + // Function set interface to 4 bit mode + write4bits(inst, 0b0010); + sleep_us(100); + + executeInstruction(inst, INSTRUCTION_FUNCTION_SET, FUNCTION_SET_USE_2_LINES); + executeInstruction(inst, INSTRUCTION_ENTRY_MODE_SET, ENTRY_MODE_INCREMENT); + + i2c_lcd_clear(inst); + i2c_lcd_setDisplayVisible(inst, true); + i2c_lcd_setCursorLocation(inst, 0,0); + + return inst; +} + +void i2c_lcd_writeChar(i2c_lcd_handle inst, char c) +{ + writeByte(inst, c, false); +} + +void i2c_lcd_writeString(i2c_lcd_handle inst, char* string) +{ + for (int i = 0; i < strlen(string); i++) + { + i2c_lcd_writeChar(inst, string[i]); + } +} + +void i2c_lcd_writeStringf(i2c_lcd_handle inst, const char* __restrict format, ...) +{ + va_list args; + va_start(args, format); + + char linebuf[LINEBUF_LEN]; + vsnprintf(linebuf, LINEBUF_LEN, format, args); + i2c_lcd_writeString(inst, linebuf); + + va_end(args); +} + +void i2c_lcd_setCursorLocation(i2c_lcd_handle inst, uint8_t x, uint8_t y) +{ + // Bounds check + if (x < N_COLS && y <= N_ROWS) + { + executeInstruction(inst, INSTRUCTION_SET_RAM_ADDR, x + ROW_OFFSETS[y]); + } +} + +void i2c_lcd_setCursorLine(i2c_lcd_handle inst, uint8_t line) +{ + // Bounds check + if (line <= N_ROWS) + { + executeInstruction(inst, INSTRUCTION_SET_RAM_ADDR, ROW_OFFSETS[line]); + } +} + +void i2c_lcd_writeLines(i2c_lcd_handle inst, char* line1, char* line2) +{ + char linebuf1[LINEBUF_LEN]; + char linebuf2[LINEBUF_LEN]; + + snprintf(linebuf1, LINEBUF_LEN, "%-16s", line1); + snprintf(linebuf2, LINEBUF_LEN, "%-16s", line2); + + i2c_lcd_setCursorLocation(inst, 0,0); + i2c_lcd_writeString(inst, linebuf1); + i2c_lcd_setCursorLocation(inst, 0,1); + i2c_lcd_writeString(inst, linebuf2); +} + +void i2c_lcd_clear(i2c_lcd_handle inst) +{ + executeInstruction(inst, INSTRUCTION_CLEAR_DISPLAY, 0); +} + +void i2c_lcd_setBacklightEnabled(i2c_lcd_handle inst, bool en) +{ + inst->portExpanderDat.BACKLIGHT = en; + writeExpanderData(inst, false); +} + +static void updateDisplayConfiguration(i2c_lcd_handle inst) +{ + uint8_t flags = 0; + if (inst->cursor) flags |= DISPLAY_CTRL_CURSOR_ON; + if (inst->cursorBlink) flags |= DISPLAY_CTRL_CURSOR_BLINK; + if (inst->displayEnabled) flags |= DISPLAY_CTRL_DISPLAY_ON; + + executeInstruction(inst, INSTRUCTION_DISPLAY_CTRL, flags); +} + +void i2c_lcd_setDisplayVisible(i2c_lcd_handle inst, bool en) +{ + inst->displayEnabled = en; + updateDisplayConfiguration(inst); +} + +void i2c_lcd_setCursorEnabled(i2c_lcd_handle inst, bool cusror, bool blink) +{ + inst->cursor = cusror; + inst->cursorBlink = blink; + updateDisplayConfiguration(inst); +} diff --git a/i2c/lcd_1602_i2c/i2c_lcd.h b/i2c/lcd_1602_i2c/i2c_lcd.h new file mode 100644 index 000000000..643cd36ff --- /dev/null +++ b/i2c/lcd_1602_i2c/i2c_lcd.h @@ -0,0 +1,26 @@ +/** +* Copyright (c) 2025 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef I2C_LCD_H +#define I2C_LCD_H +#include +#include + +typedef struct i2c_lcd* i2c_lcd_handle; + +i2c_lcd_handle i2c_lcd_init(i2c_inst_t* handle, uint8_t address); +void i2c_lcd_setCursorLocation(i2c_lcd_handle inst, uint8_t x, uint8_t y); +void i2c_lcd_setCursorLine(i2c_lcd_handle inst, uint8_t line); +void i2c_lcd_writeString(i2c_lcd_handle inst, char* string); +void i2c_lcd_writeStringf(i2c_lcd_handle inst, const char* __restrict format, ...) _ATTRIBUTE ((__format__ (__printf__, 2, 3))); +void i2c_lcd_writeChar(i2c_lcd_handle inst, char c); +void i2c_lcd_writeLines(i2c_lcd_handle inst, char* line1, char* line2); +void i2c_lcd_clear(i2c_lcd_handle inst); +void i2c_lcd_setBacklightEnabled(i2c_lcd_handle inst, bool enabled); +void i2c_lcd_setDisplayVisible(i2c_lcd_handle inst, bool en); +void i2c_lcd_setCursorEnabled(i2c_lcd_handle inst, bool cusror, bool blink); + +#endif //I2C_LCD_H diff --git a/i2c/lcd_1602_i2c/lcd_1602_i2c.c b/i2c/lcd_1602_i2c/lcd_1602_i2c.c deleted file mode 100644 index 1a54c4a42..000000000 --- a/i2c/lcd_1602_i2c/lcd_1602_i2c.c +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include "pico/stdlib.h" -#include "hardware/i2c.h" -#include "pico/binary_info.h" - -/* Example code to drive a 16x2 LCD panel via a I2C bridge chip (e.g. PCF8574) - - NOTE: The panel must be capable of being driven at 3.3v NOT 5v. The Pico - GPIO (and therefore I2C) cannot be used at 5v. - - You will need to use a level shifter on the I2C lines if you want to run the - board at 5v. - - Connections on Raspberry Pi Pico board, other boards may vary. - - GPIO 4 (pin 6)-> SDA on LCD bridge board - GPIO 5 (pin 7)-> SCL on LCD bridge board - 3.3v (pin 36) -> VCC on LCD bridge board - GND (pin 38) -> GND on LCD bridge board -*/ -// commands -const int LCD_CLEARDISPLAY = 0x01; -const int LCD_RETURNHOME = 0x02; -const int LCD_ENTRYMODESET = 0x04; -const int LCD_DISPLAYCONTROL = 0x08; -const int LCD_CURSORSHIFT = 0x10; -const int LCD_FUNCTIONSET = 0x20; -const int LCD_SETCGRAMADDR = 0x40; -const int LCD_SETDDRAMADDR = 0x80; - -// flags for display entry mode -const int LCD_ENTRYSHIFTINCREMENT = 0x01; -const int LCD_ENTRYLEFT = 0x02; - -// flags for display and cursor control -const int LCD_BLINKON = 0x01; -const int LCD_CURSORON = 0x02; -const int LCD_DISPLAYON = 0x04; - -// flags for display and cursor shift -const int LCD_MOVERIGHT = 0x04; -const int LCD_DISPLAYMOVE = 0x08; - -// flags for function set -const int LCD_5x10DOTS = 0x04; -const int LCD_2LINE = 0x08; -const int LCD_8BITMODE = 0x10; - -// flag for backlight control -const int LCD_BACKLIGHT = 0x08; - -const int LCD_ENABLE_BIT = 0x04; - -// By default these LCD display drivers are on bus address 0x27 -static int addr = 0x27; - -// Modes for lcd_send_byte -#define LCD_CHARACTER 1 -#define LCD_COMMAND 0 - -#define MAX_LINES 2 -#define MAX_CHARS 16 - -/* Quick helper function for single byte transfers */ -void i2c_write_byte(uint8_t val) { -#ifdef i2c_default - i2c_write_blocking(i2c_default, addr, &val, 1, false); -#endif -} - -void lcd_toggle_enable(uint8_t val) { - // Toggle enable pin on LCD display - // We cannot do this too quickly or things don't work -#define DELAY_US 600 - sleep_us(DELAY_US); - i2c_write_byte(val | LCD_ENABLE_BIT); - sleep_us(DELAY_US); - i2c_write_byte(val & ~LCD_ENABLE_BIT); - sleep_us(DELAY_US); -} - -// The display is sent a byte as two separate nibble transfers -void lcd_send_byte(uint8_t val, int mode) { - uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT; - uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT; - - i2c_write_byte(high); - lcd_toggle_enable(high); - i2c_write_byte(low); - lcd_toggle_enable(low); -} - -void lcd_clear(void) { - lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND); -} - -// go to location on LCD -void lcd_set_cursor(int line, int position) { - int val = (line == 0) ? 0x80 + position : 0xC0 + position; - lcd_send_byte(val, LCD_COMMAND); -} - -static inline void lcd_char(char val) { - lcd_send_byte(val, LCD_CHARACTER); -} - -void lcd_string(const char *s) { - while (*s) { - lcd_char(*s++); - } -} - -void lcd_init() { - lcd_send_byte(0x03, LCD_COMMAND); - lcd_send_byte(0x03, LCD_COMMAND); - lcd_send_byte(0x03, LCD_COMMAND); - lcd_send_byte(0x02, LCD_COMMAND); - - lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND); - lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND); - lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND); - lcd_clear(); -} - -int main() { -#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) - #warning i2c/lcd_1602_i2c example requires a board with I2C pins -#else - // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico) - i2c_init(i2c_default, 100 * 1000); - gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); - gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); - gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); - gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); - // Make the I2C pins available to picotool - bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); - - lcd_init(); - - static char *message[] = - { - "RP2040 by", "Raspberry Pi", - "A brand new", "microcontroller", - "Twin core M0", "Full C SDK", - "More power in", "your product", - "More beans", "than Heinz!" - }; - - while (1) { - for (uint m = 0; m < sizeof(message) / sizeof(message[0]); m += MAX_LINES) { - for (int line = 0; line < MAX_LINES; line++) { - lcd_set_cursor(line, (MAX_CHARS / 2) - strlen(message[m + line]) / 2); - lcd_string(message[m + line]); - } - sleep_ms(2000); - lcd_clear(); - } - } -#endif -} diff --git a/i2c/lcd_1602_i2c/main.c b/i2c/lcd_1602_i2c/main.c new file mode 100644 index 000000000..69e2d710b --- /dev/null +++ b/i2c/lcd_1602_i2c/main.c @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2025 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "i2c_lcd.h" +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/i2c.h" + +/* + * Example code to drive a 16x2 LCD panel via a I2C bridge chip (e.g. PCF8574) + * NOTE: The panel must be capable of being driven at 3.3v NOT 5v. The RP2040 + * is not 5V tolerant. + * + * You will need to use a level shifter on the I2C lines if you want to run the + * board at 5V. Using it without a level shifter will work but may damage the I/O + * cells on the RP2040 over time since the I2C pullups on the display will pull + * the data lines up to 5V. + * + * Connections on Raspberry Pi Pico board, other boards may vary. + * GPIO 0 -> SDA on LCD bridge board + * GPIO 1 -> SCL on LCD bridge board + */ + +// You may need to change this for your LCD module +#define I2_LCD_ADDR 0x27 + +int main() +{ + // Set up USB virtual COM port + stdio_init_all(); + + // Set up the I2C peripheral at 100k + i2c_init(i2c0, 100 * 1000); + + // Bind pins to I2C peripheral + gpio_set_function(0, GPIO_FUNC_I2C); + gpio_set_function(1, GPIO_FUNC_I2C); + + // Wait long enough for power rails to rise on the LCD module + sleep_ms(100); + + // Create an instance of the LCD driver (you can create multiple instances + // either with the same address on different i2c ports or with different + // addresses on the same i2c port) + i2c_lcd_handle lcd = i2c_lcd_init(i2c0, I2_LCD_ADDR); + + // Fairly self-explanatory + i2c_lcd_setBacklightEnabled(lcd, true); + + // Demonstrate writing to the different lines on the display + int counter = 0; + while (1) + { + i2c_lcd_setCursorLine(lcd, 0); + i2c_lcd_writeStringf(lcd, "Hello World!"); + i2c_lcd_setCursorLine(lcd, 1); + i2c_lcd_writeStringf(lcd, "Counter = %d", counter); + counter++; + } +} From c383fbf4dd53c3eb2a1a3de41e3bfd173f956fcb Mon Sep 17 00:00:00 2001 From: Windwoes <26417650+Windwoes@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:10:26 -0400 Subject: [PATCH 2/2] Address code review comments --- i2c/lcd_1602_i2c/i2c_lcd.c | 84 ++++++++++++++--------------- i2c/lcd_1602_i2c/i2c_lcd.h | 108 +++++++++++++++++++++++++++++++++---- i2c/lcd_1602_i2c/main.c | 22 ++++---- 3 files changed, 151 insertions(+), 63 deletions(-) diff --git a/i2c/lcd_1602_i2c/i2c_lcd.c b/i2c/lcd_1602_i2c/i2c_lcd.c index 8eab12216..15a233438 100644 --- a/i2c/lcd_1602_i2c/i2c_lcd.c +++ b/i2c/lcd_1602_i2c/i2c_lcd.c @@ -65,7 +65,7 @@ struct i2c_lcd uint8_t address; }; -static void writeExpanderData(i2c_lcd_handle inst, bool clockDisplayController) +static void write_expander_data(i2c_lcd_handle inst, bool clockDisplayController) { if (clockDisplayController) { @@ -89,7 +89,7 @@ static void writeExpanderData(i2c_lcd_handle inst, bool clockDisplayController) } } -static void write4bits(i2c_lcd_handle inst, uint8_t bits) +static void write_4_bits(i2c_lcd_handle inst, uint8_t bits) { // Load in the bits inst->portExpanderDat.D4 = (bits & (1 << 0)) != 0; @@ -97,23 +97,23 @@ static void write4bits(i2c_lcd_handle inst, uint8_t bits) inst->portExpanderDat.D6 = (bits & (1 << 2)) != 0; inst->portExpanderDat.D7 = (bits & (1 << 3)) != 0; - writeExpanderData(inst, true); + write_expander_data(inst, true); } -static void writeByte(i2c_lcd_handle inst, uint8_t data, bool dstControlReg) +static void write_byte(i2c_lcd_handle inst, uint8_t data, bool dstControlReg) { inst->portExpanderDat.RS = !dstControlReg; // Write more significant nibble first - write4bits(inst, data >> 4); + write_4_bits(inst, data >> 4); // Then write less significant nibble - write4bits(inst, data); + write_4_bits(inst, data); } -static void executeInstruction(i2c_lcd_handle inst, uint8_t instruction, uint8_t flags) +static void send_instruction(i2c_lcd_handle inst, uint8_t instruction, uint8_t flags) { - writeByte(inst, instruction | flags, true); + write_byte(inst, instruction | flags, true); switch (instruction) { @@ -135,78 +135,78 @@ static void executeInstruction(i2c_lcd_handle inst, uint8_t instruction, uint8_t } // https://web.alfredstate.edu/faculty/weimandn/lcd/lcd_initialization/lcd_initialization_index.html -i2c_lcd_handle i2c_lcd_init(i2c_inst_t* handle, uint8_t address) +i2c_lcd_handle i2c_lcd_init(i2c_inst_t* i2c_peripheral, uint8_t address) { i2c_lcd_handle inst = calloc(sizeof(struct i2c_lcd), 1); - inst->i2cHandle = handle; + inst->i2cHandle = i2c_peripheral; inst->address = address; // Special case of function set - write4bits(inst, 0b0011); + write_4_bits(inst, 0b0011); sleep_us(4100); - write4bits(inst, 0b0011); + write_4_bits(inst, 0b0011); sleep_us(100); - write4bits(inst, 0b0011); + write_4_bits(inst, 0b0011); sleep_us(100); // Function set interface to 4 bit mode - write4bits(inst, 0b0010); + write_4_bits(inst, 0b0010); sleep_us(100); - executeInstruction(inst, INSTRUCTION_FUNCTION_SET, FUNCTION_SET_USE_2_LINES); - executeInstruction(inst, INSTRUCTION_ENTRY_MODE_SET, ENTRY_MODE_INCREMENT); + send_instruction(inst, INSTRUCTION_FUNCTION_SET, FUNCTION_SET_USE_2_LINES); + send_instruction(inst, INSTRUCTION_ENTRY_MODE_SET, ENTRY_MODE_INCREMENT); i2c_lcd_clear(inst); - i2c_lcd_setDisplayVisible(inst, true); - i2c_lcd_setCursorLocation(inst, 0,0); + i2c_lcd_set_display_visible(inst, true); + i2c_lcd_set_cursor_location(inst, 0,0); return inst; } -void i2c_lcd_writeChar(i2c_lcd_handle inst, char c) +void i2c_lcd_write_char(i2c_lcd_handle inst, char c) { - writeByte(inst, c, false); + write_byte(inst, c, false); } -void i2c_lcd_writeString(i2c_lcd_handle inst, char* string) +void i2c_lcd_write_string(i2c_lcd_handle inst, char* string) { for (int i = 0; i < strlen(string); i++) { - i2c_lcd_writeChar(inst, string[i]); + i2c_lcd_write_char(inst, string[i]); } } -void i2c_lcd_writeStringf(i2c_lcd_handle inst, const char* __restrict format, ...) +void i2c_lcd_write_stringf(i2c_lcd_handle inst, const char* __restrict format, ...) { va_list args; va_start(args, format); char linebuf[LINEBUF_LEN]; vsnprintf(linebuf, LINEBUF_LEN, format, args); - i2c_lcd_writeString(inst, linebuf); + i2c_lcd_write_string(inst, linebuf); va_end(args); } -void i2c_lcd_setCursorLocation(i2c_lcd_handle inst, uint8_t x, uint8_t y) +void i2c_lcd_set_cursor_location(i2c_lcd_handle inst, uint8_t x, uint8_t y) { // Bounds check if (x < N_COLS && y <= N_ROWS) { - executeInstruction(inst, INSTRUCTION_SET_RAM_ADDR, x + ROW_OFFSETS[y]); + send_instruction(inst, INSTRUCTION_SET_RAM_ADDR, x + ROW_OFFSETS[y]); } } -void i2c_lcd_setCursorLine(i2c_lcd_handle inst, uint8_t line) +void i2c_lcd_set_cursor_line(i2c_lcd_handle inst, uint8_t line) { // Bounds check if (line <= N_ROWS) { - executeInstruction(inst, INSTRUCTION_SET_RAM_ADDR, ROW_OFFSETS[line]); + send_instruction(inst, INSTRUCTION_SET_RAM_ADDR, ROW_OFFSETS[line]); } } -void i2c_lcd_writeLines(i2c_lcd_handle inst, char* line1, char* line2) +void i2c_lcd_write_lines(i2c_lcd_handle inst, char* line1, char* line2) { char linebuf1[LINEBUF_LEN]; char linebuf2[LINEBUF_LEN]; @@ -214,42 +214,42 @@ void i2c_lcd_writeLines(i2c_lcd_handle inst, char* line1, char* line2) snprintf(linebuf1, LINEBUF_LEN, "%-16s", line1); snprintf(linebuf2, LINEBUF_LEN, "%-16s", line2); - i2c_lcd_setCursorLocation(inst, 0,0); - i2c_lcd_writeString(inst, linebuf1); - i2c_lcd_setCursorLocation(inst, 0,1); - i2c_lcd_writeString(inst, linebuf2); + i2c_lcd_set_cursor_location(inst, 0,0); + i2c_lcd_write_string(inst, linebuf1); + i2c_lcd_set_cursor_location(inst, 0,1); + i2c_lcd_write_string(inst, linebuf2); } void i2c_lcd_clear(i2c_lcd_handle inst) { - executeInstruction(inst, INSTRUCTION_CLEAR_DISPLAY, 0); + send_instruction(inst, INSTRUCTION_CLEAR_DISPLAY, 0); } -void i2c_lcd_setBacklightEnabled(i2c_lcd_handle inst, bool en) +void i2c_lcd_set_backlight_enabled(i2c_lcd_handle inst, bool en) { inst->portExpanderDat.BACKLIGHT = en; - writeExpanderData(inst, false); + write_expander_data(inst, false); } -static void updateDisplayConfiguration(i2c_lcd_handle inst) +static void update_display_configuration(i2c_lcd_handle inst) { uint8_t flags = 0; if (inst->cursor) flags |= DISPLAY_CTRL_CURSOR_ON; if (inst->cursorBlink) flags |= DISPLAY_CTRL_CURSOR_BLINK; if (inst->displayEnabled) flags |= DISPLAY_CTRL_DISPLAY_ON; - executeInstruction(inst, INSTRUCTION_DISPLAY_CTRL, flags); + send_instruction(inst, INSTRUCTION_DISPLAY_CTRL, flags); } -void i2c_lcd_setDisplayVisible(i2c_lcd_handle inst, bool en) +void i2c_lcd_set_display_visible(i2c_lcd_handle inst, bool en) { inst->displayEnabled = en; - updateDisplayConfiguration(inst); + update_display_configuration(inst); } -void i2c_lcd_setCursorEnabled(i2c_lcd_handle inst, bool cusror, bool blink) +void i2c_lcd_set_cursor_enabled(i2c_lcd_handle inst, bool cusror, bool blink) { inst->cursor = cusror; inst->cursorBlink = blink; - updateDisplayConfiguration(inst); + update_display_configuration(inst); } diff --git a/i2c/lcd_1602_i2c/i2c_lcd.h b/i2c/lcd_1602_i2c/i2c_lcd.h index 643cd36ff..49a2fb2e1 100644 --- a/i2c/lcd_1602_i2c/i2c_lcd.h +++ b/i2c/lcd_1602_i2c/i2c_lcd.h @@ -9,18 +9,106 @@ #include #include +/* + * This is a library-like example - it demonstrates creating a driver that can be + * instantiated multiple times to control more than one device, as well as hiding + * internal driver state from user code, and showing how to document function + * arguments and return values. + */ + +// "Opaque" type; implementation defined "privately" inside the .c file +// so that user code cannot mess with the internal state of the driver typedef struct i2c_lcd* i2c_lcd_handle; -i2c_lcd_handle i2c_lcd_init(i2c_inst_t* handle, uint8_t address); -void i2c_lcd_setCursorLocation(i2c_lcd_handle inst, uint8_t x, uint8_t y); -void i2c_lcd_setCursorLine(i2c_lcd_handle inst, uint8_t line); -void i2c_lcd_writeString(i2c_lcd_handle inst, char* string); -void i2c_lcd_writeStringf(i2c_lcd_handle inst, const char* __restrict format, ...) _ATTRIBUTE ((__format__ (__printf__, 2, 3))); -void i2c_lcd_writeChar(i2c_lcd_handle inst, char c); -void i2c_lcd_writeLines(i2c_lcd_handle inst, char* line1, char* line2); +/*! \brief Create an instance of the LCD driver + * + * Initializes the LCD driver and performs initial configuration of the display + * + * \param i2c_peripheral a reference to the I2C hardware bus that the LCD is connected to + * \param address the I2C address of the LCD module + * \return Pointer to newly created driver instance + */ +i2c_lcd_handle i2c_lcd_init(i2c_inst_t* i2c_peripheral, uint8_t address); + +/*! \brief Move the cursor to a certain location on the LCD + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param x the column (0-indexed) to move the cursor to + * \param y the row (0-indexed) to move the cursor to + */ +void i2c_lcd_set_cursor_location(i2c_lcd_handle inst, uint8_t x, uint8_t y); + +/*! \brief Move the cursor to the beginning of a certain line on the LCD + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param line the line number (0-indexed) to the move the cursor to the beginning of + */ +void i2c_lcd_set_cursor_line(i2c_lcd_handle inst, uint8_t line); + +/*! \brief Write a string into the display memory starting from the current cursor location + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param string the string to send to the display + */ +void i2c_lcd_write_string(i2c_lcd_handle inst, char* string); + +/*! \brief Write a formatted string into the display memory starting from the current cursor location + * + * This is a convenience function to allow "printf" functionality to the display without + * having to do snprintf on a buffer yourself + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param format the format string used to generate the final string send to the display + * \param ... the arguments for string formatting + */ +void i2c_lcd_write_stringf(i2c_lcd_handle inst, const char* __restrict format, ...) _ATTRIBUTE ((__format__ (__printf__, 2, 3))); + +/*! \brief Write a single character into the display memory starting from the current cursor location + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param c the character to write into display memory + */ +void i2c_lcd_write_char(i2c_lcd_handle inst, char c); + +/*! \brief Write a string to each line of the display + * + * Convenience function to avoid having to move the cursor around when writing to + * both lines of the display. + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param line1 the string to be shown on the first line of the display + * \param line2 the string to be shown on the second line of the display + */ +void i2c_lcd_write_lines(i2c_lcd_handle inst, char* line1, char* line2); + +/*! \brief Clear the contents of the display memory + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + */ void i2c_lcd_clear(i2c_lcd_handle inst); -void i2c_lcd_setBacklightEnabled(i2c_lcd_handle inst, bool enabled); -void i2c_lcd_setDisplayVisible(i2c_lcd_handle inst, bool en); -void i2c_lcd_setCursorEnabled(i2c_lcd_handle inst, bool cusror, bool blink); + +/*! \brief Enable or disable the LCD backlight + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param enabled whether to enable the backlight + */ +void i2c_lcd_set_backlight_enabled(i2c_lcd_handle inst, bool enabled); + +/*! \brief Control display visibility + * + * This allows "blanking" the display without actually clearing the memory contents + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param enabled whether the display should be visible + */ +void i2c_lcd_set_display_visible(i2c_lcd_handle inst, bool enabled); + +/*! \brief Control display cursor visibility + * + * \param inst pointer to an instance of this driver, obtained from \link i2c_lcd_init + * \param cursorVisible whether the cursor should be visible + * \param blink whether the cursor should also blink + */ +void i2c_lcd_set_cursor_enabled(i2c_lcd_handle inst, bool cursorVisible, bool blink); #endif //I2C_LCD_H diff --git a/i2c/lcd_1602_i2c/main.c b/i2c/lcd_1602_i2c/main.c index 69e2d710b..888184688 100644 --- a/i2c/lcd_1602_i2c/main.c +++ b/i2c/lcd_1602_i2c/main.c @@ -21,8 +21,8 @@ * the data lines up to 5V. * * Connections on Raspberry Pi Pico board, other boards may vary. - * GPIO 0 -> SDA on LCD bridge board - * GPIO 1 -> SCL on LCD bridge board + * GPIO 4 -> SDA on LCD bridge board + * GPIO 5 -> SCL on LCD bridge board */ // You may need to change this for your LCD module @@ -34,11 +34,11 @@ int main() stdio_init_all(); // Set up the I2C peripheral at 100k - i2c_init(i2c0, 100 * 1000); + i2c_init(i2c_default, 100 * 1000); // Bind pins to I2C peripheral - gpio_set_function(0, GPIO_FUNC_I2C); - gpio_set_function(1, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); // Wait long enough for power rails to rise on the LCD module sleep_ms(100); @@ -46,19 +46,19 @@ int main() // Create an instance of the LCD driver (you can create multiple instances // either with the same address on different i2c ports or with different // addresses on the same i2c port) - i2c_lcd_handle lcd = i2c_lcd_init(i2c0, I2_LCD_ADDR); + i2c_lcd_handle lcd = i2c_lcd_init(i2c_default, I2_LCD_ADDR); // Fairly self-explanatory - i2c_lcd_setBacklightEnabled(lcd, true); + i2c_lcd_set_backlight_enabled(lcd, true); // Demonstrate writing to the different lines on the display int counter = 0; while (1) { - i2c_lcd_setCursorLine(lcd, 0); - i2c_lcd_writeStringf(lcd, "Hello World!"); - i2c_lcd_setCursorLine(lcd, 1); - i2c_lcd_writeStringf(lcd, "Counter = %d", counter); + i2c_lcd_set_cursor_line(lcd, 0); + i2c_lcd_write_stringf(lcd, "Hello World!"); + i2c_lcd_set_cursor_line(lcd, 1); + i2c_lcd_write_stringf(lcd, "Counter = %d", counter); counter++; } }