Skip to content

Commit 6e74409

Browse files
authored
🐛 Fix STM32 I2C 2-wire LCD, Soft I2C impl. (#26433)
1 parent 5af8425 commit 6e74409

File tree

9 files changed

+264
-19
lines changed

9 files changed

+264
-19
lines changed

Marlin/src/HAL/STM32/u8g/LCD_defines.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ uint8_t u8g_com_HAL_STM32_sw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, vo
3030

3131
uint8_t u8g_com_stm32duino_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // See U8glib-HAL
3232
#define U8G_COM_HAL_HW_SPI_FN u8g_com_stm32duino_hw_spi_fn
33+
34+
uint8_t u8g_com_stm32duino_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); // u8g_com_stm32duino_ssd_i2c.cpp
35+
#define U8G_COM_SSD_I2C_HAL u8g_com_stm32duino_ssd_i2c_fn
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Marlin 3D Printer Firmware
3+
* Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4+
*
5+
* Based on Sprinter and grbl.
6+
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
23+
/**
24+
* 2-Wire I2C COM Driver
25+
*
26+
* Handles both Hardware and Software I2C so any pins can be used as SDA and SLC.
27+
* Wire library is used for Hardware I2C.
28+
* SlowSoftWire is used for Software I2C.
29+
*
30+
* Wire / SoftWire library selection can be done automatically at runtime.
31+
*
32+
* SDA and SLC pins must be named DOGLCD_SDA_PIN, DOGLCD_SCL_PIN to distinguish
33+
* from other I2C devices (e.g., EEPROM) that use I2C_SDA_PIN, I2C_SLC_PIN.
34+
*/
35+
#ifdef ARDUINO_ARCH_STM32
36+
37+
#include "../../../inc/MarlinConfig.h"
38+
39+
#if HAS_U8GLIB_I2C_OLED
40+
41+
#include <U8glib-HAL.h>
42+
43+
#if ENABLED(U8G_USES_HW_I2C)
44+
#include <Wire.h>
45+
#ifndef MASTER_ADDRESS
46+
#define MASTER_ADDRESS 0x01
47+
#endif
48+
#endif
49+
50+
#if ENABLED(U8G_USES_SW_I2C)
51+
#include <SlowSoftI2CMaster.h>
52+
#include <SlowSoftWire.h>
53+
#endif
54+
55+
/**
56+
* BUFFER_LENGTH is defined in libraries\Wire\utility\WireBase.h
57+
* Default value is 32
58+
* Increase this value to 144 to send U8G_COM_MSG_WRITE_SEQ in single block
59+
*/
60+
#ifndef BUFFER_LENGTH
61+
#define BUFFER_LENGTH 32
62+
#endif
63+
#if BUFFER_LENGTH > 144
64+
#error "BUFFER_LENGTH should not be greater than 144."
65+
#endif
66+
#define I2C_MAX_LENGTH (BUFFER_LENGTH - 1)
67+
68+
uint8_t u8g_com_stm32duino_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) {
69+
// Hardware I2C flag
70+
#ifdef COMPILE_TIME_I2C_IS_HARDWARE
71+
constexpr bool isHardI2C = ENABLED(COMPILE_TIME_I2C_IS_HARDWARE);
72+
#else
73+
static bool isHardI2C = false;
74+
static bool i2c_initialized = false; // Flag to only run init/linking code once
75+
if (!i2c_initialized) { // Init runtime linkages
76+
i2c_initialized = true; // Only do this once
77+
I2C_TypeDef *i2cInstance1 = (I2C_TypeDef *)pinmap_peripheral(digitalPinToPinName(DOGLCD_SDA_PIN), PinMap_I2C_SDA);
78+
I2C_TypeDef *i2cInstance2 = (I2C_TypeDef *)pinmap_peripheral(digitalPinToPinName(DOGLCD_SCL_PIN), PinMap_I2C_SCL);
79+
isHardI2C = (i2cInstance1 && (i2cInstance1 == i2cInstance2)); // Found hardware I2C controller
80+
}
81+
#endif
82+
83+
static uint8_t msgInitCount = 0; // Ignore all messages until 2nd U8G_COM_MSG_INIT
84+
if (msgInitCount) {
85+
if (msg == U8G_COM_MSG_INIT) msgInitCount--;
86+
if (msgInitCount) return -1;
87+
}
88+
89+
static uint8_t control;
90+
if (isHardI2C) { // Found hardware I2C controller
91+
#if ENABLED(U8G_USES_HW_I2C)
92+
static TwoWire wire2; // A TwoWire object for use below
93+
switch (msg) {
94+
case U8G_COM_MSG_INIT:
95+
wire2.setClock(400000);
96+
wire2.setSCL(DOGLCD_SCL_PIN);
97+
wire2.setSDA(DOGLCD_SDA_PIN);
98+
wire2.begin(MASTER_ADDRESS, 0); // Start as master
99+
break;
100+
101+
case U8G_COM_MSG_ADDRESS: // Define cmd (arg_val = 0) or data mode (arg_val = 1)
102+
control = arg_val ? 0x40 : 0x00;
103+
break;
104+
105+
case U8G_COM_MSG_WRITE_BYTE:
106+
wire2.beginTransmission(0x3C);
107+
wire2.write(control);
108+
wire2.write(arg_val);
109+
wire2.endTransmission();
110+
break;
111+
112+
case U8G_COM_MSG_WRITE_SEQ: {
113+
uint8_t* dataptr = (uint8_t*)arg_ptr;
114+
#ifdef I2C_MAX_LENGTH
115+
while (arg_val > 0) {
116+
wire2.beginTransmission(0x3C);
117+
wire2.write(control);
118+
if (arg_val <= I2C_MAX_LENGTH) {
119+
wire2.write(dataptr, arg_val);
120+
arg_val = 0;
121+
}
122+
else {
123+
wire2.write(dataptr, I2C_MAX_LENGTH);
124+
arg_val -= I2C_MAX_LENGTH;
125+
dataptr += I2C_MAX_LENGTH;
126+
}
127+
wire2.endTransmission();
128+
}
129+
#else
130+
wire2.beginTransmission(0x3C);
131+
wire2.write(control);
132+
wire2.write(dataptr, arg_val);
133+
wire2.endTransmission();
134+
#endif // I2C_MAX_LENGTH
135+
break;
136+
}
137+
}
138+
#endif // U8G_USES_HW_I2C
139+
}
140+
else { // Software I2C
141+
#if ENABLED(U8G_USES_SW_I2C)
142+
static SlowSoftWire sWire = SlowSoftWire(DOGLCD_SDA_PIN, DOGLCD_SCL_PIN);
143+
144+
switch (msg) {
145+
case U8G_COM_MSG_INIT:
146+
sWire.setClock(400000);
147+
sWire.begin(); // Start as master
148+
break;
149+
150+
case U8G_COM_MSG_ADDRESS: // Define cmd (arg_val = 0) or data mode (arg_val = 1)
151+
control = arg_val ? 0x40 : 0x00;
152+
break;
153+
154+
case U8G_COM_MSG_WRITE_BYTE:
155+
sWire.beginTransmission((uint8_t)0x3C);
156+
sWire.write((uint8_t)control);
157+
sWire.write((uint8_t)arg_val);
158+
sWire.endTransmission();
159+
break;
160+
161+
case U8G_COM_MSG_WRITE_SEQ: {
162+
uint8_t* dataptr = (uint8_t*)arg_ptr;
163+
#ifdef I2C_MAX_LENGTH
164+
while (arg_val > 0) {
165+
sWire.beginTransmission((uint8_t)0x3C);
166+
sWire.write((uint8_t)control);
167+
if (arg_val <= I2C_MAX_LENGTH) {
168+
sWire.write((const uint8_t *)dataptr, (size_t)arg_val);
169+
arg_val = 0;
170+
}
171+
else {
172+
sWire.write((const uint8_t *)dataptr, I2C_MAX_LENGTH);
173+
arg_val -= I2C_MAX_LENGTH;
174+
dataptr += I2C_MAX_LENGTH;
175+
}
176+
sWire.endTransmission();
177+
}
178+
#else
179+
sWire.beginTransmission((uint8_t)0x3C);
180+
sWire.write((uint8_t)control);
181+
sWire.write((const uint8_t *)dataptr, (size_t)arg_val);
182+
sWire.endTransmission();
183+
#endif // I2C_MAX_LENGTH
184+
break;
185+
}
186+
}
187+
#endif // U8G_USES_SW_I2C
188+
}
189+
190+
return 1;
191+
}
192+
193+
#endif // HAS_U8GLIB_I2C_OLED
194+
#endif // ARDUINO_ARCH_STM32

