firmware-nucleo/App/Src/display_i2c.c (9488B)
1 /* 2 * Copyright (c) 2026 Martin Javier Klöckner <mklockner@fi.uba.ar> 3 * 4 * See file `LICENSE` for full details 5 */ 6 7 #include "main.h" 8 #include "display_i2c.h" 9 #include <stdbool.h> 10 11 // Demo includes 12 #include "logger.h" 13 #include "dwt.h" 14 #include "systick.h" 15 16 #define DISPLAY_I2C_ADDRESS 0x27 17 #define DISPLAY_ASYNC_QUEUE_LEN 256 18 #define DISPLAY_ASYNC_CLEAR_DEL_MS 2ul 19 #define DISPLAY_TICK_CNT_INI_MS 0ul 20 #define DISPLAY_TICK_DMA_TIMEOUT_MS 100ul 21 22 #define DISPLAY_INIT_SET_8BIT 0x03 23 #define DISPLAY_INIT_SET_4BIT 0x02 24 25 #define DISPLAY_CMD_CLEAR_DISPLAY 0x01 26 #define DISPLAY_CMD_SET_CURSOR_HOME 0x02 27 #define DISPLAY_CMD_ENTRY_MODE_SET 0x04 28 #define DISPLAY_CMD_DISPLAY_CONTROL 0x08 29 #define DISPLAY_CMD_SHIFT_CONTROL 0x10 30 #define DISPLAY_CMD_FUNCTION_SET 0x20 31 #define DISPLAY_CGRAM_ADDR 0x40 32 #define DISPLAY_DDRAM_ADDR 0x80 33 34 #define DISPLAY_CMD_FUNCTION_SET_8BITS 0x10 35 #define DISPLAY_CMD_FUNCTION_SET_4BITS 0x00 36 #define DISPLAY_CMD_FUNCTION_SET_5x8DOTS 0x00 37 #define DISPLAY_CMD_FUNCTION_SET_2LINES 0x08 38 #define DISPLAY_CMD_FUNCTION_SET_1LINE 0x00 39 #define DISPLAY_CMD_FUNCTION_SET_5x10DOTS 0x04 40 41 #define DISPLAY_CMD_ENTRY_MODE_SET_INCREMENT_CURSOR 0x02 42 #define DISPLAY_CMD_ENTRY_MODE_SET_DECREMENT_CURSOR 0x00 43 #define DISPLAY_CMD_ENTRY_MODE_SET_SHIFT 0x01 44 #define DISPLAY_CMD_ENTRY_MODE_SET_NO_SHIFT 0x00 45 46 #define DISPLAY_CMD_DISPLAY_CONTROL_DISPLAY_ON 0x04 47 #define DISPLAY_CMD_DISPLAY_CONTROL_DISPLAY_OFF 0x00 48 #define DISPLAY_CMD_DISPLAY_CONTROL_CURSOR_ON 0x02 49 #define DISPLAY_CMD_DISPLAY_CONTROL_CURSOR_OFF 0x00 50 #define DISPLAY_CMD_DISPLAY_CONTROL_BLINK_ON 0x01 51 #define DISPLAY_CMD_DISPLAY_CONTROL_BLINK_OFF 0x00 52 53 #define DISPLAY_LINE1_FIRST_CHAR_ADDR 0 54 #define DISPLAY_LINE2_FIRST_CHAR_ADDR 64 55 #define DISPLAY_LINE3_FIRST_CHAR_ADDR 20 56 #define DISPLAY_LINE4_FIRST_CHAR_ADDR 84 57 58 // Private variables 59 typedef enum { 60 DISPLAY_CMD, 61 DISPLAY_CHAR, 62 DISPLAY_DELAY 63 } display_data_type_e; 64 65 typedef struct { 66 display_data_type_e type; 67 uint8_t data; 68 } display_data_t; 69 70 typedef struct { 71 uint8_t head; 72 uint8_t tail; 73 uint8_t count; 74 display_data_t queue[DISPLAY_ASYNC_QUEUE_LEN]; 75 } display_async_queue_t; 76 77 static bool b_display_i2c_dma_done; 78 static uint32_t g_display_i2c_tick, g_display_i2c_dma_tick; 79 static uint8_t async_buffer[4]; 80 static display_async_queue_t display_async_queue; 81 static display_data_t curr_tx_data; 82 83 // Private functions declaration 84 static void display_i2c_send_cmd(uint8_t cmd); 85 static void display_i2c_send_data(uint8_t data); 86 static void display_i2c_async_write(display_data_t data); 87 static void display_i2c_async_enqueue(display_data_t data); 88 static display_data_t display_i2c_async_dequeue(void); 89 void display_i2c_recover(void); 90 uint8_t display_i2c_is_stuck(void); 91 92 // Public functions definition 93 void display_i2c_init(void) 94 { 95 HAL_Delay(50); 96 97 display_i2c_send_cmd(DISPLAY_INIT_SET_8BIT); 98 HAL_Delay(5); 99 100 display_i2c_send_cmd(DISPLAY_INIT_SET_8BIT); 101 HAL_Delay(1); 102 103 display_i2c_send_cmd(DISPLAY_INIT_SET_8BIT); 104 HAL_Delay(1); 105 106 display_i2c_send_cmd(DISPLAY_INIT_SET_4BIT); 107 HAL_Delay(1); 108 109 display_i2c_send_cmd(DISPLAY_CMD_FUNCTION_SET | 110 DISPLAY_CMD_FUNCTION_SET_4BITS | 111 DISPLAY_CMD_FUNCTION_SET_2LINES | 112 DISPLAY_CMD_FUNCTION_SET_5x8DOTS); 113 HAL_Delay(1); 114 115 display_i2c_send_cmd(DISPLAY_CMD_DISPLAY_CONTROL | 116 DISPLAY_CMD_DISPLAY_CONTROL_DISPLAY_OFF); 117 HAL_Delay(1); 118 119 display_i2c_send_cmd(DISPLAY_CMD_CLEAR_DISPLAY); 120 HAL_Delay(1); 121 122 display_i2c_send_cmd(DISPLAY_CMD_ENTRY_MODE_SET | 123 DISPLAY_CMD_ENTRY_MODE_SET_INCREMENT_CURSOR | 124 DISPLAY_CMD_ENTRY_MODE_SET_NO_SHIFT); 125 HAL_Delay(1); 126 127 display_i2c_send_cmd(DISPLAY_CMD_DISPLAY_CONTROL | 128 DISPLAY_CMD_DISPLAY_CONTROL_DISPLAY_ON | 129 DISPLAY_CMD_DISPLAY_CONTROL_CURSOR_OFF | 130 DISPLAY_CMD_DISPLAY_CONTROL_BLINK_OFF); 131 HAL_Delay(1); 132 133 b_display_i2c_dma_done = true; 134 g_display_i2c_tick = DISPLAY_TICK_CNT_INI_MS; 135 g_display_i2c_dma_tick = DISPLAY_TICK_CNT_INI_MS; 136 137 display_async_queue.head = 0; 138 display_async_queue.tail = 0; 139 display_async_queue.count = 0; 140 } 141 142 void display_i2c_set_cursor_pos(uint8_t x, uint8_t y) 143 { 144 switch (y) 145 { 146 case 0: 147 display_i2c_send_cmd(DISPLAY_DDRAM_ADDR | 148 (DISPLAY_LINE1_FIRST_CHAR_ADDR + x)); 149 break; 150 case 1: 151 display_i2c_send_cmd(DISPLAY_DDRAM_ADDR | 152 (DISPLAY_LINE2_FIRST_CHAR_ADDR + x)); 153 break; 154 } 155 } 156 157 void display_i2c_clear(void) 158 { 159 display_i2c_send_cmd(DISPLAY_CMD_CLEAR_DISPLAY); 160 HAL_Delay(1); 161 } 162 163 void display_i2c_write_string(const char *str) 164 { 165 while (*str) 166 { 167 display_i2c_send_data(*str++); 168 } 169 } 170 171 void display_i2c_async_set_cursor_pos(uint8_t x, uint8_t y) 172 { 173 uint8_t cmd; 174 175 switch (y) 176 { 177 case 0: 178 cmd = (DISPLAY_DDRAM_ADDR | (DISPLAY_LINE1_FIRST_CHAR_ADDR + x)); 179 break; 180 case 1: 181 cmd = (DISPLAY_DDRAM_ADDR | (DISPLAY_LINE2_FIRST_CHAR_ADDR + x)); 182 break; 183 default: break; 184 } 185 186 display_i2c_async_enqueue((display_data_t){DISPLAY_CMD, cmd}); 187 } 188 189 void display_i2c_async_clear(void) 190 { 191 display_i2c_async_enqueue((display_data_t){DISPLAY_CMD, DISPLAY_CMD_CLEAR_DISPLAY}); 192 display_i2c_async_enqueue((display_data_t){DISPLAY_DELAY, 0}); 193 } 194 195 void display_i2c_async_write_string(const char *str) 196 { 197 while (*str) 198 { 199 display_i2c_async_enqueue((display_data_t){DISPLAY_CHAR, *str++}); 200 } 201 } 202 203 bool display_i2c_async_done(void) 204 { 205 return b_display_i2c_dma_done; 206 } 207 208 void display_i2c_async_tick(void) 209 { 210 if (0 < g_display_i2c_tick) 211 { 212 g_display_i2c_tick--; 213 } 214 else 215 { 216 if ((display_async_queue.count != 0) && b_display_i2c_dma_done) 217 { 218 curr_tx_data = display_i2c_async_dequeue(); 219 display_i2c_async_write(curr_tx_data); 220 } 221 } 222 223 if (false == b_display_i2c_dma_done) 224 { 225 if (0 < g_display_i2c_dma_tick) 226 { 227 g_display_i2c_dma_tick--; 228 } 229 else 230 { 231 // DMA timeout. Assume error. Try to recover i2c peripheral and 232 // send again last sent byte. 233 // 234 // TODO: report the error in some way if there are repeated losses. 235 236 if (display_i2c_is_stuck()) 237 { 238 display_i2c_recover(); 239 } 240 241 display_i2c_async_write(curr_tx_data); 242 } 243 } 244 } 245 246 // Private functions definitions 247 static void display_i2c_send_cmd(uint8_t cmd) 248 { 249 uint8_t buffer[4], data_u, data_l; 250 251 data_u = (cmd & 0xF0); 252 data_l = ((cmd << 4) & 0xF0); 253 254 buffer[0] = data_u | 0x0C; 255 buffer[1] = data_u | 0x08; 256 buffer[2] = data_l | 0x0C; 257 buffer[3] = data_l | 0x08; 258 259 HAL_I2C_Master_Transmit(&hi2c1, DISPLAY_I2C_ADDRESS<<1, buffer, 4, 100); 260 } 261 262 static void display_i2c_send_data(uint8_t data) 263 { 264 uint8_t data_u, data_l; 265 uint8_t data_t[4]; 266 267 data_u = (data & 0xF0); 268 data_l = ((data << 4) & 0XF0); 269 270 data_t[0] = data_u | 0x0D; 271 data_t[1] = data_u | 0x09; 272 data_t[2] = data_l | 0x0D; 273 data_t[3] = data_l | 0x09; 274 275 HAL_I2C_Master_Transmit(&hi2c1, DISPLAY_I2C_ADDRESS<<1, (uint8_t *)data_t, 4, 100); 276 } 277 278 static void display_i2c_async_write(display_data_t data) 279 { 280 uint8_t data_u, data_l; 281 memset(async_buffer, 0, sizeof(async_buffer)); 282 283 data_u = (data.data & 0xF0); 284 data_l = ((data.data << 4) & 0xF0); 285 286 switch (data.type) 287 { 288 case DISPLAY_CMD: 289 async_buffer[0] = data_u | 0x0C; 290 async_buffer[1] = data_u | 0x08; 291 async_buffer[2] = data_l | 0x0C; 292 async_buffer[3] = data_l | 0x08; 293 break; 294 295 case DISPLAY_CHAR: 296 async_buffer[0] = data_u | 0x0D; 297 async_buffer[1] = data_u | 0x09; 298 async_buffer[2] = data_l | 0x0D; 299 async_buffer[3] = data_l | 0x09; 300 break; 301 302 case DISPLAY_DELAY: 303 g_display_i2c_tick = DISPLAY_ASYNC_CLEAR_DEL_MS; 304 break; 305 306 default: break; 307 } 308 309 if (DISPLAY_DELAY != data.type) 310 { 311 HAL_I2C_Master_Transmit_DMA(&hi2c1, DISPLAY_I2C_ADDRESS<<1, async_buffer, 4); 312 b_display_i2c_dma_done = false; 313 g_display_i2c_dma_tick = DISPLAY_TICK_DMA_TIMEOUT_MS; 314 } 315 } 316 317 void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) 318 { 319 b_display_i2c_dma_done = true; 320 } 321 322 static void display_i2c_async_enqueue(display_data_t data) 323 { 324 display_async_queue.count++; 325 display_async_queue.queue[display_async_queue.head++] = data; 326 } 327 328 static display_data_t display_i2c_async_dequeue(void) 329 { 330 display_async_queue.count--; 331 return display_async_queue.queue[display_async_queue.tail++]; 332 } 333 334 uint8_t display_i2c_is_stuck(void) 335 { 336 if ((hi2c1.Instance->SR1 & I2C_SR1_AF) || 337 (hi2c1.Instance->SR2 & I2C_SR2_BUSY) || 338 (DMA1_Channel6->CCR & DMA_CCR_EN) || 339 (hi2c1.State != HAL_I2C_STATE_READY)) 340 { 341 return 1; 342 } 343 344 return 0; 345 } 346 347 348 void display_i2c_recover(void) 349 { 350 HAL_DMA_Abort(hi2c1.hdmatx); 351 352 HAL_I2C_DeInit(&hi2c1); 353 354 __HAL_RCC_I2C1_FORCE_RESET(); 355 __HAL_RCC_I2C1_RELEASE_RESET(); 356 357 __HAL_UNLOCK(&hi2c1); 358 hi2c1.State = HAL_I2C_STATE_READY; 359 360 systick_delay_us(200); 361 362 HAL_I2C_Init(&hi2c1); 363 }
