juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

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, &reg, 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 }