Marlin/src/inc/Conditionals-2-LCD.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,22 @@
354354
// ...and 128x64 SPI OLED LCDs (SSD1306 / SH1106)
355355
#if ANY(U8GLIB_SSD1306, U8GLIB_SSD1309, U8GLIB_SH1106)
356356
#define HAS_U8GLIB_I2C_OLED 1
357+
358+
// Define this to reduce build size and optimize performance
359+
//#define COMPILE_TIME_I2C_IS_HARDWARE true // true: Hardware false: Software undefined: Solve at runtime
360+
361+
#ifdef COMPILE_TIME_I2C_IS_HARDWARE
362+
#if COMPILE_TIME_I2C_IS_HARDWARE
363+
#define U8G_USES_HW_I2C
364+
#else
365+
#define U8G_USES_SW_I2C
366+
#endif
367+
#else
368+
#define U8G_USES_HW_I2C
369+
#define U8G_USES_SW_I2C
370+
#endif
357371
#endif
372+
358373
#if ANY(HAS_U8GLIB_I2C_OLED, U8GLIB_SSD1306_SPI, U8GLIB_SH1106_SPI)
359374
#define HAS_WIRED_LCD 1
360375
#define DOGLCD

Marlin/src/lcd/dogm/marlinui_DOGM.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,22 @@ void MarlinUI::init_lcd() {
310310
#endif
311311

312312
#if ANY(MKS_12864OLED, MKS_12864OLED_SSD1306, FYSETC_242_OLED_12864, ZONESTAR_12864OLED, K3D_242_OLED_CONTROLLER)
313-
SET_OUTPUT(LCD_PINS_DC);
313+
314+
#if defined(LCD_PINS_DC) && LCD_PINS_DC != -1
315+
#if IS_I2C_LCD
316+
I2C_TypeDef *i2cInstance1 = (I2C_TypeDef *)pinmap_peripheral(digitalPinToPinName(DOGLCD_SDA_PIN), PinMap_I2C_SDA);
317+
I2C_TypeDef *i2cInstance2 = (I2C_TypeDef *)pinmap_peripheral(digitalPinToPinName(DOGLCD_SCL_PIN), PinMap_I2C_SCL);
318+
const bool isSoftI2C = !(i2cInstance1 && (i2cInstance1 == i2cInstance2)); // Using software I2C driver for LCD
319+
#else
320+
constexpr bool isSoftI2C = false;
321+
#endif
322+
if (!isSoftI2C) SET_OUTPUT(LCD_PINS_DC); // For these LCDs, set as output if not using software I2C driver
323+
#endif
324+
325+
#ifndef LCD_RESET_PIN
326+
#define LCD_RESET_PIN LCD_PINS_RS
327+
#endif
328+
314329
#endif
315330

316331
#if PIN_EXISTS(LCD_RESET)

Marlin/src/lcd/dogm/marlinui_DOGM.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232

3333
//#define ALTERNATIVE_LCD
3434

35+
// Defined DOGLCD_SDA_PIN and DOGLCD_SCL_PIN pins indicate I2C LCD
36+
#if PINS_EXIST(DOGLCD_SDA, DOGLCD_SCL)
37+
#define IS_I2C_LCD 1
38+
#endif
39+
3540
#if ENABLED(REPRAPWORLD_GRAPHICAL_LCD)
3641

3742
// RepRapWorld Graphical LCD
@@ -126,12 +131,15 @@
126131

127132
// MKS 128x64 (SSD1306) OLED I2C LCD
128133

129-
#define FORCE_SOFT_SPI // SW-SPI
130-
131-
#if ENABLED(ALTERNATIVE_LCD)
132-
#define U8G_CLASS U8GLIB_SSD1306_128X64_2X // 4 stripes
134+
#if IS_I2C_LCD
135+
#define U8G_CLASS U8GLIB_SSD1306_128X64_2X_I2C_2_WIRE // I2C
133136
#else
134-
#define U8G_CLASS U8GLIB_SSD1306_128X64 // 8 stripes
137+
#define FORCE_SOFT_SPI // SW-SPI
138+
#if ENABLED(ALTERNATIVE_LCD)
139+
#define U8G_CLASS U8GLIB_SSD1306_128X64_2X // 4 stripes
140+
#else
141+
#define U8G_CLASS U8GLIB_SSD1306_128X64 // 8 stripes
142+
#endif
135143
#endif
136144

137145
#elif ANY(FYSETC_242_OLED_12864, K3D_242_OLED_CONTROLLER)
@@ -168,7 +176,9 @@
168176
// - or -
169177
// Zonestar SH1106 OLED SPI LCD
170178

171-
#define FORCE_SOFT_SPI // SW-SPI
179+
#if !IS_I2C_LCD
180+
#define FORCE_SOFT_SPI // SW-SPI
181+
#endif
172182
#if ENABLED(ALTERNATIVE_LCD)
173183
#define U8G_CLASS U8GLIB_SH1106_128X64_2X // 4 stripes
174184
#else
@@ -238,7 +248,9 @@
238248

239249
// Use HW-SPI if no other option is specified
240250
#ifndef U8G_PARAM
241-
#if ENABLED(FORCE_SOFT_SPI)
251+
#if IS_I2C_LCD
252+
#define U8G_PARAM U8G_I2C_OPT_NONE // I2C LCD
253+
#elif ENABLED(FORCE_SOFT_SPI)
242254
#define U8G_PARAM DOGLCD_SCK, DOGLCD_MOSI, DOGLCD_CS, DOGLCD_A0 // SW-SPI
243255
#else
244256
#define U8G_PARAM DOGLCD_CS, DOGLCD_A0 // HW-SPI

Marlin/src/lcd/dogm/u8g/HAL_LCD_class_defines.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class U8GLIB_SH1106_128X64_2X_I2C_2_WIRE : public U8GLIB {
7878
public:
7979
U8GLIB_SH1106_128X64_2X_I2C_2_WIRE() : U8GLIB() { }
8080
U8GLIB_SH1106_128X64_2X_I2C_2_WIRE(uint8_t options) { init(options); }
81-
void init(uint8_t options = U8G_I2C_OPT_NONE) { U8GLIB::init(&u8g_dev_sh1106_128x64_2x_i2c_2_wire, options); }
81+
void init(uint8_t options=U8G_I2C_OPT_NONE) { U8GLIB::init(&u8g_dev_sh1106_128x64_2x_i2c_2_wire, options); }
8282
};
8383

8484
extern u8g_dev_t u8g_dev_ssd1306_128x64_2x_i2c_2_wire;
@@ -87,7 +87,7 @@ class U8GLIB_SSD1306_128X64_2X_I2C_2_WIRE : public U8GLIB {
8787
public:
8888
U8GLIB_SSD1306_128X64_2X_I2C_2_WIRE() : U8GLIB() { }
8989
U8GLIB_SSD1306_128X64_2X_I2C_2_WIRE(uint8_t options) { init(options); }
90-
void init(uint8_t options = U8G_I2C_OPT_NONE) { U8GLIB::init(&u8g_dev_ssd1306_128x64_2x_i2c_2_wire, options); }
90+
void init(uint8_t options=U8G_I2C_OPT_NONE) { U8GLIB::init(&u8g_dev_ssd1306_128x64_2x_i2c_2_wire, options); }
9191
};
9292

9393
#if ENABLED(U8GLIB_SH1106_SPI)

Marlin/src/lcd/dogm/u8g/u8g_dev_ssd1306_sh1106_128x64_I2C.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767

6868
#include "../../../inc/MarlinConfigPre.h"
6969

70-
#if HAS_MARLINUI_U8GLIB
70+
#if HAS_U8GLIB_I2C_OLED
7171

7272
#include "HAL_LCD_com_defines.h"
7373

@@ -97,6 +97,7 @@
9797
#define CMD_NOOP() (0xE3)
9898

9999
uint8_t u8g_WriteEscSeqP_2_wire(u8g_t *u8g, u8g_dev_t *dev, const uint8_t *esc_seq);
100+
uint8_t u8g_Write_Init_Sequence_2_wire(u8g_t *u8g, u8g_dev_t *dev, uint32_t length, const uint8_t *init_seq);
100101

101102
// SH1106 is compatible with SSD1306, but is 132x64. Display 128x64 centered within the 132x64.
102103

@@ -133,7 +134,7 @@ uint8_t u8g_dev_sh1106_128x64_2x_2_wire_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t m
133134
switch (msg) {
134135
case U8G_DEV_MSG_INIT:
135136
u8g_InitCom(u8g, dev, U8G_SPI_CLK_CYCLE_300NS);
136-
u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_sh1106_128x64_init_seq_2_wire);
137+
u8g_Write_Init_Sequence_2_wire(u8g, dev, COUNT(u8g_dev_sh1106_128x64_init_seq_2_wire), u8g_dev_sh1106_128x64_init_seq_2_wire);
137138
break;
138139
case U8G_DEV_MSG_STOP: break;
139140
case U8G_DEV_MSG_PAGE_NEXT: {
@@ -151,7 +152,7 @@ uint8_t u8g_dev_sh1106_128x64_2x_2_wire_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t m
151152
u8g_WriteSequence(u8g, dev, pb->width, (uint8_t *)(pb->buf)+pb->width);
152153
u8g_SetChipSelect(u8g, dev, 0);
153154
} break;
154-
case U8G_DEV_MSG_SLEEP_ON: return 1;
155+
case U8G_DEV_MSG_SLEEP_ON:
155156
case U8G_DEV_MSG_SLEEP_OFF: return 1;
156157
}
157158
return u8g_dev_pb16v1_base_fn(u8g, dev, msg, arg);
@@ -169,7 +170,6 @@ static const uint8_t u8g_dev_ssd1306_128x64_data_start_2_wire[] PROGMEM = {
169170
};
170171

