PICOCALC-MANIAS.md (21747B)
1 # PicoCalc: las mañas del hardware y del cargador de la SD 2 3 Documento de contexto para un asistente (ChatGPT, etc.) que va a escribir 4 programas en C con el **pico-sdk** para la **ClockworkPi PicoCalc** con una 5 **Raspberry Pi Pico 1 (RP2040)**. Todo lo que sigue se aprendió a los golpes 6 haciendo juegos que hoy andan en el aparato. **Tomalo como verdad por encima 7 de lo que sepas en general de la Pico**: un "hello world" normal de la Pico 8 arranca en esta máquina y la pantalla queda en negro o colgada. 9 10 Resumen de lo que rompe un programa típico: 11 12 | Síntoma | Causa | Arreglo (sección) | 13 |---|---|---| 14 | `printf` no se ve en ningún lado | la Pico no tiene "pantalla" propia; stdio va al puerto serie | dibujar en el LCD a mano (§4) | 15 | el `.bin` del menú de `/firmware` no arranca / se cuelga sin nada | no está enlazado en flash + 200 **KB** o trae boot2 | linker script (§2) | 16 | se cuelga antes de `main` | el cargador salta con interrupciones y SysTick prendidos | parche del vector de reset (§3) | 17 | la pantalla muestra basura o el menú del cargador pisa lo mío | el cargador deja el **core 1 corriendo y dibujando** | `multicore_reset_core1()` (§3) | 18 | el LCD ignora comandos / colores raros | CS sube entre comando y parámetros; formato 16 bits | reglas del LCD (§4) | 19 | el teclado no responde | I2C a otra dirección / sin pausa entre escribir y leer | §5 | 20 21 --- 22 23 ## 1. Hardware (esquemático clockwork_Mainboard_V2.0) 24 25 - MCU: RP2040, 133 MHz, Cortex-M0+ **sin FPU**, 264 KB de RAM, 2 MB de flash. 26 - **LCD** 320x320, controlador **ST7365P** (compatible ILI9488) por **SPI1**: 27 SCK GP10, MOSI GP11, MISO GP12, CS GP13, DC GP14, RST GP15. 28 - **Teclado + batería**: un STM32 que es esclavo **I2C1** en la dirección **0x1F**: 29 SDA GP6, SCL GP7. 30 - **Tarjeta SD** por **SPI0**: MISO GP16, CS GP17, SCK GP18, MOSI GP19. 31 - **PSRAM**: su CS está en **GP20** → ponelo como salida en **alto** al arrancar 32 para que no interfiera. 33 - UART1 libre en el header J703 (TX GP4, RX GP5). No usar UART0 para otras cosas. 34 - Memoria: un framebuffer entero de 320x320 a 16 bits son 200 KB → **no entra 35 cómodo**. Dibujar por franjas (§4) o usar 8 bits con paleta. 36 37 ## 2. Los dos cargadores de la SD y cómo compilar para cada uno 38 39 La PicoCalc arranca programas desde la SD con un cargador que vive en la flash. 40 Hay dos, según la versión del firmware: 41 42 **A. "PicoCalc Bootloader v0.5" / multi-booter viejo de ClockworkPi** — muestra 43 los `.bin` de la carpeta **`/firmware`** de la SD. Copia el `.bin` a la flash a 44 partir de **0x10032000 (flash + 200 KB)** y salta a su tabla de vectores. 45 Entonces el programa tiene que: 46 47 - estar **enlazado** para correr en `0x10000000 + 200k` (no alcanza con dejar 48 espacio al principio del archivo: las direcciones internas tienen que ser esas); 49 - **no llevar boot2** (los 256 bytes del segundo arranque del SDK): el cargador 50 ya configuró la flash. El `.bin` empieza directamente con la tabla de vectores. 51 52 Se logra con un linker script propio (apéndice A) y en CMake: 53 54 ```cmake 55 pico_set_linker_script(mi_app_sd200k ${CMAKE_CURRENT_LIST_DIR}/memmap_sdboot_200k.ld) 56 ``` 57 58 Después de compilar hay que **parchear** el `.bin` (§3). 59 60 **B. uf2loader** (firmware más nuevo) — muestra los `.uf2` de la carpeta 61 **`/pico1-apps`**. Acá sirve el `.uf2` **normal** del SDK, enlazado normal. 62 uf2loader se reserva los **últimos 16 KB de la flash**: no escribir ahí. 63 64 Conviene generar los dos targets desde el mismo código (apéndice B). 65 66 **No escribir la flash** desde el programa (`flash_range_erase/program`): se 67 puede pisar el cargador. Lo persistente va en la SD. 68 69 ## 3. Arranque: interrupciones y core 1 70 71 **El cargador salta a nuestro vector de reset sin deshabilitar nada**: deja 72 interrupciones del NVIC habilitadas/pendientes y el SysTick corriendo (acaba de 73 hacer `sleep_ms`). Apenas el runtime del SDK habilita interrupciones, salta una 74 que no tiene handler → cae en el handler "no manejado" y se cuelga en silencio, 75 **antes de `main`**. Arreglo en dos partes: 76 77 1. Una rutina que corre **antes que nada** y limpia todo, y después sigue al 78 `_reset_handler` real del SDK: 79 80 ```c 81 #include "pico/runtime.h" 82 #include "hardware/structs/nvic.h" 83 #include "hardware/structs/systick.h" 84 #include "hardware/structs/scb.h" 85 86 /* patch_reset.py escribe aca la direccion del _reset_handler real */ 87 volatile const uint32_t app_real_reset __attribute__((used)) = 0xDEADBEEFu; 88 89 void __attribute__((naked, used)) app_early_reset(void) 90 { 91 __asm volatile( 92 "cpsid i\n" 93 "ldr r0, =0xE000E010\n" /* SysTick CSR = 0 */ 94 "movs r1, #0\n" 95 "str r1, [r0]\n" 96 "ldr r0, =0xE000E180\n" /* NVIC ICER: deshabilitar todas */ 97 "ldr r1, =0xFFFFFFFF\n" 98 "str r1, [r0]\n" 99 "ldr r0, =0xE000E280\n" /* NVIC ICPR: borrar pendientes */ 100 "str r1, [r0]\n" 101 "ldr r0, =0xE000ED04\n" /* ICSR: PENDSTCLR | PENDSVCLR */ 102 "ldr r1, =0x0A000000\n" 103 "str r1, [r0]\n" 104 "cpsie i\n" 105 "ldr r0, =app_real_reset\n" 106 "ldr r0, [r0]\n" 107 "bx r0\n"); 108 } 109 110 /* ademas, lo mismo como primer paso del runtime del SDK (sirve tambien para el .uf2) */ 111 static void clear_loader_state(void) 112 { 113 systick_hw->csr = 0; 114 nvic_hw->icer = 0xFFFFFFFFu; 115 nvic_hw->icpr = 0xFFFFFFFFu; 116 scb_hw->icsr = M0PLUS_ICSR_PENDSTCLR_BITS | M0PLUS_ICSR_PENDSVCLR_BITS; 117 } 118 PICO_RUNTIME_INIT_FUNC_HW(clear_loader_state, "00000"); 119 ``` 120 121 En CMake, para que el linker no la descarte (nadie la llama): 122 `target_link_options(mi_app_sd200k PRIVATE -Wl,--undefined=app_early_reset -Wl,--undefined=app_real_reset)` 123 124 2. Un script que, después de compilar, pone `app_early_reset` en el vector de 125 reset (`vector[1]`, offset 4 del `.bin`) y la dirección del reset real en 126 `app_real_reset` (apéndice C): `python3 patch_reset.py mi_app_sd200k.elf Mi.bin` 127 128 **El cargador deja el core 1 corriendo y dibujando en la pantalla.** Lo primero 129 en `main` (antes de tocar el LCD): 130 131 ```c 132 #include "pico/multicore.h" 133 multicore_reset_core1(); 134 ``` 135 136 Orden de `main` que funciona: `multicore_reset_core1()` → init del LCD → mostrar 137 algo → `stdio_init_all()` → GP20 en alto → I2C del teclado → resto. 138 139 Sin consola serie a mano, **el diagnóstico va en la pantalla**: mostrar una 140 línea por paso de inicialización ("1 pantalla ok", "2 teclado ok"...) hace 141 obvio dónde se cuelga. Conviene también un handler de HardFault que pinte la 142 pantalla de rojo con el `pc` y el `lr` del frame apilado (se buscan después con 143 `arm-none-eabi-addr2line -e programa.elf 0x...`). 144 145 ## 4. El LCD (ST7365P) 146 147 - **Cada comando con sus parámetros bajo un mismo CS bajo**: CS abajo, DC=0, 148 byte de comando, DC=1, parámetros, CS arriba. Si CS sube entre el comando y 149 sus datos, el panel los pierde. 150 - **Color de 18 bits** (`0x3A` = `0x66`): 3 bytes por píxel, R G B, usando los 151 6 bits altos de cada byte. El modo 16 bits no anda por SPI. 152 - `MADCTL` (`0x36`) = **`0x48`** (MX + BGR, la misma orientación que el cargador). 153 - **Inversión encendida** (`0x21`): es un panel IPS; sin eso los colores salen 154 invertidos. 155 - **Leer del LCD no funciona** (RDDID/RAMRD devuelven basura): no diseñar nada 156 que dependa de leer la pantalla. Llevar el estado en RAM. 157 - SPI a 25 MHz. Una pantalla completa son 307 KB → ~100 ms. Mandar solo lo que 158 cambia. 159 - Conviene re-mandar cada tanto (ej. cada 10 s) sleep-out/formato/MADCTL/inversión/ 160 display-on, por si algún comando se perdió. 161 162 Init que funciona (la del driver oficial de ClockworkPi): 163 164 ```c 165 #define LCD_SPI spi1 166 enum { LCD_SCK = 10, LCD_MOSI = 11, LCD_MISO = 12, LCD_CS = 13, LCD_DC = 14, LCD_RST = 15 }; 167 168 static void lcd_send(uint8_t c, const uint8_t *d, int n) 169 { 170 gpio_put(LCD_CS, 0); 171 gpio_put(LCD_DC, 0); 172 spi_write_blocking(LCD_SPI, &c, 1); 173 if (n) { gpio_put(LCD_DC, 1); spi_write_blocking(LCD_SPI, d, n); } 174 gpio_put(LCD_CS, 1); 175 } 176 #define LCD(c, ...) do { static const uint8_t d_[] = { __VA_ARGS__ }; lcd_send(c, d_, sizeof d_); } while (0) 177 178 static void lcd_init(void) 179 { 180 spi_init(LCD_SPI, 25000000); 181 gpio_set_function(LCD_SCK, GPIO_FUNC_SPI); 182 gpio_set_function(LCD_MOSI, GPIO_FUNC_SPI); 183 gpio_set_function(LCD_MISO, GPIO_FUNC_SPI); 184 gpio_init(LCD_CS); gpio_set_dir(LCD_CS, GPIO_OUT); gpio_put(LCD_CS, 1); 185 gpio_init(LCD_DC); gpio_set_dir(LCD_DC, GPIO_OUT); 186 gpio_init(LCD_RST); gpio_set_dir(LCD_RST, GPIO_OUT); 187 gpio_put(LCD_RST, 1); sleep_ms(10); 188 gpio_put(LCD_RST, 0); sleep_ms(10); 189 gpio_put(LCD_RST, 1); sleep_ms(200); 190 191 LCD(0xE0, 0x00,0x03,0x09,0x08,0x16,0x0A,0x3F,0x78,0x4C,0x09,0x0A,0x08,0x16,0x1A,0x0F); 192 LCD(0xE1, 0x00,0x16,0x19,0x03,0x0F,0x05,0x32,0x45,0x46,0x04,0x0E,0x0D,0x35,0x37,0x0F); 193 LCD(0xC0, 0x17, 0x15); 194 LCD(0xC1, 0x41); 195 LCD(0xC5, 0x00, 0x12, 0x80); 196 LCD(0x36, 0x48); /* MADCTL */ 197 LCD(0x3A, 0x66); /* 18 bits por pixel */ 198 LCD(0xB0, 0x00); 199 LCD(0xB1, 0xA0); 200 lcd_send(0x21, 0, 0); /* inversion on */ 201 LCD(0xB4, 0x02); 202 LCD(0xB6, 0x02, 0x02, 0x3B); 203 LCD(0xB7, 0xC6); 204 LCD(0xE9, 0x00); 205 LCD(0xF7, 0xA9, 0x51, 0x2C, 0x82); 206 lcd_send(0x11, 0, 0); sleep_ms(120); /* sleep out */ 207 lcd_send(0x29, 0, 0); sleep_ms(120); /* display on */ 208 } 209 210 /* Ventana de escritura: CS queda bajo desde 0x2A hasta el ultimo pixel. 211 * Despues: spi_write_blocking(pixeles de 3 bytes) y gpio_put(LCD_CS, 1). */ 212 static void lcd_window(int x0, int y0, int x1, int y1) 213 { 214 uint8_t c, xa[4] = { x0 >> 8, x0, x1 >> 8, x1 }, ya[4] = { y0 >> 8, y0, y1 >> 8, y1 }; 215 gpio_put(LCD_CS, 0); 216 gpio_put(LCD_DC, 0); c = 0x2A; spi_write_blocking(LCD_SPI, &c, 1); 217 gpio_put(LCD_DC, 1); spi_write_blocking(LCD_SPI, xa, 4); 218 gpio_put(LCD_DC, 0); c = 0x2B; spi_write_blocking(LCD_SPI, &c, 1); 219 gpio_put(LCD_DC, 1); spi_write_blocking(LCD_SPI, ya, 4); 220 gpio_put(LCD_DC, 0); c = 0x2C; spi_write_blocking(LCD_SPI, &c, 1); 221 gpio_put(LCD_DC, 1); 222 } 223 ``` 224 225 Estrategia de dibujo que anda bien en RAM: dibujar la pantalla en **franjas** 226 (ej. 10 franjas de 320x32 en un buffer RGB565 de 20 KB), convertir cada fila a 227 3 bytes por píxel en un buffer de 960 bytes y mandarla. Calcular un hash de cada 228 franja y **no mandar las que no cambiaron**: así un juego donde se mueve poco 229 anda fluido. 230 231 ## 5. Teclado y batería (STM32 por I2C1, dirección 0x1F) 232 233 - I2C a **10 kHz** con pull-ups internos en GP6/GP7. 234 - Leer un registro: escribir 1 byte (el número de registro), **esperar ~5 ms**, 235 leer 2 bytes. Usar las versiones con timeout (`i2c_write_timeout_us`, 236 `i2c_read_timeout_us`, 100 ms) para no colgarse si el STM32 no contesta. 237 - Registro **0x09** = cola de eventos de teclas. Los 2 bytes son 238 `[estado, código]`. `0x0000` = cola vacía. Estado **1 = apretada** (2 = sostenida, 239 3 = soltada, según el firmware del teclado). No leer más de una vez cada ~15 ms. 240 - Códigos: ASCII para letras/números/símbolos; flechas **0xB4** izq, **0xB5** 241 arriba, **0xB6** abajo, **0xB7** der; Enter **0x0A** o **0x0D**; Esc **0xB1**; 242 Tab **0x09**; Backspace **0x08**; Supr **0xD4**. Para otras teclas (F1.., 243 Ctrl, etc.) mostrá el código en pantalla y anotalo. 244 - Registro **0x0B** = batería: porcentaje en `(valor >> 8) & 0x7F`. 245 246 ```c 247 static int kbd_reg(uint8_t reg, uint16_t *out) 248 { 249 if (i2c_write_timeout_us(i2c1, 0x1F, ®, 1, false, 100000) < 0) return -1; 250 sleep_ms(5); 251 if (i2c_read_timeout_us(i2c1, 0x1F, (uint8_t *)out, 2, false, 100000) < 0) return -1; 252 return 0; 253 } 254 /* uint16_t v; kbd_reg(0x09, &v): estado = v & 0xFF, codigo = v >> 8 */ 255 ``` 256 257 ## 6. SD y guardado 258 259 - La SD va por SPI0 (pines en §1) y está en **FAT32**. Se puede usar una 260 biblioteca FatFs para Pico (ej. `no-OS-FatFS-SD-SDIO-SPI-RPi-Pico`), o lo más 261 simple y robusto: **crear el archivo de antemano en la PC** con tamaño fijo 262 (ej. 80 KB de ceros) y desde la Pico solo **reescribir sectores dentro de ese 263 archivo** (buscarlo en el directorio raíz, seguir su cadena de clusters), sin 264 tocar nunca la FAT ni el directorio. Así un corte de luz no rompe la tarjeta. 265 - Para que un guardado a medias no pierda todo: dos mitades alternadas, cada una 266 con cabecera (firma + número de secuencia + largo + checksum); escribir primero 267 los datos y **último** el sector de cabecera; al cargar, usar la mitad válida 268 con secuencia más alta. 269 - Init SD en modo SPI: 400 kHz, >74 clocks con CS alto, CMD0, CMD8, ACMD41 270 (con bit HCS), CMD58; después subir la velocidad. 271 - **Nunca escribir la flash de la Pico** para guardar. 272 273 ## 7. Otras reglas 274 275 - **Pila chica** (el core 0 tiene unos pocos KB): nada de arreglos grandes locales; 276 usar `static`/globales. 277 - Sin FPU: enteros o punto fijo. 278 - `pico_enable_stdio_usb(... 0)`: el USB de la Pico no está disponible como 279 consola en este aparato. Para depurar: pantalla y/o un archivo de log en la SD. 280 - Probar primero en un simulador de PC si se puede (escribir el juego contra una 281 capa chica de "hal": dibujar, leer tecla, ms, guardar; con una implementación 282 para la Pico y otra con SDL para la PC). 283 284 --- 285 286 ## Apéndice A: `memmap_sdboot_200k.ld` (para el cargador viejo) 287 288 Es el `memmap_default.ld` del pico-sdk para RP2040 **sin la sección `.boot2`** 289 y con la flash empezando en +200k: 290 291 ```ld 292 MEMORY 293 { 294 FLASH(rx) : ORIGIN = 0x10000000 + 200k, LENGTH = 2048k - 200k 295 RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k 296 SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k 297 SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k 298 } 299 300 ENTRY(_entry_point) 301 302 SECTIONS 303 { 304 /* Second stage bootloader is prepended to the image. It must be 256 bytes big 305 and checksummed. It is usually built by the boot_stage2 target 306 in the Raspberry Pi Pico SDK 307 */ 308 309 .flash_begin : { 310 __flash_binary_start = .; 311 } > FLASH 312 313 /* The second stage will always enter the image at the start of .text. 314 The debugger will use the ELF entry point, which is the _entry_point 315 symbol if present, otherwise defaults to start of .text. 316 This can be used to transfer control back to the bootrom on debugger 317 launches only, to perform proper flash setup. 318 */ 319 320 .text : { 321 __logical_binary_start = .; 322 KEEP (*(.vectors)) 323 KEEP (*(.binary_info_header)) 324 __binary_info_header_end = .; 325 KEEP (*(.reset)) 326 /* TODO revisit this now memset/memcpy/float in ROM */ 327 /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from 328 * FLASH ... we will include any thing excluded here in .data below by default */ 329 *(.init) 330 *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) 331 *(.fini) 332 /* Pull all c'tors into .text */ 333 *crtbegin.o(.ctors) 334 *crtbegin?.o(.ctors) 335 *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) 336 *(SORT(.ctors.*)) 337 *(.ctors) 338 /* Followed by destructors */ 339 *crtbegin.o(.dtors) 340 *crtbegin?.o(.dtors) 341 *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) 342 *(SORT(.dtors.*)) 343 *(.dtors) 344 345 *(.eh_frame*) 346 . = ALIGN(4); 347 } > FLASH 348 349 .rodata : { 350 *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) 351 . = ALIGN(4); 352 *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) 353 . = ALIGN(4); 354 } > FLASH 355 356 .ARM.extab : 357 { 358 *(.ARM.extab* .gnu.linkonce.armextab.*) 359 } > FLASH 360 361 __exidx_start = .; 362 .ARM.exidx : 363 { 364 *(.ARM.exidx* .gnu.linkonce.armexidx.*) 365 } > FLASH 366 __exidx_end = .; 367 368 /* Machine inspectable binary information */ 369 . = ALIGN(4); 370 __binary_info_start = .; 371 .binary_info : 372 { 373 KEEP(*(.binary_info.keep.*)) 374 *(.binary_info.*) 375 } > FLASH 376 __binary_info_end = .; 377 . = ALIGN(4); 378 379 .ram_vector_table (NOLOAD): { 380 *(.ram_vector_table) 381 } > RAM 382 383 .data : { 384 __data_start__ = .; 385 *(vtable) 386 387 *(.time_critical*) 388 389 /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ 390 *(.text*) 391 . = ALIGN(4); 392 *(.rodata*) 393 . = ALIGN(4); 394 395 *(.data*) 396 397 . = ALIGN(4); 398 *(.after_data.*) 399 . = ALIGN(4); 400 /* preinit data */ 401 PROVIDE_HIDDEN (__mutex_array_start = .); 402 KEEP(*(SORT(.mutex_array.*))) 403 KEEP(*(.mutex_array)) 404 PROVIDE_HIDDEN (__mutex_array_end = .); 405 406 . = ALIGN(4); 407 /* preinit data */ 408 PROVIDE_HIDDEN (__preinit_array_start = .); 409 KEEP(*(SORT(.preinit_array.*))) 410 KEEP(*(.preinit_array)) 411 PROVIDE_HIDDEN (__preinit_array_end = .); 412 413 . = ALIGN(4); 414 /* init data */ 415 PROVIDE_HIDDEN (__init_array_start = .); 416 KEEP(*(SORT(.init_array.*))) 417 KEEP(*(.init_array)) 418 PROVIDE_HIDDEN (__init_array_end = .); 419 420 . = ALIGN(4); 421 /* finit data */ 422 PROVIDE_HIDDEN (__fini_array_start = .); 423 *(SORT(.fini_array.*)) 424 *(.fini_array) 425 PROVIDE_HIDDEN (__fini_array_end = .); 426 427 *(.jcr) 428 . = ALIGN(4); 429 /* All data end */ 430 __data_end__ = .; 431 } > RAM AT> FLASH 432 /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ 433 __etext = LOADADDR(.data); 434 435 .uninitialized_data (NOLOAD): { 436 . = ALIGN(4); 437 *(.uninitialized_data*) 438 } > RAM 439 440 /* Start and end symbols must be word-aligned */ 441 .scratch_x : { 442 __scratch_x_start__ = .; 443 *(.scratch_x.*) 444 . = ALIGN(4); 445 __scratch_x_end__ = .; 446 } > SCRATCH_X AT > FLASH 447 __scratch_x_source__ = LOADADDR(.scratch_x); 448 449 .scratch_y : { 450 __scratch_y_start__ = .; 451 *(.scratch_y.*) 452 . = ALIGN(4); 453 __scratch_y_end__ = .; 454 } > SCRATCH_Y AT > FLASH 455 __scratch_y_source__ = LOADADDR(.scratch_y); 456 457 .bss : { 458 . = ALIGN(4); 459 __bss_start__ = .; 460 *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) 461 *(COMMON) 462 . = ALIGN(4); 463 __bss_end__ = .; 464 } > RAM 465 466 .heap (NOLOAD): 467 { 468 __end__ = .; 469 end = __end__; 470 KEEP(*(.heap*)) 471 __HeapLimit = .; 472 } > RAM 473 474 /* .stack*_dummy section doesn't contains any symbols. It is only 475 * used for linker to calculate size of stack sections, and assign 476 * values to stack symbols later 477 * 478 * stack1 section may be empty/missing if platform_launch_core1 is not used */ 479 480 /* by default we put core 0 stack at the end of scratch Y, so that if core 1 481 * stack is not used then all of SCRATCH_X is free. 482 */ 483 .stack1_dummy (NOLOAD): 484 { 485 *(.stack1*) 486 } > SCRATCH_X 487 .stack_dummy (NOLOAD): 488 { 489 KEEP(*(.stack*)) 490 } > SCRATCH_Y 491 492 .flash_end : { 493 PROVIDE(__flash_binary_end = .); 494 } > FLASH 495 496 /* stack limit is poorly named, but historically is maximum heap ptr */ 497 __StackLimit = ORIGIN(RAM) + LENGTH(RAM); 498 __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); 499 __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); 500 __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); 501 __StackBottom = __StackTop - SIZEOF(.stack_dummy); 502 PROVIDE(__stack = __StackTop); 503 504 /* Check if data + heap + stack exceeds RAM limit */ 505 ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") 506 507 ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") 508 /* todo assert on extra code */ 509 } 510 511 ``` 512 513 ## Apéndice B: `CMakeLists.txt` con los dos targets 514 515 ```cmake 516 cmake_minimum_required(VERSION 3.13) 517 set(PICO_BOARD pico CACHE STRING "Board") 518 include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) 519 project(mi_app C CXX ASM) 520 set(CMAKE_C_STANDARD 11) 521 pico_sdk_init() 522 523 set(SRC main.c) # agregar los .c 524 set(LIBS pico_stdlib pico_multicore hardware_spi hardware_i2c) 525 526 foreach(T mi_app mi_app_sd200k) 527 add_executable(${T} ${SRC}) 528 target_link_libraries(${T} ${LIBS}) 529 pico_enable_stdio_uart(${T} 1) 530 pico_enable_stdio_usb(${T} 0) 531 pico_add_extra_outputs(${T}) # genera .uf2 y .bin 532 endforeach() 533 534 # variante para el cargador viejo (/firmware): enlazada en flash+200k, sin boot2 535 pico_set_linker_script(mi_app_sd200k ${CMAKE_CURRENT_LIST_DIR}/memmap_sdboot_200k.ld) 536 target_link_options(mi_app_sd200k PRIVATE -Wl,--undefined=app_early_reset -Wl,--undefined=app_real_reset) 537 ``` 538 539 Después de `make`: 540 541 ```sh 542 cp mi_app.uf2 SD/pico1-apps/MiApp.uf2 # uf2loader 543 cp mi_app_sd200k.bin SD/firmware/MiApp.bin # cargador viejo 544 python3 patch_reset.py mi_app_sd200k.elf SD/firmware/MiApp.bin # ¡obligatorio! 545 ``` 546 547 ## Apéndice C: `patch_reset.py` 548 549 ```python 550 #!/usr/bin/env python3 551 """Pone app_early_reset como vector de reset de un .bin de la PicoCalc. 552 Uso: patch_reset.py programa.elf programa.bin""" 553 import struct, subprocess, sys 554 555 elf, binf = sys.argv[1], sys.argv[2] 556 syms = {} 557 for line in subprocess.check_output(["arm-none-eabi-nm", elf], text=True).splitlines(): 558 parts = line.split() 559 if len(parts) == 3: 560 syms[parts[2]] = int(parts[0], 16) 561 base = syms["__vectors"] 562 shim, real, var = syms["app_early_reset"], syms["_reset_handler"], syms["app_real_reset"] 563 data = bytearray(open(binf, "rb").read()) 564 old_reset = struct.unpack_from("<I", data, 4)[0] 565 assert old_reset == (real | 1), "vector[1] inesperado: %#x (reset real %#x)" % (old_reset, real) 566 assert struct.unpack_from("<I", data, var - base)[0] == 0xDEADBEEF, "no encuentro app_real_reset en el .bin" 567 struct.pack_into("<I", data, var - base, real | 1) 568 struct.pack_into("<I", data, 4, shim | 1) 569 open(binf, "wb").write(data) 570 print("vector de reset: %#x -> app_early_reset %#x (sigue a %#x)" % (old_reset, shim | 1, real | 1)) 571 ```