hal_rp2040.c (15445B)
1 /* hal_rp2040.c - hardware real de la PicoCalc (Pico 1 / RP2040). 2 * 3 * Pines (esquematico clockwork_Mainboard_V2.0): 4 * LCD ST7365P por SPI1: SCK 10, MOSI 11, MISO 12, CS 13, DC 14, RST 15 5 * Teclado (STM32) por I2C1: SDA 6, SCL 7, direccion 0x1F 6 * Cable 1v1 por UART1 en el header J703: TX GP4 (pin 4), RX GP5 (pin 5), GND pin 8 7 * PSRAM: CS en GP20 -> se deja en alto para que no moleste en GP2-5/GP21 8 */ 9 #include <stdarg.h> 10 #include <string.h> 11 #include "pico/stdlib.h" 12 #include "pico/rand.h" 13 #include "hardware/spi.h" 14 #include "hardware/i2c.h" 15 #include "hardware/uart.h" 16 #include "hardware/irq.h" 17 #include "hardware/sync.h" 18 #include "hardware/structs/nvic.h" 19 #include "hardware/structs/systick.h" 20 #include "hardware/structs/scb.h" 21 #include "pico/runtime.h" 22 #include "hardware/structs/vreg_and_chip_reset.h" 23 #include "hardware/watchdog.h" 24 #include "hardware/structs/spi.h" 25 #include "pico/multicore.h" 26 #include "hal.h" 27 #include "sdlog.h" 28 #include "app_config.h" 29 30 #define LCD_SPI spi1 31 #define LCD_SCK 10 32 #define LCD_MOSI 11 33 #define LCD_MISO 12 34 #define LCD_CS 13 35 #define LCD_DC 14 36 #define LCD_RST 15 37 #define LCD_HZ 25000000 38 39 #define KBD_I2C i2c1 40 #define KBD_SDA 6 41 #define KBD_SCL 7 42 #define KBD_ADDR 0x1F 43 44 #define LINK_UART uart1 45 #define LINK_TX 4 46 #define LINK_RX 5 47 #define LINK_BAUD 115200 48 49 #define PSRAM_CS 20 50 51 #define BAND 32 52 53 /* ------------------------------------------------------------ pantalla */ 54 55 /* Comando + parametros con CS bajo todo el tiempo (esta pantalla pierde los 56 * parametros si CS sube entre el comando y sus datos). */ 57 static void lcd_send(uint8_t c, const uint8_t *d, int n) 58 { 59 gpio_put(LCD_CS, 0); 60 gpio_put(LCD_DC, 0); 61 spi_write_blocking(LCD_SPI, &c, 1); 62 if (n) { 63 gpio_put(LCD_DC, 1); 64 spi_write_blocking(LCD_SPI, d, (size_t)n); 65 } 66 gpio_put(LCD_CS, 1); 67 } 68 69 static void lcd_cmd(uint8_t c) { lcd_send(c, 0, 0); } 70 71 static void lcd_cmdn(uint8_t c, int n, ...) 72 { 73 uint8_t d[16]; 74 va_list ap; 75 va_start(ap, n); 76 for (int i = 0; i < n && i < 16; i++) d[i] = (uint8_t)va_arg(ap, int); 77 va_end(ap); 78 lcd_send(c, d, n); 79 } 80 81 /* Lo que define como se ve: formato de color, orientacion, inversion, encendido. 82 * Se reenvia periodicamente por si algun comando se perdio. */ 83 static void lcd_reconfig(void) 84 { 85 lcd_cmd(0x11); /* sleep out (repetirlo no molesta) */ 86 sleep_ms(5); 87 lcd_cmdn(0x3A, 1, 0x66); /* 18 bits por pixel */ 88 lcd_cmdn(0x36, 1, 0x48); /* MADCTL: MX, BGR (igual que el cargador) */ 89 lcd_cmd(0x21); /* inversion on (panel IPS) */ 90 lcd_cmd(0x29); /* display on */ 91 } 92 93 /* Secuencia de init del driver oficial de ClockworkPi (picocalc_helloworld/lcdspi). */ 94 static void lcd_init(void) 95 { 96 gpio_init(LCD_SCK); 97 gpio_init(LCD_MOSI); 98 gpio_init(LCD_MISO); 99 gpio_set_dir(LCD_SCK, GPIO_OUT); 100 gpio_set_dir(LCD_MOSI, GPIO_OUT); 101 spi_init(LCD_SPI, LCD_HZ); 102 gpio_set_function(LCD_SCK, GPIO_FUNC_SPI); 103 gpio_set_function(LCD_MOSI, GPIO_FUNC_SPI); 104 gpio_set_function(LCD_MISO, GPIO_FUNC_SPI); 105 gpio_set_input_hysteresis_enabled(LCD_MISO, true); 106 gpio_init(LCD_CS); gpio_set_dir(LCD_CS, GPIO_OUT); gpio_put(LCD_CS, 1); 107 gpio_init(LCD_DC); gpio_set_dir(LCD_DC, GPIO_OUT); 108 gpio_init(LCD_RST); gpio_set_dir(LCD_RST, GPIO_OUT); 109 gpio_put(LCD_RST, 1); sleep_ms(10); 110 gpio_put(LCD_RST, 0); sleep_ms(10); 111 gpio_put(LCD_RST, 1); sleep_ms(200); 112 113 lcd_cmdn(0xE0, 15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F); 114 lcd_cmdn(0xE1, 15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F); 115 lcd_cmdn(0xC0, 2, 0x17, 0x15); 116 lcd_cmdn(0xC1, 1, 0x41); 117 lcd_cmdn(0xC5, 3, 0x00, 0x12, 0x80); 118 lcd_cmdn(0x36, 1, 0x48); /* MADCTL: MX, BGR */ 119 lcd_cmdn(0x3A, 1, 0x66); /* 18 bits por pixel (el SPI no admite 16) */ 120 lcd_cmdn(0xB0, 1, 0x00); 121 lcd_cmdn(0xB1, 1, 0xA0); 122 lcd_cmd(0x21); /* inversion on (panel IPS) */ 123 lcd_cmdn(0xB4, 1, 0x02); 124 lcd_cmdn(0xB6, 3, 0x02, 0x02, 0x3B); 125 lcd_cmdn(0xB7, 1, 0xC6); 126 lcd_cmdn(0xE9, 1, 0x00); 127 lcd_cmdn(0xF7, 4, 0xA9, 0x51, 0x2C, 0x82); 128 lcd_cmd(0x11); sleep_ms(120); /* sleep out */ 129 lcd_cmd(0x29); sleep_ms(120); /* display on */ 130 lcd_reconfig(); 131 } 132 133 /* Igual que el driver oficial (define_region_spi): CS queda bajo desde CASET hasta 134 * el ultimo pixel; los parametros van con DC alto dentro de la misma seleccion. 135 * Deja CS bajo y DC alto, listo para mandar pixeles (o leer, si rw == 0). */ 136 static void lcd_region(int x0, int y0, int x1, int y1, bool rw) 137 { 138 uint8_t c; 139 uint8_t xa[4] = { (uint8_t)(x0 >> 8), (uint8_t)x0, (uint8_t)(x1 >> 8), (uint8_t)x1 }; 140 uint8_t ya[4] = { (uint8_t)(y0 >> 8), (uint8_t)y0, (uint8_t)(y1 >> 8), (uint8_t)y1 }; 141 gpio_put(LCD_CS, 0); 142 gpio_put(LCD_DC, 0); c = 0x2A; spi_write_blocking(LCD_SPI, &c, 1); 143 gpio_put(LCD_DC, 1); spi_write_blocking(LCD_SPI, xa, 4); 144 gpio_put(LCD_DC, 0); c = 0x2B; spi_write_blocking(LCD_SPI, &c, 1); 145 gpio_put(LCD_DC, 1); spi_write_blocking(LCD_SPI, ya, 4); 146 gpio_put(LCD_DC, 0); c = rw ? 0x2C : 0x2E; spi_write_blocking(LCD_SPI, &c, 1); 147 gpio_put(LCD_DC, 1); 148 } 149 150 151 static uint16_t band[320 * BAND]; 152 static int band_y0; 153 static uint32_t band_hash[320 / BAND]; 154 static bool first_frame = true; 155 static uint32_t last_full_ms; 156 #define FULL_REFRESH_MS 10000 /* auto-reparacion: reconfigura y repinta todo cada tanto */ 157 158 static void span_band(Surface *s, int x, int y, int n, Color c) 159 { 160 (void)s; 161 uint16_t *p = band + (y - band_y0) * 320 + x; 162 while (n--) *p++ = c; 163 } 164 165 void hal_draw(void (*paint)(Surface *, void *), void *ctx) 166 { 167 static uint8_t line[320 * 3]; 168 uint32_t now = to_ms_since_boot(get_absolute_time()); 169 if (!first_frame && now - last_full_ms > FULL_REFRESH_MS) first_frame = true; 170 if (first_frame) { lcd_reconfig(); last_full_ms = now; } 171 Surface s; 172 surf_init(&s, 320, 320, span_band, 0); 173 for (int b = 0; b < 320 / BAND; b++) { 174 band_y0 = b * BAND; 175 gfx_band(&s, band_y0, band_y0 + BAND); 176 paint(&s, ctx); 177 /* solo mandamos por SPI las franjas que cambiaron */ 178 uint32_t h = 2166136261u; 179 const uint32_t *w = (const uint32_t *)band; 180 for (int i = 0; i < 320 * BAND / 2; i++) { h ^= w[i]; h *= 16777619u; } 181 if (!first_frame && h == band_hash[b]) continue; 182 band_hash[b] = h; 183 lcd_region(0, band_y0, 319, band_y0 + BAND - 1, true); 184 for (int y = 0; y < BAND; y++) { 185 const uint16_t *src = band + y * 320; 186 for (int x = 0; x < 320; x++) { 187 uint16_t c = src[x]; 188 line[3 * x] = (uint8_t)((c >> 8) & 0xF8); 189 line[3 * x + 1] = (uint8_t)((c >> 3) & 0xFC); 190 line[3 * x + 2] = (uint8_t)(c << 3); 191 } 192 spi_write_blocking(LCD_SPI, line, sizeof line); 193 } 194 gpio_put(LCD_CS, 1); 195 } 196 first_frame = false; 197 } 198 199 /* ------------------------------------------------------------ teclado */ 200 201 static int kbd_reg(uint8_t reg, uint16_t *out) 202 { 203 if (i2c_write_timeout_us(KBD_I2C, KBD_ADDR, ®, 1, false, 100000) < 0) return -1; 204 sleep_ms(5); 205 if (i2c_read_timeout_us(KBD_I2C, KBD_ADDR, (uint8_t *)out, 2, false, 100000) < 0) return -1; 206 return 0; 207 } 208 209 int hal_key(void) 210 { 211 static uint32_t last; 212 uint32_t now = to_ms_since_boot(get_absolute_time()); 213 if (now - last < 15) return -1; 214 last = now; 215 if (now - last_full_ms > FULL_REFRESH_MS) return 0; /* 0 = "redibujar", la app lo ignora como tecla */ 216 uint16_t v = 0; 217 if (kbd_reg(0x09, &v) < 0 || !v) return -1; 218 int state = v & 0xFF, code = v >> 8; 219 if (state != 1) return -1; /* solo "apretada" */ 220 switch (code) { 221 case 0xB5: return K_UP; 222 case 0xB6: return K_DOWN; 223 case 0xB4: return K_LEFT; 224 case 0xB7: return K_RIGHT; 225 case 0x0A: case 0x0D: return K_OK; 226 case 0xB1: return K_BACK; 227 case 0x09: return K_TAB; 228 case 0x08: case 0xD4: return K_BKSP; 229 } 230 if (code >= 32 && code < 127) return code; 231 return -1; 232 } 233 234 int hal_battery(void) 235 { 236 uint16_t v = 0; 237 if (kbd_reg(0x0B, &v) < 0) return -1; 238 return (v >> 8) & 0x7F; 239 } 240 241 /* ------------------------------------------------------------ cable (UART1 con interrupcion) */ 242 243 static volatile uint8_t rx_buf[4096]; 244 static volatile uint16_t rx_head, rx_tail; 245 246 static void on_uart_rx(void) 247 { 248 while (uart_is_readable(LINK_UART)) { 249 uint8_t c = (uint8_t)uart_getc(LINK_UART); 250 uint16_t next = (uint16_t)((rx_head + 1) % sizeof rx_buf); 251 if (next != rx_tail) { rx_buf[rx_head] = c; rx_head = next; } 252 } 253 } 254 255 void hal_link_write(const char *buf, int n) { uart_write_blocking(LINK_UART, (const uint8_t *)buf, (size_t)n); } 256 257 int hal_link_read(char *buf, int max) 258 { 259 int n = 0; 260 while (n < max && rx_tail != rx_head) { 261 buf[n++] = (char)rx_buf[rx_tail]; 262 rx_tail = (uint16_t)((rx_tail + 1) % sizeof rx_buf); 263 } 264 return n; 265 } 266 267 /* ------------------------------------------------------------ guardado en la SD */ 268 269 /* La partida va en APP_SD_SAV de la tarjeta (ver sdlog.c). La flash de la Pico 270 * no se escribe nunca. */ 271 bool hal_store_save(const char *data, int n) 272 { 273 if (sdsave_save(data, n)) return true; 274 sdlog(sdsave_available() ? "guardado: error escribiendo " APP_SD_SAV : "guardado: no hay " APP_SD_SAV " en la SD"); 275 return false; 276 } 277 278 int hal_store_load(char *buf, int max) { return sdsave_load(buf, max); } 279 280 /* ------------------------------------------------------------ arranque y diagnostico */ 281 282 /* El cargador de la SD salta a nuestro programa con su estado encima: puede dejar 283 * interrupciones habilitadas/pendientes y el SysTick corriendo. Si alguna salta antes 284 * de que instalemos los handlers, cae en el handler "no manejado" y se cuelga en 285 * silencio. Lo limpiamos antes que cualquier otra inicializacion. */ 286 static void clear_loader_state(void) 287 { 288 systick_hw->csr = 0; 289 nvic_hw->icer = 0xFFFFFFFFu; 290 nvic_hw->icpr = 0xFFFFFFFFu; 291 scb_hw->icsr = M0PLUS_ICSR_PENDSTCLR_BITS | M0PLUS_ICSR_PENDSVCLR_BITS; 292 } 293 PICO_RUNTIME_INIT_FUNC_HW(clear_loader_state, "00000"); 294 295 /* Primera instruccion del programa. El cargador de la SD salta al vector de reset 296 * sin deshabilitar nada; esto limpia interrupciones y SysTick ANTES del arranque 297 * del SDK y despues sigue al _reset_handler real. tools/patch_reset.py pone la 298 * direccion de esta funcion en vector[1] del .bin y la del reset real en 299 * comun_real_reset. */ 300 volatile const uint32_t comun_real_reset __attribute__((used)) = 0xDEADBEEFu; 301 302 void __attribute__((naked, used)) comun_early_reset(void) 303 { 304 __asm volatile( 305 "cpsid i\n" 306 "ldr r0, =0xE000E010\n" /* SysTick CSR */ 307 "movs r1, #0\n" 308 "str r1, [r0]\n" 309 "ldr r0, =0xE000E180\n" /* NVIC ICER */ 310 "ldr r1, =0xFFFFFFFF\n" 311 "str r1, [r0]\n" 312 "ldr r0, =0xE000E280\n" /* NVIC ICPR */ 313 "str r1, [r0]\n" 314 "ldr r0, =0xE000ED04\n" /* ICSR: PENDSTCLR | PENDSVCLR */ 315 "ldr r1, =0x0A000000\n" 316 "str r1, [r0]\n" 317 "cpsie i\n" 318 "ldr r0, =comun_real_reset\n" 319 "ldr r0, [r0]\n" 320 "bx r0\n"); 321 } 322 323 /* Log de arranque en la pantalla (no hay cable serie): una linea por paso. */ 324 static char blog[22][54]; 325 static int nblog; 326 static bool lcd_ready; 327 328 static void paint_blog(Surface *s, void *ctx) 329 { 330 Color bg = ctx ? RGB(150, 0, 0) : RGB(0, 0, 60); 331 gfx_rect(s, 0, 0, 320, 320, bg); 332 for (int i = 0; i < nblog; i++) gfx_text(s, &font_normal, 4, 4 + i * 14, blog[i], C_WHITE); 333 } 334 335 void boot_log(const char *msg); 336 void boot_log(const char *msg) 337 { 338 sdlog(msg); 339 if (nblog == 22) { memmove(blog[0], blog[1], sizeof blog[0] * 21); nblog--; } 340 str_put(blog[nblog++], sizeof blog[0], 0, msg); 341 if (lcd_ready) { first_frame = true; hal_draw(paint_blog, 0); } 342 } 343 344 void hal_log(const char *line) { boot_log(line); } 345 void hal_log_clear(void) { nblog = 0; } 346 347 static void log_hex(const char *label, uint32_t v) 348 { 349 static const char HX[] = "0123456789abcdef"; 350 char buf[54]; 351 int p = str_put(buf, sizeof buf, 0, label); 352 p = str_put(buf, sizeof buf, p, "0x"); 353 for (int i = 7; i >= 0; i--) buf[p++] = HX[(v >> (4 * i)) & 15]; 354 buf[p] = 0; 355 boot_log(buf); 356 } 357 358 /* HardFault: pantalla roja con la direccion que fallo (se busca con arm-none-eabi-addr2line). */ 359 void __attribute__((used)) hardfault_c(uint32_t *frame) 360 { 361 if (!lcd_ready && !sdlog_ready()) for (;;) tight_loop_contents(); 362 boot_log(""); 363 boot_log("*** CRASH (HardFault) ***"); 364 log_hex("pc ", frame[6]); 365 log_hex("lr ", frame[5]); 366 log_hex("xpsr ", frame[7]); 367 boot_log("Anota estos numeros y pasaselos a quien te ayude"); 368 if (lcd_ready) { first_frame = true; hal_draw(paint_blog, (void *)1); } 369 for (;;) tight_loop_contents(); 370 } 371 372 void __attribute__((naked)) isr_hardfault(void) 373 { 374 __asm volatile( 375 "movs r0, #4\n" 376 "mov r1, lr\n" 377 "tst r0, r1\n" 378 "beq 1f\n" 379 "mrs r0, psp\n" 380 "b 2f\n" 381 "1: mrs r0, msp\n" 382 "2: ldr r1, =hardfault_c\n" 383 "bx r1\n"); 384 } 385 386 /* ------------------------------------------------------------ varios */ 387 388 void hal_init(void) 389 { 390 multicore_reset_core1(); /* por si el cargador dejo algo corriendo en el core 1 */ 391 bool sd = sdlog_open(); 392 sdlog(""); 393 sdlog("=== " APP_TITLE " PicoCalc - build " __DATE__ " " __TIME__ " ==="); 394 log_hex("reset chip ", vreg_and_chip_reset_hw->chip_reset); 395 sdlog(watchdog_caused_reboot() ? "reinicio por watchdog" : "arranque normal"); 396 sdlog("core1 reseteado"); 397 lcd_init(); 398 sdlog("1a init de pantalla enviado"); 399 400 lcd_ready = true; 401 boot_log(APP_TITLE " PicoCalc - build " __DATE__ " " __TIME__); 402 boot_log(sd ? "0 log en SD: " APP_SD_LOG " ok" : "0 log en SD: no (falta " APP_SD_LOG "?)"); 403 boot_log("1 pantalla ok"); 404 405 stdio_init_all(); 406 boot_log("2 consola uart0 ok"); 407 gpio_init(PSRAM_CS); 408 gpio_set_dir(PSRAM_CS, GPIO_OUT); 409 gpio_put(PSRAM_CS, 1); 410 boot_log("3 psram cs alto ok"); 411 412 i2c_init(KBD_I2C, 10000); 413 gpio_set_function(KBD_SDA, GPIO_FUNC_I2C); 414 gpio_set_function(KBD_SCL, GPIO_FUNC_I2C); 415 gpio_pull_up(KBD_SDA); 416 gpio_pull_up(KBD_SCL); 417 { 418 uint16_t v = 0; 419 int r = kbd_reg(0x0B, &v); 420 boot_log(r < 0 ? "4 teclado: SIN RESPUESTA (i2c)" : "4 teclado ok"); 421 if (r >= 0) log_hex(" bateria ", v); 422 } 423 424 uart_init(LINK_UART, LINK_BAUD); 425 gpio_set_function(LINK_TX, GPIO_FUNC_UART); 426 gpio_set_function(LINK_RX, GPIO_FUNC_UART); 427 gpio_pull_up(LINK_RX); /* cable desenchufado: linea en reposo, sin basura */ 428 uart_set_fifo_enabled(LINK_UART, true); 429 irq_set_exclusive_handler(UART1_IRQ, on_uart_rx); 430 irq_set_enabled(UART1_IRQ, true); 431 uart_set_irq_enables(LINK_UART, true, false); 432 boot_log("5 cable uart1 (gp4/gp5) ok"); 433 boot_log(sdsave_available() ? "6 guardado en SD: " APP_SD_SAV " ok" : "6 guardado en SD: no (falta " APP_SD_SAV "?)"); 434 boot_log("7 arrancando el menu..."); 435 } 436 437 uint32_t hal_ms(void) { return to_ms_since_boot(get_absolute_time()); } 438 void hal_sleep(int ms) { sleep_ms((uint32_t)ms); } 439 uint32_t hal_random(void) { return get_rand_32(); } 440 441 int main(void) 442 { 443 app_main(); 444 return 0; 445 }