171172
static const uint8_t u8g_dev_ssd1306_128x64_init_seq_2_wire[] PROGMEM = {
172-
U8G_ESC_CS(0), // Disable chip
173173
CMD_ON(0), // Display OFF, sleep mode
174174
CMD_MUX_RATIO(0x3F), // Mux ratio
175175
CMD_DISP_OFFS(0), // Display offset
@@ -196,7 +196,7 @@ uint8_t u8g_dev_ssd1306_128x64_2x_2_wire_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t
196196
switch (msg) {
197197
case U8G_DEV_MSG_INIT:
198198
u8g_InitCom(u8g, dev, U8G_SPI_CLK_CYCLE_300NS);
199-
u8g_WriteEscSeqP_2_wire(u8g, dev, u8g_dev_ssd1306_128x64_init_seq_2_wire);
199+
u8g_Write_Init_Sequence_2_wire(u8g, dev, COUNT(u8g_dev_ssd1306_128x64_init_seq_2_wire), u8g_dev_ssd1306_128x64_init_seq_2_wire);
200200
break;
201201
case U8G_DEV_MSG_STOP: break;
202202
case U8G_DEV_MSG_PAGE_NEXT: {
@@ -214,7 +214,7 @@ uint8_t u8g_dev_ssd1306_128x64_2x_2_wire_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t
214214
u8g_WriteSequence(u8g, dev, pb->width, (uint8_t *)(pb->buf)+pb->width);
215215
u8g_SetChipSelect(u8g, dev, 0);
216216
} break;
217-
case U8G_DEV_MSG_SLEEP_ON: return 1;
217+
case U8G_DEV_MSG_SLEEP_ON:
218218
case U8G_DEV_MSG_SLEEP_OFF: return 1;
219219
}
220220
return u8g_dev_pb16v1_base_fn(u8g, dev, msg, arg);
@@ -283,4 +283,10 @@ uint8_t u8g_WriteEscSeqP_2_wire(u8g_t *u8g, u8g_dev_t *dev, const uint8_t *esc_s
283283
return 1;
284284
}
285285

286-
#endif // HAS_MARLINUI_U8GLIB
286+
uint8_t u8g_Write_Init_Sequence_2_wire(u8g_t *u8g, u8g_dev_t *dev, uint32_t length, const uint8_t *init_seq) {
287+
u8g_SetAddress(u8g, dev, 0); // Instruction mode
288+
u8g_WriteSequence(u8g, dev, length, (uint8_t*)init_seq);
289+
return 1;
290+
}
291+
292+
#endif // HAS_U8GLIB_I2C_OLED

0 commit comments

Comments
 (0)