From 43e3540518c9f930683a5a408f72d19c697d0309 Mon Sep 17 00:00:00 2001 From: openshwprojects <85486843+openshwprojects@users.noreply.github.com> Date: Thu, 29 Jan 2026 01:12:04 +0100 Subject: [PATCH] SSD1306 tiny I2C display driver * TEST * t1 * faster * Update drv_ssd1306.c * mk * Update drv_ssd1306.c * test * Update drv_ssd1306.c * Update drv_ssd1306.c * Update drv_ssd1306.c * fx * test * Update drv_ssd1306.c * cmds * fx * q * goto * Update drv_ssd1306.c * finish * rev --- platforms/obk_main.cmake | 1 + platforms/obk_main.mk | 1 + src/driver/drv_local.h | 3 + src/driver/drv_main.c | 16 ++ src/driver/drv_ssd1306.c | 311 +++++++++++++++++++++++++++++++++++++++ src/obk_config.h | 2 + 6 files changed, 334 insertions(+) create mode 100644 src/driver/drv_ssd1306.c diff --git a/platforms/obk_main.cmake b/platforms/obk_main.cmake index 03b9fb5b9..13ee7dcbc 100644 --- a/platforms/obk_main.cmake +++ b/platforms/obk_main.cmake @@ -129,6 +129,7 @@ set(OBKM_SRC ${OBK_SRCS}driver/drv_spi_flash.c ${OBK_SRCS}driver/drv_spidma.c ${OBK_SRCS}driver/drv_ssdp.c + ${OBK_SRCS}driver/drv_ssd1306.c ${OBK_SRCS}driver/drv_tasmotaDeviceGroups.c ${OBK_SRCS}driver/drv_tca9554.c ${OBK_SRCS}driver/drv_tclAC.c diff --git a/platforms/obk_main.mk b/platforms/obk_main.mk index 23aabaee2..4b6a25e1d 100644 --- a/platforms/obk_main.mk +++ b/platforms/obk_main.mk @@ -148,6 +148,7 @@ OBKM_SRC += $(OBK_SRCS)driver/drv_spiLED.c OBKM_SRC += $(OBK_SRCS)driver/drv_spi_flash.c OBKM_SRC += $(OBK_SRCS)driver/drv_spidma.c OBKM_SRC += $(OBK_SRCS)driver/drv_ssdp.c +OBKM_SRC += $(OBK_SRCS)driver/drv_ssd1306.c OBKM_SRC += $(OBK_SRCS)driver/drv_tasmotaDeviceGroups.c OBKM_SRC += $(OBK_SRCS)driver/drv_tclAC.c OBKM_SRC += $(OBK_SRCS)driver/drv_test.c diff --git a/src/driver/drv_local.h b/src/driver/drv_local.h index 685cea5a5..a57753d3f 100644 --- a/src/driver/drv_local.h +++ b/src/driver/drv_local.h @@ -29,6 +29,9 @@ void DRV_MAX72XX_Clock_OnEverySecond(); void DRV_MAX72XX_Clock_RunFrame(); void DRV_MAX72XX_Clock_Init(); +void SSD1306_Init(); +void SSD1306_OnEverySecond(); + void DRV_ADCButton_Init(); void DRV_ADCButton_RunFrame(); diff --git a/src/driver/drv_main.c b/src/driver/drv_main.c index be60259d0..cddc1385f 100644 --- a/src/driver/drv_main.c +++ b/src/driver/drv_main.c @@ -1007,6 +1007,22 @@ static driver_t g_drivers[] = { false, // loaded }, #endif +#if ENABLE_DRIVER_SSD1306 + //drvdetail:{"name":"SSD1306", + //drvdetail:"title":"TODO", + //drvdetail:"descr":"SSD1306SSD1306SSD1306SSD1306SSD1306SSD1306e.", + //drvdetail:"requires":""} + { "SSD1306", // Driver Name + SSD1306_Init, // Init + SSD1306_OnEverySecond, // onEverySecond + NULL, // appendInformationToHTTPIndexPage + NULL, // runQuickTick + NULL, // stopFunction + NULL, // onChannelChanged + NULL, // onHassDiscovery + false, // loaded + }, +#endif #if ENABLE_DRIVER_BMP280 //drvdetail:{"name":"BMP280", //drvdetail:"title":"TODO", diff --git a/src/driver/drv_ssd1306.c b/src/driver/drv_ssd1306.c new file mode 100644 index 000000000..0da602628 --- /dev/null +++ b/src/driver/drv_ssd1306.c @@ -0,0 +1,311 @@ +#include "../new_common.h" +#include "../new_pins.h" +#include "../new_cfg.h" +// Commands register, execution API and cmd tokenizer +#include "../cmnds/cmd_public.h" +#include "../mqtt/new_mqtt.h" +#include "../logging/logging.h" +#include "drv_local.h" +#include "drv_uart.h" +#include "../httpserver/new_http.h" +#include "../hal/hal_pins.h" + + +static softI2C_t g_softI2C; + +static int ssd1306_addr = 0x3C; + +#define SSD1306_CMD 0x00 +#define SSD1306_DATA 0x40 + + +static void SSD1306_WriteCmd(byte c) { + Soft_I2C_Start(&g_softI2C, ssd1306_addr << 1); + Soft_I2C_WriteByte(&g_softI2C, SSD1306_CMD); + Soft_I2C_WriteByte(&g_softI2C, c); + Soft_I2C_Stop(&g_softI2C); +} + +static void SSD1306_WriteData(byte d) { + Soft_I2C_Start(&g_softI2C, ssd1306_addr << 1); + Soft_I2C_WriteByte(&g_softI2C, SSD1306_DATA); + Soft_I2C_WriteByte(&g_softI2C, d); + Soft_I2C_Stop(&g_softI2C); +} + +void SSD1306_Fill(byte v) { + int i; + SSD1306_WriteCmd(0x21); // Set column range + SSD1306_WriteCmd(0x00); + SSD1306_WriteCmd(0x7F); + SSD1306_WriteCmd(0x22); // Set page range (for 128x32: 0-3) + SSD1306_WriteCmd(0x00); + SSD1306_WriteCmd(0x03); + for (i = 0; i < 512; i++) + SSD1306_WriteData(v); +} +void SSD1306_SetOn(bool b) { + if (b) { + SSD1306_WriteCmd(0xAF); + } + else { + SSD1306_WriteCmd(0xAE); + } +} +void SSD1306_SetPos(byte x, byte page) { + SSD1306_WriteCmd(0xB0 | page); + SSD1306_WriteCmd(0x00 | (x & 0x0F)); + SSD1306_WriteCmd(0x10 | (x >> 4)); +} + +void SSD1306_DrawRect(byte x, byte y, byte w, byte h, byte fill) { + byte px, py; + byte page_start = y >> 3; + byte page_end = (y + h - 1) >> 3; + + for (py = page_start; py <= page_end; py++) { + SSD1306_SetPos(x, py); + for (px = 0; px < w; px++) { + + byte mask = 0x00; + + if (fill) { + mask = 0xFF; + } + else { + if (py == page_start || py == page_end) + mask = 0xFF; + else if (px == 0 || px == w - 1) + mask = 0xFF; + } + + SSD1306_WriteData(mask); + } + } +} +// Standard 5x7 ASCII font +static const byte font5x7[][5] = { + {0x00, 0x00, 0x00, 0x00, 0x00}, // space + {0x00, 0x00, 0x5F, 0x00, 0x00}, // ! + {0x00, 0x07, 0x00, 0x07, 0x00}, // " + {0x14, 0x7F, 0x14, 0x7F, 0x14}, // # + {0x24, 0x2A, 0x7F, 0x2A, 0x12}, // $ + {0x23, 0x13, 0x08, 0x64, 0x62}, // % + {0x36, 0x49, 0x55, 0x22, 0x50}, // & + {0x00, 0x05, 0x03, 0x00, 0x00}, // ' + {0x00, 0x1C, 0x22, 0x41, 0x00}, // ( + {0x00, 0x41, 0x22, 0x1C, 0x00}, // ) + {0x14, 0x08, 0x3E, 0x08, 0x14}, // * + {0x08, 0x08, 0x3E, 0x08, 0x08}, // + + {0x00, 0x50, 0x60, 0x00, 0x00}, // , + {0x08, 0x08, 0x08, 0x08, 0x08}, // - + {0x00, 0x60, 0x60, 0x00, 0x00}, // . + {0x20, 0x10, 0x08, 0x04, 0x02}, // / + {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0 + {0x00, 0x42, 0x7F, 0x40, 0x00}, // 1 + {0x42, 0x61, 0x51, 0x49, 0x46}, // 2 + {0x21, 0x41, 0x45, 0x4B, 0x31}, // 3 + {0x18, 0x14, 0x12, 0x7F, 0x10}, // 4 + {0x27, 0x45, 0x45, 0x45, 0x39}, // 5 + {0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6 + {0x01, 0x71, 0x09, 0x05, 0x03}, // 7 + {0x36, 0x49, 0x49, 0x49, 0x36}, // 8 + {0x06, 0x49, 0x49, 0x29, 0x1E}, // 9 + {0x00, 0x36, 0x36, 0x00, 0x00}, // : + {0x00, 0x56, 0x36, 0x00, 0x00}, // ; + {0x08, 0x14, 0x22, 0x41, 0x00}, // < + {0x14, 0x14, 0x14, 0x14, 0x14}, // = + {0x00, 0x41, 0x22, 0x14, 0x08}, // > + {0x02, 0x01, 0x51, 0x09, 0x06}, // ? + {0x32, 0x49, 0x79, 0x41, 0x3E}, // @ + {0x7E, 0x11, 0x11, 0x11, 0x7E}, // A + {0x7F, 0x49, 0x49, 0x49, 0x36}, // B + {0x3E, 0x41, 0x41, 0x41, 0x22}, // C + {0x7F, 0x41, 0x41, 0x22, 0x1C}, // D + {0x7F, 0x49, 0x49, 0x49, 0x41}, // E + {0x7F, 0x09, 0x09, 0x09, 0x01}, // F + {0x3E, 0x41, 0x49, 0x49, 0x7A}, // G + {0x7F, 0x08, 0x08, 0x08, 0x7F}, // H + {0x00, 0x41, 0x7F, 0x41, 0x00}, // I + {0x20, 0x40, 0x41, 0x3F, 0x01}, // J + {0x7F, 0x08, 0x14, 0x22, 0x41}, // K + {0x7F, 0x40, 0x40, 0x40, 0x40}, // L + {0x7F, 0x02, 0x0C, 0x02, 0x7F}, // M + {0x7F, 0x04, 0x08, 0x10, 0x7F}, // N + {0x3E, 0x41, 0x41, 0x41, 0x3E}, // O + {0x7F, 0x09, 0x09, 0x09, 0x06}, // P + {0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q + {0x7F, 0x09, 0x19, 0x29, 0x46}, // R + {0x46, 0x49, 0x49, 0x49, 0x31}, // S + {0x01, 0x01, 0x7F, 0x01, 0x01}, // T + {0x3F, 0x40, 0x40, 0x40, 0x3F}, // U + {0x1F, 0x20, 0x40, 0x20, 0x1F}, // V + {0x3F, 0x40, 0x38, 0x40, 0x3F}, // W + {0x63, 0x14, 0x08, 0x14, 0x63}, // X + {0x07, 0x08, 0x70, 0x08, 0x07}, // Y + {0x61, 0x51, 0x49, 0x45, 0x43}, // Z +}; +void SSD1306_WriteChar(char c) { + // Convert lowercase to uppercase + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + if (c < 32 || c > 90) c = 32; // Basic bounds check, map unknown to space + c -= 32; // Offset to match array index + + Soft_I2C_Start(&g_softI2C, ssd1306_addr << 1); + Soft_I2C_WriteByte(&g_softI2C, SSD1306_DATA); + + for (int i = 0; i < 5; i++) { + Soft_I2C_WriteByte(&g_softI2C, font5x7[(uint8_t)c][i]); + } + Soft_I2C_WriteByte(&g_softI2C, 0x00); // 1px spacing between chars + Soft_I2C_Stop(&g_softI2C); +} + +void SSD1306_String(const char *str) { + while (*str) { + SSD1306_WriteChar(*str++); + } +} + +//int x = 1000; +void SSD1306_OnEverySecond() { + //SSD1306_SetOn(true); + //x++; + //char tmp[8]; + //sprintf(tmp, "%i", x); + //SSD1306_SetPos(0, 0); + //SSD1306_String(tmp); +} + + +commandResult_t SSD1306_Cmd_Print(const void* context, const char* cmd, const char* args, int cmdFlags) { + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + // following check must be done after 'Tokenizer_TokenizeString', + // so we know arguments count in Tokenizer. 'cmd' argument is + // only for warning display + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + const char *s = Tokenizer_GetArg(0); + SSD1306_String(s); + return CMD_RES_OK; +} +commandResult_t SSD1306_Cmd_GoTo(const void* context, const char* cmd, const char* args, int cmdFlags) { + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + // following check must be done after 'Tokenizer_TokenizeString', + // so we know arguments count in Tokenizer. 'cmd' argument is + // only for warning display + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 2)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + int x = Tokenizer_GetArgInteger(0); + int y = Tokenizer_GetArgInteger(1); + SSD1306_SetPos(x, y); + return CMD_RES_OK; +} +commandResult_t SSD1306_Cmd_On(const void* context, const char* cmd, const char* args, int cmdFlags) { + Tokenizer_TokenizeString(args, 0); + // following check must be done after 'Tokenizer_TokenizeString', + // so we know arguments count in Tokenizer. 'cmd' argument is + // only for warning display + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + int bOn = Tokenizer_GetArgInteger(0); + SSD1306_SetOn(bOn); + return CMD_RES_OK; +} +commandResult_t SSD1306_Cmd_Clear(const void* context, const char* cmd, const char* args, int cmdFlags) { + Tokenizer_TokenizeString(args, 0); + // following check must be done after 'Tokenizer_TokenizeString', + // so we know arguments count in Tokenizer. 'cmd' argument is + // only for warning display + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + int val = Tokenizer_GetArgInteger(0); + SSD1306_Fill(val); + return CMD_RES_OK; +} + +void SSD1306_Cmd_Rect(const void* context, const char* cmd, const char* args, int cmdFlags) { + Tokenizer_TokenizeString(args, 0); + // following check must be done after 'Tokenizer_TokenizeString', + // so we know arguments count in Tokenizer. 'cmd' argument is + // only for warning display + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 4)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + int x = Tokenizer_GetArgInteger(0); + int y = Tokenizer_GetArgInteger(1); + int w = Tokenizer_GetArgInteger(2); + int h = Tokenizer_GetArgInteger(3); + int fill = Tokenizer_GetArgIntegerDefault(4, 0xff); + SSD1306_DrawRect(x, y, w, h, fill); + return CMD_RES_OK; +} +// startDriver SSD1306 16 20 0x3C +// backlog stopdriver SSD1306 ; startDriver SSD1306 16 20 0x3C +/* + + + + +backlog stopdriver SSD1306 ; startDriver SSD1306 16 20 0x3C +ssd1306_clear 0 +ssd1306_on 1 +setChannel 12 0 +again: +delay_s 1 +ssd1306_goto 0 0 +ssd1306_print "Hello " +ssd1306_print $CH12 +addChannel 12 1 +goto again + + +*/ +void SSD1306_Init() { + + g_softI2C.pin_clk = Tokenizer_GetPin(1, 16); + g_softI2C.pin_data = Tokenizer_GetPin(2, 20); + ssd1306_addr = Tokenizer_GetArgIntegerDefault(3, 0x3C); + + CMD_RegisterCommand("ssd1306_clear", SSD1306_Cmd_Clear, NULL); + CMD_RegisterCommand("ssd1306_on", SSD1306_Cmd_On, NULL); + CMD_RegisterCommand("ssd1306_print", SSD1306_Cmd_Print, NULL); + CMD_RegisterCommand("ssd1306_rect", SSD1306_Cmd_Rect, NULL); + CMD_RegisterCommand("ssd1306_goto", SSD1306_Cmd_GoTo, NULL); + + Soft_I2C_PreInit(&g_softI2C); + + SSD1306_WriteCmd(0xAE); + SSD1306_WriteCmd(0xD5); + SSD1306_WriteCmd(0x80); + SSD1306_WriteCmd(0xA8); + SSD1306_WriteCmd(0x1F); + SSD1306_WriteCmd(0xD3); + SSD1306_WriteCmd(0x00); + SSD1306_WriteCmd(0x40); + SSD1306_WriteCmd(0x8D); + SSD1306_WriteCmd(0x14); + SSD1306_WriteCmd(0x20); + SSD1306_WriteCmd(0x00); + SSD1306_WriteCmd(0xA1); + SSD1306_WriteCmd(0xC8); + SSD1306_WriteCmd(0xDA); + SSD1306_WriteCmd(0x02); + SSD1306_WriteCmd(0x81); + SSD1306_WriteCmd(0x8F); + SSD1306_WriteCmd(0xD9); + SSD1306_WriteCmd(0xF1); + SSD1306_WriteCmd(0xDB); + SSD1306_WriteCmd(0x40); + SSD1306_WriteCmd(0xA4); + SSD1306_WriteCmd(0xA6); + SSD1306_WriteCmd(0xAF); + + SSD1306_Fill(0x00); +} \ No newline at end of file diff --git a/src/obk_config.h b/src/obk_config.h index 86d4942a7..b204e6896 100644 --- a/src/obk_config.h +++ b/src/obk_config.h @@ -242,6 +242,8 @@ //#define ENABLE_DRIVER_UART_TCP 1 +//#define ENABLE_DRIVER_SSD1306 1 + // #define ENABLE_DRIVER_PIR 1 //#define ENABLE_DRIVER_BKPARTITIONS 1 #define ENABLE_HA_DISCOVERY 1