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