From 8b5ad2c038aa8f3fe55354a4a8c20317aadd0745 Mon Sep 17 00:00:00 2001 From: Didas72 Date: Thu, 26 Jun 2025 22:51:55 +0100 Subject: [PATCH] Random --- a | 6 + doc/features.md | 25 +++ include/drivers/display.h | 56 ++++++ include/systems/lcd_writer.h | 21 +++ include/systems/pager.h | 7 + src/pages/display.c | 329 +++++++++++++++++++++++++++++++++++ src/pages/lcd_writer.c | 271 +++++++++++++++++++++++++++++ src/pages/pager.c | 1 + 8 files changed, 716 insertions(+) create mode 100644 a create mode 100644 doc/features.md create mode 100644 include/drivers/display.h create mode 100644 include/systems/lcd_writer.h create mode 100644 include/systems/pager.h create mode 100644 src/pages/display.c create mode 100644 src/pages/lcd_writer.c create mode 100644 src/pages/pager.c diff --git a/a b/a new file mode 100644 index 0000000..84c3e7e --- /dev/null +++ b/a @@ -0,0 +1,6 @@ +pactl set-card-profile bluez_card.7C_89_56_4C_BD_1B a2dp_source +pactl list short sinks +pactl list short sources +pactl load-module module-loopback source=bluez_source.7C_89_56_4C_BD_1B.a2dp_source sink=alsa_output.platform-bcm2835_audio.analog-stereo +pactl list short sinks +pactl set-sink-volume 0 50% diff --git a/doc/features.md b/doc/features.md new file mode 100644 index 0000000..f8871bf --- /dev/null +++ b/doc/features.md @@ -0,0 +1,25 @@ +# Planned features + +## Pages + +- [ ] Page description format +- [ ] Pages auto generated for LCD GLCD +- [ ] Self organizing page list + +## Music + +- [ ] Bluetooth audio + +## Utility + +- [ ] Shutdown/restart program ability +- [ ] Shutdown/reboot system ability +- [ ] View local IP +- [ ] Manage Wi-Fi +- [ ] Manage Bluetooth + +## OBD-II + +- [ ] Connect to OBD-II via bluetooth +- [ ] Display measured values +- [ ] Display DTCs diff --git a/include/drivers/display.h b/include/drivers/display.h new file mode 100644 index 0000000..9ddac0e --- /dev/null +++ b/include/drivers/display.h @@ -0,0 +1,56 @@ +//display.h - Raw control over I2C display hardware + +#ifndef DISPLAY_H_ +#define DISPLAY_H_ + +#include +#include + + + +#define DISP_LINES 4 +#define DISP_COLS 20 +#define DISP_SIZE (DISP_COLS * DISP_LINES) + +#define DISP_I2C0 0 +#define DISP_I2C1 1 + +#define DISP_CHAR_ARROWR '\x7e' +#define DISP_CHAR_ARROWL '\x7f' +#define DISP_CHAR_DOT_HIGH '\xa5' +#define DISP_CHAR_BOX '\xdb' +#define DISP_CHAR_DEGREE '\xdf' +#define DISP_CHAR_ALPHA '\xe0' +#define DISP_CHAR_BETA '\xe2' +#define DISP_CHAR_EPSILON '\xe3' +#define DISP_CHAR_MU '\xe4' +#define DISP_CHAR_SQRT '\xe9' +#define DISP_CHAR_OMEGA '\xf4' +#define DISP_CHAR_SUM '\xf7' +#define DISP_CHAR_PI '\xf8' +#define DISP_CHAR_DOT_DIV '\xfd' +#define DISP_CHAR_BLACK '\xff' + +//TODO: Set custom chars +#define DISP_CHAR_NOTE '\x01' +#define DISP_CHAR_LIST '\x02' +#define DISP_CHAR_SEARCH '\x03' + +typedef struct display_t display_t; + +display_t *display_init(int bus, uint8_t addr); +void display_destroy(display_t *display); + +void display_set_backlight(display_t *display, bool backlight); +void display_set_cursor(display_t *display, int lin, int col); +void display_clear(display_t *display); +void display_write_ch(display_t *display, char ch); +void display_write_str(display_t *display, char *str); +void display_write_str_at(display_t *display, char *str, int lin, int col); + +void display_set_display_on(display_t *display, bool on); +void display_set_cursor_show(display_t *display, bool show); +void display_set_cursor_blink(display_t *display, bool blink); +void display_set_custom_char(display_t *display, uint8_t char_idx, uint64_t data); + +#endif diff --git a/include/systems/lcd_writer.h b/include/systems/lcd_writer.h new file mode 100644 index 0000000..049f2e2 --- /dev/null +++ b/include/systems/lcd_writer.h @@ -0,0 +1,21 @@ +//lcd_writer.h - Defines interface for buffered, async LCD display writting + +#ifndef DISPLAY_WRITER_H_ +#define DISPLAY_WRITER_H_ + +#include + +#include + +typedef struct lcd_writer_t lcd_writer_t; + +lcd_writer_t *lcd_writer_init(display_t *display, bool bypass); +void lcd_writer_destroy(lcd_writer_t *lcdw); + +void lcd_writer_clr(lcd_writer_t *lcdw); +void lcd_writer_clr_line(lcd_writer_t *lcdw, int lin); +void lcd_writer_chr(lcd_writer_t *lcdw, char ch, int lin, int col); +void lcd_writer_str(lcd_writer_t *lcdw, char *str, int lin, int col); +void lcd_writer_flush(lcd_writer_t *lcdw); + +#endif diff --git a/include/systems/pager.h b/include/systems/pager.h new file mode 100644 index 0000000..a44ea08 --- /dev/null +++ b/include/systems/pager.h @@ -0,0 +1,7 @@ +#pragma once + +void pager_init(); +void pager_destroy(); + +//void pager_add_page(page_t *page); +//void pager_draw(display_t *disp); diff --git a/src/pages/display.c b/src/pages/display.c new file mode 100644 index 0000000..3306cef --- /dev/null +++ b/src/pages/display.c @@ -0,0 +1,329 @@ +#include "drivers/display.h" + +#include +#include +#include +#include + +#include + + + +struct display_t +{ + int handle; + bool backlight, display, cursor, blink; +}; + + + +//Bit mask for display pins +#define PIN_E 0b00000100 +#define PIN_RW 0b00000010 +#define PIN_RS 0b00000001 +#define PIN_BL 0b00001000 +#define PIN_D4 0b00010000 +#define PIN_D5 0b00100000 +#define PIN_D6 0b01000000 +#define PIN_D7 0b10000000 + +//Periods for signal hold +#define PERIOD_PULSE 500 +#define PERIOD_CMD 4100 + +//Display commands +#define CMD_CLEAR 0x01 +#define CMD_HOME 0x02 +#define CMD_ENTRY 0x04 +#define CMD_DISP 0x08 +#define CMD_CODS 0x10 +#define CMD_FUNC 0x20 +#define CMD_CGADR 0x40 +#define CMD_DRADR 0x80 + +//Flags for entry mode +#define ENTRY_NORM 0x02 +#define ENTRY_SHIFT 0x01 + +//Flags for display +#define DISP_PWR 0x04 +#define DISP_OFF 0x00 +#define DISP_CURS 0x02 +#define DISP_BLNK 0x01 + +//Flags for CODS +#define CODS_CURL 0x00 +#define CODS_CURR 0x04 +#define CODS_SCRL 0x08 +#define CODS_SCRR 0x0C + +//Flags for function +#define FUNC_4BIT 0x00 +#define FUNC_8BIT 0x10 +#define FUNC_1LIN 0x00 +#define FUNC_2LIN 0x08 +#define FUNC_5x8D 0x00 +#define FUNC_5x11D 0x04 + + + +static void write_cmd(int handle, unsigned int cmd, bool backlight) +{ + unsigned int byte; + + byte = (cmd & 0xF0) | (backlight ? PIN_BL : 0); + i2cWriteByte(handle, byte & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | PIN_E) & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | ~PIN_E) & 0xFF); + gpioDelay(PERIOD_CMD); + + byte = ((cmd << 4) & 0xF0) | (backlight ? PIN_BL : 0); + i2cWriteByte(handle, byte & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | PIN_E) & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | ~PIN_E) & 0xFF); + gpioDelay(PERIOD_CMD); +} + +static void write_char(int handle, char ch, bool backlight) +{ + unsigned int byte; + + byte = (ch & 0xF0) | PIN_RS | (backlight ? PIN_BL : 0); + i2cWriteByte(handle, byte & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | PIN_E) & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | ~PIN_E) & 0xFF); + gpioDelay(PERIOD_CMD); + + byte = ((ch << 4) & 0xF0) | PIN_RS | (backlight ? PIN_BL : 0); + i2cWriteByte(handle, byte & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | PIN_E) & 0xFF); + gpioDelay(PERIOD_PULSE); + i2cWriteByte(handle, (byte | ~PIN_E) & 0xFF); + gpioDelay(PERIOD_CMD); +} + + + +display_t *display_init(int bus, uint8_t addr) +{ + if (bus != DISP_I2C0 && bus != DISP_I2C1) + { + fprintf(stderr, "WARN: display_init received an invalid bus: %d.\n", bus); + return NULL; + } + if (addr >= 0x80) + { + fprintf(stderr, "WARN: display_init received an invalid address: %d.\n", addr); + return NULL; + } + + display_t * ret = malloc(sizeof(display_t)); + if (!ret) + { + fprintf(stderr, "WARN: display_init failed to allocate memory.\n"); + return NULL; + } + + ret->backlight = true; + ret->handle = i2cOpen(bus, addr, 0); + if (ret->handle < 0) + { + fprintf(stderr, "WARN: display_init could not open the required I2C%d bus for address %d: %d\n", bus, addr, ret->handle); + free(ret); + return NULL; + } + + //Literal magic, idk why it's needed, idk what the values are + //All I know is it no workey without this + //Average programming moment + write_cmd(ret->handle, 0x03, false); + write_cmd(ret->handle, 0x03, false); + write_cmd(ret->handle, 0x03, false); + write_cmd(ret->handle, 0x02, false); + + write_cmd(ret->handle, CMD_FUNC | FUNC_4BIT | FUNC_2LIN | FUNC_5x8D, ret->backlight); + write_cmd(ret->handle, CMD_DISP | DISP_PWR, ret->backlight); + write_cmd(ret->handle, CMD_CLEAR, ret->backlight); + write_cmd(ret->handle, CMD_ENTRY | ENTRY_NORM, ret->backlight); + gpioDelay(200000); + + return ret; +} + +void display_destroy(display_t *display) +{ + if (!display) + { + fprintf(stderr, "WARN: display_destroy received a NULL display.\n"); + return; + } + + i2cClose(display->handle); + free(display); +} + + + +void display_clear(display_t *display) +{ + if (!display) + { + fprintf(stderr, "WARN: display_clear was given a NULL display.\n"); + return; + } + + write_cmd(display->handle, CMD_CLEAR, display->backlight); +} + +void display_set_backlight(display_t *display, bool backlight) +{ + if (!display) + { + fprintf(stderr, "WARN: display_set_backlight was given a NULL display.\n"); + return; + } + + display->backlight = backlight; + i2cWriteByte(display->handle, backlight ? PIN_BL : 0); +} + +void display_set_cursor(display_t *display, int lin, int col) +{ + if (!display) + { + fprintf(stderr, "WARN: display_set_cursor was given a NULL display.\n"); + return; + } + + int pos = col % 20; + switch (lin % 4) + { + case 1: + pos += 0x40; + break; + + case 2: + pos += 0x14; + break; + + case 3: + pos += 0x54; + break; + + case 0: + default: + break; + } + + write_cmd(display->handle, CMD_DRADR | (pos & 0x7F), display->backlight); +} + +void display_write_ch(display_t *display, char ch) +{ + if (!display) + { + fprintf(stderr, "WARN: display_write_ch was given a NULL display.\n"); + return; + } + + write_char(display->handle, ch, display->backlight); +} + +void display_write_str(display_t *display, char *str) +{ + if (!display) + { + fprintf(stderr, "WARN: display_write_str was given a NULL display.\n"); + return; + } + if (!str) + { + fprintf(stderr, "WARN: display_write_str was given a NULL str.\n"); + return; + } + + for (int i = 0; str[i]; i++) + write_char(display->handle, str[i], display->backlight); +} + +void display_write_str_at(display_t *display, char *str, int lin, int col) +{ + if (!display) + { + fprintf(stderr, "WARN: display_write_str_at was given a NULL display.\n"); + return; + } + if (!str) + { + fprintf(stderr, "WARN: display_write_str_at was given a NULL str.\n"); + return; + } + + display_set_cursor(display, lin, col); + display_write_str(display, str); +} + +void display_set_display_on(display_t *display, bool on) +{ + if (!display) + { + fprintf(stderr, "WARN: display_set_display_on was given a NULL display.\n"); + return; + } + + display->display = on; + char cmd = CMD_DISP | (display->display ? 0x4 : 0x0) | (display->cursor ? 0x2 : 0x0) | (display->blink ? 0x1 : 0x0); + write_cmd(display->handle, cmd, display->backlight); +} + +void display_set_cursor_show(display_t *display, bool show) +{ + if (!display) + { + fprintf(stderr, "WARN: display_set_cursor_show was given a NULL display.\n"); + return; + } + + display->cursor = show; + char cmd = CMD_DISP | (display->display ? 0x4 : 0x0) | (display->cursor ? 0x2 : 0x0) | (display->blink ? 0x1 : 0x0); + write_cmd(display->handle, cmd, display->backlight); +} + +void display_set_cursor_blink(display_t *display, bool blink) +{ + if (!display) + { + fprintf(stderr, "WARN: display_set_cursor_blink was given a NULL display.\n"); + return; + } + + display->blink = blink; + char cmd = CMD_DISP | (display->display ? 0x4 : 0x0) | (display->cursor ? 0x2 : 0x0) | (display->blink ? 0x1 : 0x0); + write_cmd(display->handle, cmd, display->backlight); +} + +void display_set_custom_char(display_t *display, uint8_t char_idx, uint64_t data) +{ + if (!display) + { + fprintf(stderr, "WARN: display_set_custom_char was given a NULL display.\n"); + return; + } + if (char_idx >= 8) + { + fprintf(stderr, "WARN: display_set_custom_char was given an invalid char_idx: %d.\n", char_idx); + return; + } + + write_cmd(display->handle, CMD_CGADR | (char_idx << 3), display->backlight); + + for (int i = 7; i >= 0; i--) + write_char(display->handle, (data >> (i * 8)) & 0xFF, display->backlight); +} diff --git a/src/pages/lcd_writer.c b/src/pages/lcd_writer.c new file mode 100644 index 0000000..0765360 --- /dev/null +++ b/src/pages/lcd_writer.c @@ -0,0 +1,271 @@ +#include "systems/lcd_writer.h" + +#include +#include +#include +#include +#include + +#include "drivers/display.h" + + + +static void *writer_thread_main(void *arg); +static void write_disp_diff(char *buff, char *cur, display_t *disp); + + + +struct lcd_writer_t +{ + bool bypass; + display_t *display; + char buffer[DISP_SIZE]; + + pthread_t write_thread; + pthread_mutex_t write_mutex; + pthread_cond_t write_condition; +}; + + + +lcd_writer_t *lcd_writer_init(display_t *display, bool bypass) +{ + lcd_writer_t *ret = malloc(sizeof(lcd_writer_t)); + + if (!ret) + { + fprintf(stderr, "WARN: lcd_writer_init failed to allocate memory.\n"); + return NULL; + } + + if (!bypass) + { + if (pthread_mutex_init(&ret->write_mutex, NULL)) + { + fprintf(stderr, "WARN: lcd_writer_init failed to init required mutex.\n"); + free(ret); + return NULL; + } + + if (pthread_cond_init(&ret->write_condition, NULL)) + { + fprintf(stderr, "WARN: lcd_writer_init failed to init required condition.\n"); + pthread_mutex_destroy(&ret->write_mutex); + free(ret); + return NULL; + } + + if (pthread_create(&ret->write_thread, NULL, writer_thread_main, ret)) + { + fprintf(stderr, "WARN: lcd_writer_init failed to start separate thread.\n"); + pthread_cond_destroy(&ret->write_condition); + pthread_mutex_destroy(&ret->write_mutex); + free(ret); + return NULL; + } + } + + ret->bypass = bypass; + ret->display = display; + memset(ret->buffer, ' ', DISP_SIZE); + display_clear(display); + + return ret; +} + +void lcd_writer_destroy(lcd_writer_t *lcdw) +{ + if (!lcdw) + { + fprintf(stderr, "WARN: lcd_writer_destroy received a NULL lcdw.\n"); + return; + } + + if (!lcdw->bypass) + { + pthread_cancel(lcdw->write_thread); + pthread_join(lcdw->write_thread, NULL); + pthread_cond_destroy(&lcdw->write_condition); + pthread_mutex_destroy(&lcdw->write_mutex); + } + + free(lcdw); +} + + + +void lcd_writer_clr(lcd_writer_t *lcdw) +{ + if (!lcdw) + { + fprintf(stderr, "WARN: lcd_writer_clr received a NULL lcdw.\n"); + return; + } + + if (!lcdw->bypass) + pthread_mutex_lock(&lcdw->write_mutex); + memset(lcdw->buffer, ' ', DISP_SIZE); + if (!lcdw->bypass) + pthread_mutex_unlock(&lcdw->write_mutex); +} + +void lcd_writer_clr_line(lcd_writer_t *lcdw, int lin) +{ + if (!lcdw) + { + fprintf(stderr, "WARN: lcd_writer_clr_line received a NULL lcdw.\n"); + return; + } + if (lin >= DISP_LINES) + { + fprintf(stderr, "WARN: lcd_writer_clr_line received too high a line number: %d.\n", lin); + return; + } + + if (!lcdw->bypass) + pthread_mutex_lock(&lcdw->write_mutex); + memset(&lcdw->buffer[lin * DISP_COLS], ' ', DISP_COLS); + if (!lcdw->bypass) + pthread_mutex_unlock(&lcdw->write_mutex); +} + +void lcd_writer_chr(lcd_writer_t *lcdw, char ch, int lin, int col) +{ + if (!lcdw) + { + fprintf(stderr, "WARN: lcd_writer_chr received a NULL lcdw.\n"); + return; + } + if (lin >= DISP_LINES) + { + fprintf(stderr, "WARN: lcd_writer_chr received too high a line number: %d.\n", lin); + return; + } + if (col >= DISP_COLS) + { + fprintf(stderr, "WARN: lcd_writer_chr received too high a column number: %d.\n", col); + return; + } + + if (!lcdw->bypass) + pthread_mutex_lock(&lcdw->write_mutex); + lcdw->buffer[lin * DISP_COLS + col] = ch; + if (!lcdw->bypass) + pthread_mutex_unlock(&lcdw->write_mutex); +} + +void lcd_writer_str(lcd_writer_t *lcdw, char *str, int lin, int col) +{ + if (!lcdw) + { + fprintf(stderr, "WARN: lcd_writer_str received a NULL lcdw.\n"); + return; + } + if (!str) + { + fprintf(stderr, "WARN: lcd_writer_str received a NULL string.\n"); + return; + } + if (lin >= DISP_LINES) + { + fprintf(stderr, "WARN: lcd_writer_str received too high a line number: %d.\n", lin); + return; + } + if (col >= DISP_COLS) + { + fprintf(stderr, "WARN: lcd_writer_str received too high a column number: %d.\n", col); + return; + } + + size_t len = strlen(str); + if ((int)len > DISP_COLS - col) + { + fprintf(stderr, "WARN: lcd_writer_str received too long a string. Truncating.\n"); + len = DISP_COLS - col; + } + + if (!lcdw->bypass) + pthread_mutex_lock(&lcdw->write_mutex); + memcpy(&lcdw->buffer[lin * DISP_COLS + col], str, len); + if (!lcdw->bypass) + pthread_mutex_unlock(&lcdw->write_mutex); +} + +void lcd_writer_flush(lcd_writer_t *lcdw) +{ + if (!lcdw) + { + fprintf(stderr, "WARN: lcd_writer_flush received a NULL lcdw.\n"); + return; + } + + if (!lcdw->bypass) + { + pthread_cond_signal(&lcdw->write_condition); + return; + } + + //Otherwise draw entire display syncronously + for (int l = 0; l < DISP_LINES; l++) + { + display_set_cursor(lcdw->display, l, 0); + for (int c = 0; c < DISP_COLS; c++) + display_write_ch(lcdw->display, lcdw->buffer[l * DISP_COLS + c]); + } +} + + + +static void *writer_thread_main(void *arg) +{ + lcd_writer_t *lcdw = arg; + char buff[DISP_SIZE]; + char cur[DISP_SIZE]; + + memset(buff, ' ', DISP_SIZE); + memset(cur, ' ', DISP_SIZE); + + while (1) + { + pthread_mutex_lock(&lcdw->write_mutex); + //FIXME: Will miss a cond_signal if not blocked here + pthread_cond_wait(&lcdw->write_condition, &lcdw->write_mutex); + + memcpy(buff, lcdw->buffer, DISP_SIZE); + + pthread_mutex_unlock(&lcdw->write_mutex); + + write_disp_diff(buff, cur, lcdw->display); + } + + return NULL; +} + +static void write_disp_diff(char *buff, char *cur, display_t *disp) +{ + //NOTE: set_cursor = 1 write => always worth when skipping characters + //NOTE: line changes always use set_cursor (because of unusual buffer address mapping) + int last_c; + + for (int l = 0; l < DISP_LINES; l++) + { + last_c = -1; + for (int c = 0; c < DISP_COLS; c++) + { + int addr = l * DISP_COLS + c; + + if (buff[addr] != cur[addr]) + { + //Use set_cursor if first of line non-consecutive write + if (last_c == -1 || c - last_c > 1) + display_set_cursor(disp, l, c); + + display_write_ch(disp, buff[addr]); + last_c = c; + } + } + } + + //Update cur buffer + memcpy(cur, buff, DISP_SIZE); +} diff --git a/src/pages/pager.c b/src/pages/pager.c new file mode 100644 index 0000000..248d347 --- /dev/null +++ b/src/pages/pager.c @@ -0,0 +1 @@ +#include "systems/pager.h"