diff --git a/boards/addons/gdisp/board_SSD1312_stm32cubehal.h b/boards/addons/gdisp/board_SSD1312_stm32cubehal.h new file mode 100644 index 00000000..69cdf932 --- /dev/null +++ b/boards/addons/gdisp/board_SSD1312_stm32cubehal.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include + +// The command byte to put on the front of each page line +#define SSD1312_PAGE_PREFIX 0x40 // Co = 0, D/C = 1 + +static I2C_HandleTypeDef i2cHandle; + +static GFXINLINE void init_board(GDisplay *g) +{ + (void) g; + + // GPIO + { + GPIO_InitTypeDef GPIO_InitStruct = {0}; + + __HAL_RCC_GPIOB_CLK_ENABLE(); + + // I2C SCL + GPIO_InitStruct.Pin = GPIO_PIN_8; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FAST; + GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + + // I2C SDA + GPIO_InitStruct.Pin = GPIO_PIN_9; + GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FAST; + GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + } + + // I2C1 + { + __HAL_RCC_I2C1_CLK_ENABLE(); + + i2cHandle.Instance = I2C1; + i2cHandle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; + i2cHandle.Init.ClockSpeed = 400000; + i2cHandle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; + i2cHandle.Init.DutyCycle = I2C_DUTYCYCLE_2; + i2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; + i2cHandle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; + if (HAL_I2C_Init(&i2cHandle) != HAL_OK) + gfxHalt("I2C HAL init error"); + } +} + +static GFXINLINE void post_init_board(GDisplay *g) +{ + (void) g; +} + +static GFXINLINE void setpin_reset(GDisplay *g, gBool state) +{ + (void) g; + (void) state; +} + +static GFXINLINE void acquire_bus(GDisplay *g) +{ + (void) g; +} + +static GFXINLINE void release_bus(GDisplay *g) +{ + (void) g; +} + +static GFXINLINE void write_cmd(GDisplay *g, gU8 *data, gU16 length) +{ + (void) g; + + gU8 buf[4]; // length is always <= 3 + buf[0] = 0x00; + memcpy(buf+1, data, length); + + HAL_I2C_Master_Transmit(&i2cHandle, (0x3c << 1), buf, length+1, 10000); +} + +static GFXINLINE void write_data(GDisplay *g, gU8 *data, gU16 length) +{ + (void) g; + + HAL_I2C_Master_Transmit(&i2cHandle, (0x3c << 1), data, length, 10000); +} + diff --git a/changelog.txt b/changelog.txt index a65d9058..474a41b5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -32,6 +32,7 @@ FEATURE: Add cmake support FIX: Add missing driver.mk to SSD1322 driver. FEATURE: LGDP4532 driver improvements. FIX: Win32 Keyboard driver now retrieves lock key states on window activate +FEATURE: Add SSD1312 GDISP driver *** Release 2.9 *** diff --git a/drivers/gdisp/SSD1312/board_SSD1312_template.h b/drivers/gdisp/SSD1312/board_SSD1312_template.h new file mode 100644 index 00000000..45a40505 --- /dev/null +++ b/drivers/gdisp/SSD1312/board_SSD1312_template.h @@ -0,0 +1,58 @@ +/* + * This file is subject to the terms of the GFX License. If a copy of + * the license was not distributed with this file, you can obtain one at: + * + * http://ugfx.io/license.html + */ + +#pragma once + +/** + * The command byte to put in front of each page line. + * + * If this is defined, each page line is prefixed with the specified value in the locally + * maintained framebuffer. + * + * This can be omitted and handled inside of write_data() manually instead. + */ +#define SSD1312_PAGE_PREFIX 0x40 // Co = 0, D/C = 1 + +static GFXINLINE void init_board(GDisplay *g) +{ + (void) g; +} + +static GFXINLINE void post_init_board(GDisplay *g) +{ + (void) g; +} + +static GFXINLINE void setpin_reset(GDisplay *g, gBool state) +{ + (void) g; + (void) state; +} + +static GFXINLINE void acquire_bus(GDisplay *g) +{ + (void) g; +} + +static GFXINLINE void release_bus(GDisplay *g) +{ + (void) g; +} + +static GFXINLINE void write_cmd(GDisplay *g, gU8 *data, gU16 length) +{ + (void) g; + (void) data; + (void) length; +} + +static GFXINLINE void write_data(GDisplay *g, gU8 *data, gU16 length) +{ + (void) g; + (void) data; + (void) length; +} diff --git a/drivers/gdisp/SSD1312/driver.cmake b/drivers/gdisp/SSD1312/driver.cmake new file mode 100644 index 00000000..a43ed3f3 --- /dev/null +++ b/drivers/gdisp/SSD1312/driver.cmake @@ -0,0 +1,8 @@ +list(APPEND ugfx_INCLUDE_DIRS + ${CMAKE_CURRENT_LIST_DIR} +) + +list(APPEND ugfx_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/gdisp_lld_SSD1312.c +) + diff --git a/drivers/gdisp/SSD1312/driver.mk b/drivers/gdisp/SSD1312/driver.mk new file mode 100644 index 00000000..e091a878 --- /dev/null +++ b/drivers/gdisp/SSD1312/driver.mk @@ -0,0 +1,3 @@ +GFXINC += $(GFXLIB)/drivers/gdisp/SSD1312 +GFXSRC += $(GFXLIB)/drivers/gdisp/SSD1312/gdisp_lld_SSD1312.c + diff --git a/drivers/gdisp/SSD1312/gdisp_lld_SSD1312.c b/drivers/gdisp/SSD1312/gdisp_lld_SSD1312.c new file mode 100644 index 00000000..bdfd4e76 --- /dev/null +++ b/drivers/gdisp/SSD1312/gdisp_lld_SSD1312.c @@ -0,0 +1,391 @@ +/* + * This file is subject to the terms of the GFX License. If a copy of + * the license was not distributed with this file, you can obtain one at: + * + * http://ugfx.io/license.html + */ + +#include "gfx.h" + +#if GFX_USE_GDISP + +#define GDISP_DRIVER_VMT GDISPVMT_SSD1312 +#include "gdisp_lld_config.h" +#include "../../../src/gdisp/gdisp_driver.h" + +#include "board_SSD1312.h" + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +#ifndef GDISP_SCREEN_HEIGHT + #define GDISP_SCREEN_HEIGHT 32 // This controller should also support 64 (untested) +#endif +#ifndef GDISP_SCREEN_WIDTH + #define GDISP_SCREEN_WIDTH 128 +#endif +#ifndef GDISP_INITIAL_CONTRAST + #define GDISP_INITIAL_CONTRAST 100 +#endif +#ifndef GDISP_INITIAL_BACKLIGHT + #define GDISP_INITIAL_BACKLIGHT 100 +#endif +#ifdef SSD1312_PAGE_PREFIX + #define SSD1312_PAGE_WIDTH (GDISP_SCREEN_WIDTH+1) + #define SSD1312_PAGE_OFFSET 1 +#else + #define SSD1312_PAGE_WIDTH GDISP_SCREEN_WIDTH + #define SSD1312_PAGE_OFFSET 0 +#endif + +#define GDISP_FLG_NEEDFLUSH (GDISP_FLG_DRIVER<<0) + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +// Some common routines and macros +#define RAM(g) ((gU8 *)g->priv) +#define xyaddr(x, y) (SSD1312_PAGE_OFFSET + (x) + ((y)>>3)*SSD1312_PAGE_WIDTH) +#define xybit(y) (1<<((y)&7)) + +#define write_cmd_1(g, a) { gU8 cmd[1]; cmd[0] = a; write_cmd(g, cmd, 1); } +#define write_cmd_2(g, a, b) { gU8 cmd[2]; cmd[0] = a; cmd[1] = b; write_cmd(g, cmd, 2); } + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * As this controller can't update on a pixel boundary we need to maintain the + * the entire display surface in memory so that we can do the necessary bit + * operations. Fortunately it is a small monochrome display. + * 64 * 128 / 8 = 1024 bytes. + */ + +LLDSPEC gBool gdisp_lld_init(GDisplay *g) +{ + // The private area is the display surface. + g->priv = gfxAlloc(GDISP_SCREEN_HEIGHT/8 * SSD1312_PAGE_WIDTH); + if (!g->priv) + return gFalse; + + // Fill in the prefix command byte on each page line of the display buffer + // We can do this during initialisation as we're being careful that this byte is never overwritten. + #ifdef SSD1312_PAGE_PREFIX + for (unsigned i = 0; i < GDISP_SCREEN_HEIGHT/8 * SSD1312_PAGE_WIDTH; i += SSD1312_PAGE_WIDTH) + RAM(g)[i] = SSD1312_PAGE_PREFIX; + #endif + + // Initialise the board interface + init_board(g); + + // Hardware reset + setpin_reset(g, gTrue); + gfxSleepMilliseconds(20); + setpin_reset(g, gFalse); + gfxSleepMilliseconds(200); + + acquire_bus(g); + + // Configuration + // This might require display module vendor specific changes + { + // Display off + write_cmd_1(g, 0xAE); + + // Clock divider + write_cmd_2(g, 0xD5, 0x80); + + // Multiplex ratio + write_cmd_2(g, 0xA8, 0x1F); + + // Display offset + write_cmd_2(g, 0xD3, 0x30); + + // Display start line + write_cmd_1(g, 0x40); + + // Charge pump + write_cmd_2(g, 0x8D, 0x72); // 0x10 if Vcc externally supplied + + // Segment re-map + write_cmd_1(g, 0xA1); + + // COM output scan direction + write_cmd_1(g, 0xC0); + + // COM pin hardware configuration + write_cmd_2(g, 0xDA, 0x10); + + // Set internal/external current reference + write_cmd_2(g, 0xAD, 0x50); + + // Set contract + write_cmd_2(g, 0x81, 0x17); + + // Set pre-charge period + write_cmd_2(g, 0xD9, 0xF1); + + // Set VCOMH select level + write_cmd_2(g, 0xDB, 0x30); + + // Set entire display on/off + write_cmd_1(g, 0xA4); + + // Set normal/inverse display + write_cmd_1(g, 0xA6); + + // Page addressing mode + write_cmd_2(g, 0x20, 0x02); + + // Display on + write_cmd_1(g, 0xAF); + } + + release_bus(g); + + // Finish Init + post_init_board(g); + + // Initialise the GDISP structure + g->g.Width = GDISP_SCREEN_WIDTH; + g->g.Height = GDISP_SCREEN_HEIGHT; + g->g.Orientation = gOrientation0; + g->g.Powermode = gPowerOn; + g->g.Backlight = GDISP_INITIAL_BACKLIGHT; + g->g.Contrast = GDISP_INITIAL_CONTRAST; + + return gTrue; +} + +#if GDISP_HARDWARE_FLUSH + LLDSPEC void gdisp_lld_flush(GDisplay *g) + { + gU8 * ram; + unsigned pages; + + // Only flush if necessary + if (!(g->flags & GDISP_FLG_NEEDFLUSH)) + return; + + ram = RAM(g); + pages = GDISP_SCREEN_HEIGHT/8; + + acquire_bus(g); + write_cmd_1(g, 0x40 | 0); + while (pages--) { + write_cmd_1(g, 0xB0 + (((GDISP_SCREEN_HEIGHT/8)-1)-pages)); + write_cmd_1(g, 0x00); + write_cmd_1(g, 0x10); + write_data(g, ram, SSD1312_PAGE_WIDTH); + ram += SSD1312_PAGE_WIDTH; + } + release_bus(g); + + g->flags &= ~GDISP_FLG_NEEDFLUSH; + } +#endif + +#if GDISP_HARDWARE_DRAWPIXEL + LLDSPEC void gdisp_lld_draw_pixel(GDisplay *g) + { + gCoord x, y; + + switch(g->g.Orientation) { + default: + case gOrientation0: + x = g->p.x; + y = g->p.y; + break; + case gOrientation90: + x = g->p.y; + y = GDISP_SCREEN_HEIGHT-1 - g->p.x; + break; + case gOrientation180: + x = GDISP_SCREEN_WIDTH-1 - g->p.x; + y = GDISP_SCREEN_HEIGHT-1 - g->p.y; + break; + case gOrientation270: + x = GDISP_SCREEN_WIDTH-1 - g->p.y; + y = g->p.x; + break; + } + if (gdispColor2Native(g->p.color) != gdispColor2Native(GFX_BLACK)) + RAM(g)[xyaddr(x, y)] |= xybit(y); + else + RAM(g)[xyaddr(x, y)] &= ~xybit(y); + g->flags |= GDISP_FLG_NEEDFLUSH; + } +#endif + +#if GDISP_HARDWARE_PIXELREAD + LLDSPEC gColor gdisp_lld_get_pixel_color(GDisplay *g) + { + gCoord x, y; + + switch(g->g.Orientation) { + default: + case gOrientation0: + x = g->p.x; + y = g->p.y; + break; + case gOrientation90: + x = g->p.y; + y = GDISP_SCREEN_HEIGHT-1 - g->p.x; + break; + case gOrientation180: + x = GDISP_SCREEN_WIDTH-1 - g->p.x; + y = GDISP_SCREEN_HEIGHT-1 - g->p.y; + break; + case gOrientation270: + x = GDISP_SCREEN_WIDTH-1 - g->p.y; + y = g->p.x; + break; + } + + return (RAM(g)[xyaddr(x, y)] & xybit(y)) ? GFX_WHITE : GFX_BLACK; + } +#endif + +#if GDISP_HARDWARE_FILLS + LLDSPEC void gdisp_lld_fill_area(GDisplay *g) + { + gCoord sy, ey; + gCoord sx, ex; + gCoord col; + unsigned spage, zpages; + gU8 * base; + gU8 mask; + + switch(g->g.Orientation) { + default: + case gOrientation0: + sx = g->p.x; + ex = g->p.x + g->p.cx - 1; + sy = g->p.y; + ey = sy + g->p.cy - 1; + break; + case gOrientation90: + sx = g->p.y; + ex = g->p.y + g->p.cy - 1; + sy = GDISP_SCREEN_HEIGHT - g->p.x - g->p.cx; + ey = GDISP_SCREEN_HEIGHT-1 - g->p.x; + break; + case gOrientation180: + sx = GDISP_SCREEN_WIDTH - g->p.x - g->p.cx; + ex = GDISP_SCREEN_WIDTH-1 - g->p.x; + sy = GDISP_SCREEN_HEIGHT - g->p.y - g->p.cy; + ey = GDISP_SCREEN_HEIGHT-1 - g->p.y; + break; + case gOrientation270: + sx = GDISP_SCREEN_WIDTH - g->p.y - g->p.cy; + ex = GDISP_SCREEN_WIDTH-1 - g->p.y; + sy = g->p.x; + ey = g->p.x + g->p.cx - 1; + break; + } + + spage = sy / 8; + base = RAM(g) + SSD1312_PAGE_OFFSET + SSD1312_PAGE_WIDTH * spage; + mask = 0xff << (sy&7); + zpages = (ey / 8) - spage; + + if (gdispColor2Native(g->p.color) == gdispColor2Native(GFX_BLACK)) { + while (zpages--) { + for (col = sx; col <= ex; col++) + base[col] &= ~mask; + mask = 0xff; + base += SSD1312_PAGE_WIDTH; + } + mask &= (0xff >> (7 - (ey&7))); + for (col = sx; col <= ex; col++) + base[col] &= ~mask; + } + else { + while (zpages--) { + for (col = sx; col <= ex; col++) + base[col] |= mask; + mask = 0xff; + base += SSD1312_PAGE_WIDTH; + } + mask &= (0xff >> (7 - (ey&7))); + for (col = sx; col <= ex; col++) + base[col] |= mask; + } + g->flags |= GDISP_FLG_NEEDFLUSH; + } +#endif + +#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL + LLDSPEC void gdisp_lld_control(GDisplay *g) + { + switch(g->p.x) { + case GDISP_CONTROL_POWER: + if (g->g.Powermode == (gPowermode)g->p.ptr) + return; + switch((gPowermode)g->p.ptr) { + case gPowerOff: + case gPowerSleep: + case gPowerDeepSleep: + acquire_bus(g); + write_cmd_1(g, 0xAE); + release_bus(g); + break; + case gPowerOn: + acquire_bus(g); + write_cmd_1(g, 0xAF); + release_bus(g); + break; + default: + return; + } + g->g.Powermode = (gPowermode)g->p.ptr; + return; + + case GDISP_CONTROL_ORIENTATION: + if (g->g.Orientation == (gOrientation)g->p.ptr) + return; + + switch((gOrientation)g->p.ptr) { + // Rotation is handled by the drawing routines + case gOrientation0: + case gOrientation180: + g->g.Height = GDISP_SCREEN_HEIGHT; + g->g.Width = GDISP_SCREEN_WIDTH; + break; + case gOrientation90: + case gOrientation270: + g->g.Height = GDISP_SCREEN_WIDTH; + g->g.Width = GDISP_SCREEN_HEIGHT; + break; + default: + return; + } + + g->g.Orientation = (gOrientation)g->p.ptr; + return; + + case GDISP_CONTROL_CONTRAST: + if ((unsigned)g->p.ptr > 100) + g->p.ptr = (void *)100; + acquire_bus(g); + write_cmd_2(g, 0x81, (((unsigned)g->p.ptr)<<8)/101); + release_bus(g); + g->g.Contrast = (unsigned)g->p.ptr; + return; + + // Our own special controller code to inverse the display + // 0 = normal, 1 = inverse + case GDISP_CONTROL_INVERSE: + acquire_bus(g); + write_cmd_1(g, g->p.ptr ? 0xA7: 0xA6); + release_bus(g); + return; + } + } +#endif // GDISP_NEED_CONTROL + +#endif // GFX_USE_GDISP + diff --git a/drivers/gdisp/SSD1312/gdisp_lld_config.h b/drivers/gdisp/SSD1312/gdisp_lld_config.h new file mode 100644 index 00000000..289b6a00 --- /dev/null +++ b/drivers/gdisp/SSD1312/gdisp_lld_config.h @@ -0,0 +1,31 @@ +/* + * This file is subject to the terms of the GFX License. If a copy of + * the license was not distributed with this file, you can obtain one at: + * + * http://ugfx.io/license.html + */ + +#ifndef _GDISP_LLD_CONFIG_H +#define _GDISP_LLD_CONFIG_H + +#if GFX_USE_GDISP + +/*===========================================================================*/ +/* Driver hardware support. */ +/*===========================================================================*/ + +#define GDISP_HARDWARE_FLUSH GFXON // This controller requires flushing +#define GDISP_HARDWARE_DRAWPIXEL GFXON +#define GDISP_HARDWARE_PIXELREAD GFXON +#define GDISP_HARDWARE_CONTROL GFXON +#define GDISP_HARDWARE_FILLS GFXON + +#define GDISP_LLD_PIXELFORMAT GDISP_PIXELFORMAT_MONO + +// This controller supports a special gdispControl() to inverse the display. +// Pass a parameter of 1 for inverse and 0 for normal. +#define GDISP_CONTROL_INVERSE (GDISP_CONTROL_LLD+0) + +#endif // GFX_USE_GDISP + +#endif // _GDISP_LLD_CONFIG_H diff --git a/drivers/gdisp/readme.txt b/drivers/gdisp/readme.txt index 8658f845..6e331501 100644 --- a/drivers/gdisp/readme.txt +++ b/drivers/gdisp/readme.txt @@ -24,6 +24,7 @@ S6D1121 - Mid-sized color LCD displays eg RGB565 320x240 SPFD54124B - Mid-sized color LCD displays eg RGB565 320x240 SSD1289 - Mid-sized color LCD displays eg RGB565 320x240 SSD1306 - Small monochrome LCD +SSD1312 - Small monochrome LCD (usually 128x32 or 128x64 OLED) SSD1322 - Small 16 level grayscale LCD SSD1331 - Small hardware accelerated OLED display RGB565 96x64 SSD1351 - Mid-sized color LCD displays eg RGB565 320x240