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