From 4c74e3b3fec4feb9a7cb5f01914965fc1623120e Mon Sep 17 00:00:00 2001 From: cuu Date: Mon, 19 May 2025 14:41:19 +0800 Subject: [PATCH] add pico_multi_booter code --- .gitignore | 5 + .gitmodules | 3 + Code/pico_multi_booter/CMakeLists.txt | 104 +++ Code/pico_multi_booter/README.md | 87 ++ Code/pico_multi_booter/applink.py | 630 +++++++++++++++ Code/pico_multi_booter/boot.c | 196 +++++ .../memmap_default_rp2040.ld | 219 +++++ .../memmap_default_rp2350.ld | 302 +++++++ Code/pico_multi_booter/sd_boot/CMakeLists.txt | 83 ++ Code/pico_multi_booter/sd_boot/config.h | 45 ++ Code/pico_multi_booter/sd_boot/debug.h | 11 + .../sd_boot/i2ckbd/CMakeLists.txt | 21 + .../pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c | 85 ++ .../pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h | 20 + Code/pico_multi_booter/sd_boot/key_event.c | 143 ++++ Code/pico_multi_booter/sd_boot/key_event.h | 21 + .../sd_boot/lcdspi/CMakeLists.txt | 20 + .../sd_boot/lcdspi/fonts/font1.h | 233 ++++++ .../pico_multi_booter/sd_boot/lcdspi/lcdspi.c | 754 ++++++++++++++++++ .../pico_multi_booter/sd_boot/lcdspi/lcdspi.h | 148 ++++ Code/pico_multi_booter/sd_boot/main.c | 332 ++++++++ .../sd_boot/text_directory_ui.c | 516 ++++++++++++ .../sd_boot/text_directory_ui.h | 28 + 23 files changed, 4006 insertions(+) create mode 100644 .gitignore create mode 100644 Code/pico_multi_booter/CMakeLists.txt create mode 100644 Code/pico_multi_booter/README.md create mode 100644 Code/pico_multi_booter/applink.py create mode 100644 Code/pico_multi_booter/boot.c create mode 100644 Code/pico_multi_booter/memmap_default_rp2040.ld create mode 100644 Code/pico_multi_booter/memmap_default_rp2350.ld create mode 100644 Code/pico_multi_booter/sd_boot/CMakeLists.txt create mode 100644 Code/pico_multi_booter/sd_boot/config.h create mode 100644 Code/pico_multi_booter/sd_boot/debug.h create mode 100644 Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt create mode 100644 Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c create mode 100644 Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h create mode 100644 Code/pico_multi_booter/sd_boot/key_event.c create mode 100644 Code/pico_multi_booter/sd_boot/key_event.h create mode 100644 Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt create mode 100644 Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h create mode 100644 Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c create mode 100644 Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h create mode 100644 Code/pico_multi_booter/sd_boot/main.c create mode 100644 Code/pico_multi_booter/sd_boot/text_directory_ui.c create mode 100644 Code/pico_multi_booter/sd_boot/text_directory_ui.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17a234f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +cmake-build-* + +.idea + diff --git a/.gitmodules b/.gitmodules index bf09f66..0b9ea1c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "Bin/PicoMite/PicoMiteAllVersions"] path = Bin/PicoMite/PicoMiteAllVersions url = https://github.com/madcock/PicoMiteAllVersions.git +[submodule "Code/pico_multi_booter/sd_boot/lib/pico-vfs"] + path = Code/pico_multi_booter/sd_boot/lib/pico-vfs + url = https://github.com/oyama/pico-vfs.git diff --git a/Code/pico_multi_booter/CMakeLists.txt b/Code/pico_multi_booter/CMakeLists.txt new file mode 100644 index 0000000..492e286 --- /dev/null +++ b/Code/pico_multi_booter/CMakeLists.txt @@ -0,0 +1,104 @@ + +# .-------------------------------------------------------------------------. +# | MIT Licensed : Copyright 2022, "Hippy" | +# `-------------------------------------------------------------------------' + +# *************************************************************************** +# * This project - Build a combined booter and some apps * +# *************************************************************************** + +set(PROJECT pico_multi_booter) +set(BOOT boot) +set(OUTPUT combined.uf2) + +# .-------------------------------------------------------------------------. +# | The usual CMake prerequisites | +# `-------------------------------------------------------------------------' + +cmake_minimum_required(VERSION 3.12) +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) +project(${PROJECT} C CXX ASM) + +pico_sdk_init() + + +# .-------------------------------------------------------------------------. +# | The other things we need to complete the build | +# `-------------------------------------------------------------------------' + +find_package(Python3 REQUIRED COMPONENTS Interpreter) + + +add_subdirectory(picomite) +add_subdirectory(sd_boot) + +### build boot.uf2 +add_executable(${BOOT} boot.c) +target_link_libraries(${BOOT} pico_stdlib + hardware_pio +) + +target_compile_options(${BOOT} PRIVATE + -Os + -Wall + -Werror + -Wno-unused-variable + -Wno-unused-function +) +pico_enable_stdio_usb(${BOOT} 0) +pico_enable_stdio_uart(${BOOT} 1) +pico_add_uf2_output(${BOOT}) + +target_link_libraries(${BOOT} + hardware_resets + cmsis_core +) +pico_set_binary_type(${BOOT} copy_to_ram) + +add_custom_target(BUILT_${BOOT} + COMMENT "Record Build Details for '${BOOT}'" + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${BOOT}.uf2 + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/applink.py BUILT + ${CMAKE_CURRENT_BINARY_DIR} + ${BOOT} +) + + +# *************************************************************************** +# * Build the BOOT plus all the APPS * +# *************************************************************************** + + +add_dependencies(BUILT_boot boot) + +add_dependencies(PREPARE_picomite BUILT_boot) +add_dependencies(picomite PREPARE_picomite) +add_dependencies(BUILT_picomite picomite) + +add_dependencies(PREPARE_sd_boot BUILT_picomite) +add_dependencies(sd_boot PREPARE_sd_boot) +add_dependencies(BUILT_sd_boot sd_boot) + + +# *************************************************************************** +# * Join the BOOT and all APP '.uf2' files together * +# *************************************************************************** +set(UF2S boot.uf2 picomite.uf2 sd_boot.uf2) + +add_custom_target(JOIN + COMMENT "Combine the '.uf2' files" + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/applink.py JOIN + ${CMAKE_CURRENT_BINARY_DIR} + ${OUTPUT} + ${UF2S} +) + +add_dependencies(JOIN BUILT_sd_boot) + +add_custom_target(${PROJECT} ALL DEPENDS JOIN) + +# *************************************************************************** +# * Finished * +# *************************************************************************** diff --git a/Code/pico_multi_booter/README.md b/Code/pico_multi_booter/README.md new file mode 100644 index 0000000..2e8ea11 --- /dev/null +++ b/Code/pico_multi_booter/README.md @@ -0,0 +1,87 @@ +# PicoCalc multi booter + +Here is a bootloader for PicoCalc combined slightly modified [PicoMite](https://github.com/madcock/PicoMiteAllVersions) and [SD boot](https://github.com/adwuard/Picocalc_SD_Boot) + +- Pico1 +- No sdcard inserted ,PicoMite will show up. +- Sdcard inserted, SD boot menu will show up, load third pico app bin to run at FLASH TARGET OFFSET 2048k-940k + +## How to compile +``` +export PICO_SDK_PATH=/where/picosdk/is +mkdir build +cd build +cmake .. +make +``` +## How to run + +Just copy **combined.uf2** into PicoCalc + + +## PicoMite +configuration.h +``` +#define FLASH_TARGET_OFFSET (920 * 1024) +``` + +## sd_boot + +config.h +``` +#define SD_BOOT_FLASH_OFFSET (940 * 1024) +``` + +### SD Card Application Build and Deployment +**Important Note:** +``` +Applications intended for SD card boot "MUST REBUILD" using a custom linker script to accommodate the program's offset(940k) address. + +Applications intended for SD card boot is in **bin** format, not uf2. + +``` + +--- +This section explains how to build and deploy applications on an SD card. Below is a simple example of a CMakeLists.txt for an application. + + +#### Step 1 Copy Custom Link Script +Copy `memmap_sdcard_app.ld` to your project repository. + + +#### Step 2 Add Custom Link Script to CMakeList.txt +In the project for multi boot, add custom link script to CMakeList.txt,usually at bottom. +We also need to use `pico_add_extra_outputs` to sure that there will .bin file generated. + +``` +... +pico_add_extra_outputs(${CMAKE_PROJECT_NAME}) +... + +function(enable_sdcard_app target) + #pico_set_linker_script(${target} ${CMAKE_SOURCE_DIR}/memmap_sdcard_app.ld) + if(${PICO_PLATFORM} STREQUAL "rp2040") + pico_set_linker_script(${CMAKE_PROJECT_NAME} ${CMAKE_SOURCE_DIR}/memmap_default_rp2040.ld) + elseif(${PICO_PLATFORM} MATCHES "rp2350") + pico_set_linker_script(${CMAKE_PROJECT_NAME} ${CMAKE_SOURCE_DIR}/memmap_default_rp2350.ld) + endif() +endfunction() + +enable_sdcard_app(${CMAKE_PROJECT_NAME}) +``` +#### Build and Deployment Process +1. Build the project using the above CMakeLists.txt. + +```bash +mkdir build; +cd build +export PICO_SDK_PATH=/path/to/pico-sdk +cmake .. +make +``` + +#### Step 3 Your Custom Application Is Ready For SD Card Boot +Once the build is complete, copy the generated `.bin` file to the `/sd` directory of the SD card. + + + diff --git a/Code/pico_multi_booter/applink.py b/Code/pico_multi_booter/applink.py new file mode 100644 index 0000000..07d66ee --- /dev/null +++ b/Code/pico_multi_booter/applink.py @@ -0,0 +1,630 @@ +#!/usr/bin/python3 + +# **************************************************************************** +# * * +# * App linking Tool * +# * * +# **************************************************************************** + +PRODUCT = "App Linking Tool" +PACKAGE = "uf2" +PROGRAM = "applink.py" +VERSION = "0.01" +CHANGES = "0000" +TOUCHED = "2022-03-06 18:42:21" +LICENSE = "MIT Licensed" +DETAILS = "https://opensource.org/licenses/MIT" + +# .--------------------------------------------------------------------------. +# | MIT Licence | +# `--------------------------------------------------------------------------' + +# Copyright 2022, "Hippy" + +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# .--------------------------------------------------------------------------. +# | Satandard libraries | +# `--------------------------------------------------------------------------' + +import os +import sys; python3 = sys.version_info[0] == 3 +import time; today = time.strftime("%Y-%m-%d %H:%M:%S") + +# .--------------------------------------------------------------------------. +# | Configuration | +# `--------------------------------------------------------------------------' + +SHOW_INVOCATIONS = True +LOG_INVOCATIONS = True +IGNORE_ERRORS = False +REMOVE_STAGE_2_BOOLOADER = True +EMIT_INDIVIDUAL_MAP_FILES = False + +# .--------------------------------------------------------------------------. +# | Utility code | +# `--------------------------------------------------------------------------' + +def Pad(s, w, c=" "): + if len(s) < w: + s += c * (w-len(s)) + return s + +# .--------------------------------------------------------------------------. +# | UF2 Read Handling | +# `--------------------------------------------------------------------------' + +def Uf2Block(f): + if python3: + b = f.read(512) + else: + s = f.read(512) + b = bytearray() + for c in s: + b.append(ord(c)) + return b + +def Expand(b): + def Field(b, n, w=4): + val = 0 + shf = 0 + for count in range(w): + val = val | (b[n + count] << shf) + shf = shf + 8 + return val, n + w + def Bytes(b, n, w): + return b[n:n+w], n + w + n = 0 + head, n = Field(b, n, 8) + flags, n = Field(b, n) + adr, n = Field(b, n) + length, n = Field(b, n) + seq, n = Field(b, n) + tot, n = Field(b, n) + family, n = Field(b, n) + data, n = Bytes(b, n, 476) + tail, n = Field(b, n) + return head, flags, adr, length, seq, tot, family, data, tail + +# .--------------------------------------------------------------------------. +# | UF2 Write Handling | +# `--------------------------------------------------------------------------' + +def Encode(fDst, head, flags, adr, length, seq, tot, family, data, tail): + def Field(b, n, w=4): + for count in range(w): + b.append(n & 0xFF) + n = n >> 8 + def Bytes(b, data, w): + for n in range(w): + if n >= len(data) : b.append(0) + else : b.append(data[n]) + b = bytearray() + Field(b, head, 8) + Field(b, flags) + Field(b, adr) + Field(b, length) + Field(b, seq) + Field(b, tot) + Field(b, family) + Bytes(b, data, 476) + Field(b, tail) + fDst.write(b) + return adr + length, seq + 1 + +# .--------------------------------------------------------------------------. +# | Create a linker script for an APP build | +# `--------------------------------------------------------------------------' + +def Align(n, mask): + if (n & (mask-1)) != 0: + n = (n | (mask-1)) + 1 + return n + +def Size(n, mask): + return int(Align(n & 0x0FFFFFFF, mask) / 1024) + +def Prepare(dir, app): + # dir = "./build" + # app = "app" + + if REMOVE_STAGE_2_BOOLOADER : stage2 = " Removed " + else : stage2 = "*" + + mapFile = os.path.join(dir, "applink.map") + if not os.path.isfile(mapFile): + print("LINK {} - '{}' not found".format(app, mapFile)) + if IGNORE_ERRORS : sys.exit() + else : sys.exit(1) + + last = 0 + with open(mapFile, "r") as f: + for s in f: + if s[0] != "S": + a = s.strip().split() + this = int(a[1], 16) + if this > last: + last = this + used = "{:>3}k".format(Size(last, 4 * 1024)) + + dstFile = os.path.join(dir, app + ".ld") + + with open(dstFile, "w") as f: + f.write( +""" +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +/* + Increased the FLASH-ORIGIN by 64KB to not overwrite the bootloader + (reduce LENGTH accordingly) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000 +###k, LENGTH = 2048k -###k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Raspberry Pi Pico SDK + */ + + .flash_begin : { + __flash_binary_start = .; + } > FLASH + +/*###/ + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") +/###*/ + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ +/* + . = ALIGN(4); + } > FLASH + .text2 0x10010000 : { +*/ + *(.init) + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + /* End of .text-like segments */ + __etext = .; + + .ram_vector_table (COPY): { + *(.ram_vector_table) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.jcr) + . = ALIGN(4); + /* All data end */ + __data_end__ = .; + } > RAM AT> FLASH + + .uninitialized_data (COPY): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (COPY): + { + __end__ = .; + end = __end__; + *(.heap*) + __HeapLimit = .; + } > RAM + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (COPY): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (COPY): + { + *(.stack*) + } > SCRATCH_Y + + .flash_end : { + __flash_binary_end = .; + } > FLASH + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ +} +""" +.strip() +.replace("###k", used) +.replace("###", stage2)) + +# .--------------------------------------------------------------------------. +# | Post UF2 build Handling | +# `--------------------------------------------------------------------------' + +def Built(dir, app): + # dir = "./build" + # app = "app" + + uf2File = os.path.join(dir, app + ".uf2") + if not os.path.isfile(uf2File): + print("BUILT {} - '{}' not found".format(app, uf2File)) + if IGNORE_ERRORS : sys.exit() + else : sys.exit(1) + + strt = -1 + last = -1 + with open(uf2File, "rb") as f: + block = Uf2Block(f) + while block: + head, flags, adr, length, seq, tot, family, data, tail = Expand(block) + if head == 0x9E5D51570A324655 \ + and tail == 0xAB16F30 \ + and family == 0xE48BFF56: + if strt < 0 : strt = adr + if adr + length - 1 > last : last = adr + length - 1 + block = Uf2Block(f) + + size = "{:>3}k".format(Size(last-strt, 1024)) + used = "{:>3}k".format(Size(last, 4*1024)) + + print(last,strt) + print(size,used) + + mapfile = os.path.join(dir, "applink.map") + if (strt & 0x0FFFFFFF) == 0: + with open(mapfile, "w") as f: + # 12345678 12345678 ###k ###k + f.write("Start Last Size Used\n") + f.write("{:>08X} {:>08X} {} {} {}\n".format(strt, last, + size, used, app)) + info = Align(last, 256) + f.write("{:>08X} {:>08X} 256 {:>3}k {}\n".format(info, info | 0xFF, + Size(info | 0XFF, 4*1024), + "Info Table")) + else: + with open(mapfile, "a") as f: + f.write("{:>08X} {:>08X} {} {} {}\n".format(strt, last, + size, used, app)) + + mapFile = os.path.join(dir, app + ".map") + if EMIT_INDIVIDUAL_MAP_FILES: + with open(mapFile, "w") as f: + # 12345678 12345678 ###k ###k + f.write("Start Last Size Used\n") + f.write("{:>08X} {:>08X} {} {} {}\n".format(strt, last, + size, used, app)) + if (strt & 0x0FFFFFFF) == 0: + info = Align(last, 256) + f.write("{:>08X} {:>08X} 256 {:>3}k {}\n".format(info, info | 0xFF, + Size(info | 0XFF, 4*1024), + "Info Table")) + elif os.path.isfile(mapFile): + os.remove(mapFile) + +# .--------------------------------------------------------------------------. +# | Combining UF2 files | +# `--------------------------------------------------------------------------' + +def ToBytes(s): + b = bytearray() + for c in s: + b.append(ord(c)) + return b + +def Join(dir, dst, uf2): + # dir = "./build" + # dst = "out.uf2" + # uf2 = [ "boot.uf2", "app1.uf2", "app2.uf2", ... ] + + # Check the files exist + for src in uf2: + uf2File = os.path.join(dir, src) + if not os.path.isfile(uf2File): + print("JOIN {} - '{}' not found".format(dst, uf2File)) + if IGNORE_ERRORS : sys.exit() + else : sys.exit(1) + + # Determine how many blocks in output including padding and info block + adr = -1 + tot = 0 + for src in uf2: + uf2File = os.path.join(dir, src) + with open(uf2File, "rb") as fSrc: + block = Uf2Block(fSrc) + while block: + head, flags, Xadr, length, seq, Xtot, family, data, tail = Expand(block) + if adr < 0: + adr = Xadr + else: + while adr < Xadr: + adr = adr + 256 + tot = tot + 1 + adr = adr + length + tot = tot + 1 + block = Uf2Block(fSrc) + + # Build the info table + + info = "" + mapFile = os.path.join(dir, "applink.map") + with open(mapFile, "r") as f: + line = 0 + for s in f: + line = line + 1 + if line > 3: + a = s.strip().split() + strt = int(a[0], 16) + info += chr((strt >> 0) & 0xFF) + info += chr((strt >> 8) & 0xFF) + info += chr((strt >> 16) & 0xFF) + info += chr((strt >> 24) & 0xFF) + info += Pad(a[-1][:12], 12, chr(0)) + + # Concatenate the files + dstFile = os.path.join(dir, dst) + adr = -1 + seq = 0 + with open(dstFile, "wb") as fDst: + for n in range(len(uf2)): + uf2File = os.path.join(dir, uf2[n]) + with open(uf2File, "rb") as fSrc: + block = Uf2Block(fSrc) + while block: + head, flags, Xadr, length, Xseq, Xtot, family, data, tail = Expand(block) + if adr < 0: + adr = Xadr + else: + while adr < Xadr: + # 123456789-123456 + pads = ToBytes("Padding before {}".format(uf2[n])) + adr, seq = Encode(fDst, head, flags, adr, 256, seq, tot, family, + pads, tail) + adr, seq = Encode(fDst, head, flags, adr, 256, seq, tot, family, + data[:256], tail) + block = Uf2Block(fSrc) + if n == 0: + # Add info block + adr, seq = Encode(fDst, head, flags, adr, 256, seq, tot, family, + ToBytes(info)[:256], tail) + +# .--------------------------------------------------------------------------. +# | Main program and command dispatcher | +# `--------------------------------------------------------------------------' + +def GetArgv(n): + if n < 0 : return sys.argv[-n:] + elif n < len(sys.argv) : return sys.argv[n] + else : return "" + +def Main(): + + # 0 1 2 3 4 + # ./applink.py PREPARE ./build app + # ./applink.py BUILT ./build app + # ./applink.py JOIN ./build out.uf2 boot.uf2 app1.uf2 app2.uf2 ... + + cmd = GetArgv(1) + dir = GetArgv(2) + app = GetArgv(3) + uf2 = GetArgv(-4) + + if SHOW_INVOCATIONS: + for n in range(1, len(sys.argv)): + print("****** applink.py : [{}] {}".format(n, GetArgv(n))) + + logFile = os.path.join(dir, "applink.log") + if LOG_INVOCATIONS: + s = " ".join(sys.argv[2:]).replace("/home/pi/", "~/") + with open(logFile, "a") as f: + f.write("{} {:<5} {}\n".format(today, cmd, s)) + elif os.path.isfile(logFile): + os.remove(logFile) + + if cmd == "PREPARE" : Prepare (dir, app) + elif cmd == "BUILT" : Built (dir, app) + elif cmd == "JOIN" : Join (dir, app, uf2) + else: + print("Unknown '{}' command".format(cmd)) + sys.exit(1) + +if __name__ == "__main__": + Main() diff --git a/Code/pico_multi_booter/boot.c b/Code/pico_multi_booter/boot.c new file mode 100644 index 0000000..03aff41 --- /dev/null +++ b/Code/pico_multi_booter/boot.c @@ -0,0 +1,196 @@ + +// .------------------------------------------------------------------------. +// | MIT Licensed : Copyright 2022, "Hippy" | +// `------------------------------------------------------------------------' + +// ************************************************************************** +// * Check we did build booter with 'Copy to RAM' option * +// ************************************************************************** + +#if !PICO_COPY_TO_RAM + #error "The booter must use: pico_set_binary_type(${BOOT} copy_to_ram)" +#endif + +// ************************************************************************** +// * Pico SDK integration * +// ************************************************************************** + +#include +#include "RP2040.h" +#include "pico/stdlib.h" +#include "hardware/resets.h" +#include "hardware/gpio.h" + +// ************************************************************************** +// * Utility code * +// ************************************************************************** + +#define PEEK32(adr) *((uint32_t *) (adr)) +#define POKE32(adr, n) *((uint32_t *) (adr)) = n + +#define PEEK8(adr) *((uint8_t *) (adr)) + +uint32_t Align(uint32_t n, uint32_t mask) { + if ((n & (mask-1)) != 0) { + n = (n | (mask-1)) + 1; + } + return n; +} + +uint32_t Size(uint32_t n, uint32_t mask) { + return (((n & 0x0FFFFFFF) | (mask-1)) + 1) / 1024; +} + +uint32_t Crc32(uint32_t crc, uint8_t byt) { + for (uint32_t n = 8; n-- ; + crc = (crc << 1) ^ (((crc >> 31) ^ (byt >> 7)) * 0x04C11DB7), + byt <<= 1 + ); + return crc; +} + +// ************************************************************************** +// * Low-level interfacing * +// ************************************************************************** + +extern char __flash_binary_end; + +// Credit : https://github.com/usedbytes/rp2040-serial-bootloader +// Copyright (c) 2021 Brian Starkey +// SPDX-License-Identifier: BSD-3-Clause + +static void disable_interrupts(void) { + SysTick->CTRL &= ~1; + + NVIC->ICER[0] = 0xFFFFFFFF; + NVIC->ICPR[0] = 0xFFFFFFFF; +} + +static void reset_peripherals(void) { + reset_block(~( + RESETS_RESET_IO_QSPI_BITS | + RESETS_RESET_PADS_QSPI_BITS | + RESETS_RESET_SYSCFG_BITS | + RESETS_RESET_PLL_SYS_BITS + )); +} + +static void jump_to_vtor(uint32_t vtor) { + // Derived from the Leaf Labs Cortex-M3 bootloader. + // Copyright (c) 2010 LeafLabs LLC. + // Modified 2021 Brian Starkey + // Originally under The MIT License + uint32_t reset_vector = *(volatile uint32_t *)(vtor + 0x04); + + SCB->VTOR = (volatile uint32_t)(vtor); + + asm volatile("msr msp, %0"::"g" (*(volatile uint32_t *)vtor)); + asm volatile("bx %0"::"r" (reset_vector)); +} + +#define LEDPIN 25 +#define SD_DET_PIN 22 +bool sd_card_inserted(void) +{ + // Active low detection - returns true when pin is low + return !gpio_get(SD_DET_PIN); +} + +// ************************************************************************** +// * Main booter program * +// ************************************************************************** + +int main(void) { + while (true) { + + stdio_init_all(); + sleep_ms(1000); + + + gpio_init(SD_DET_PIN); + gpio_set_dir(SD_DET_PIN, GPIO_IN); + gpio_pull_up(SD_DET_PIN); // Enable pull-up resistor + + gpio_init(LEDPIN); + gpio_set_dir(LEDPIN,GPIO_OUT); + gpio_put(LEDPIN,1); + + // Determine where the boot binary ends + uintptr_t last = (uintptr_t) &__flash_binary_end; + printf("Booter ends at %08X\n", last); + // Determine the size in 1K blocks + printf("Booter size is %luk\n", Size(last, 1024)); + // Determine the size in 4K blocks + printf("Booter used is %luk\n", Size(last, 4 * 1024)); + // Determine the start of the next 256 byte block + printf("App info table %08lX\n", Align(last, 256)); + // Determine the start of the next 4K block + printf("App base is at %08lX\n", Align(last, 4 * 1024)); + printf("\n"); + + // Find the start of the next 256 byte block, the info table + uint32_t info = Align(last, 256); + uint32_t addr; + // Report items in the info table + int max = 0; + for (uint32_t item=0; item < 16; item++) { + addr = PEEK32(info + (item * 16)); + if (addr != 0) { + printf("%lu : %08lX ", item+1, addr); + for (uint32_t cptr=4; cptr < 16; cptr++) { + char c = PEEK8(info + (item * 16 ) + cptr); + if (c != 0) { + printf("%c", c); + } + } + printf("\n"); + max++; + } + } + printf("max %d \n",max); + + // Choose an app to launch + int chosen; + if(!sd_card_inserted()){ + printf("No sd card\n"); + chosen = 0; + }else{ + printf("Has SD card\n"); + chosen = max-1; + if(chosen <0 ) chosen = 0; + } + + // Get start address of app + addr = PEEK32( info + ((chosen * 16))); + printf("Application at %08lX\n", addr); + + // Determine if the start of the application is actually + // the second stage bootloader. if so we need to skip + // beyond that. + + uint32_t crc = 0xFFFFFFFF; + for(uint32_t pc = 0; pc < 0xFC; pc += 4) { + uint32_t u32 = PEEK32(addr + pc); + crc = Crc32(crc, (u32 >> 0 ) & 0xFF); + crc = Crc32(crc, (u32 >> 8 ) & 0xFF); + crc = Crc32(crc, (u32 >> 16 ) & 0xFF); + crc = Crc32(crc, (u32 >> 24 ) & 0xFF); + } + if (crc == PEEK32(addr + 0xFC)) { + printf("Stage 2 adjust\n"); + addr += 256; + printf("Application at %08lX\n", addr); + } + printf("\n"); + + printf("Launching application code ...\n"); + printf("\n"); + + sleep_ms(1000); + + disable_interrupts(); + reset_peripherals(); + jump_to_vtor(addr); + } + +} diff --git a/Code/pico_multi_booter/memmap_default_rp2040.ld b/Code/pico_multi_booter/memmap_default_rp2040.ld new file mode 100644 index 0000000..fc8f9fa --- /dev/null +++ b/Code/pico_multi_booter/memmap_default_rp2040.ld @@ -0,0 +1,219 @@ +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000 + 940k, LENGTH = 2048k - 940k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Raspberry Pi Pico SDK + */ + + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + .ram_vector_table (NOLOAD): { + *(.ram_vector_table) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.jcr) + . = ALIGN(4); + /* All data end */ + __data_end__ = .; + } > RAM AT> FLASH + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + __HeapLimit = .; + } > RAM + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + KEEP(*(.stack*)) + } > SCRATCH_Y + + .flash_end : { + PROVIDE(__flash_binary_end = .); + } > FLASH + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ +} + diff --git a/Code/pico_multi_booter/memmap_default_rp2350.ld b/Code/pico_multi_booter/memmap_default_rp2350.ld new file mode 100644 index 0000000..b998198 --- /dev/null +++ b/Code/pico_multi_booter/memmap_default_rp2350.ld @@ -0,0 +1,302 @@ +/* Based on GCC ARM embedded samples . + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000 + 940k, LENGTH = 4096k - 940k + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k + SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k +} + +ENTRY(_entry_point) + +SECTIONS +{ + .flash_begin : { + __flash_binary_start = .; + } > FLASH + + /* The bootrom will enter the image at the point indicated in your + IMAGE_DEF, which is usually the reset handler of your vector table. + + The debugger will use the ELF entry point, which is the _entry_point + symbol, and in our case is *different from the bootrom's entry point.* + This is used to go back through the bootrom on debugger launches only, + to perform the same initial flash setup that would be performed on a + cold boot. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.embedded_block)) + __embedded_block_end = .; + KEEP (*(.reset)) + /* TODO revisit this now memset/memcpy/float in ROM */ + /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from + * FLASH ... we will include any thing excluded here in .data below by default */ + *(.init) + *libgcc.a:cmse_nonsecure_call.o + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + /* Note the boot2 section is optional, and should be discarded if there is + no reference to it *inside* the binary, as it is not called by the + bootrom. (The bootrom performs a simple best-effort XIP setup and + leaves it to the binary to do anything more sophisticated.) However + there is still a size limit of 256 bytes, to ensure the boot2 can be + stored in boot RAM. + + Really this is a "XIP setup function" -- the name boot2 is historic and + refers to its dual-purpose on RP2040, where it also handled vectoring + from the bootrom into the user image. + */ + + .boot2 : { + __boot2_start__ = .; + *(.boot2) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ <= 256, + "ERROR: Pico second stage bootloader must be no more than 256 bytes in size") + + .rodata : { + *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) + *(.srodata*) + . = ALIGN(4); + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + .ram_vector_table (NOLOAD): { + *(.ram_vector_table) + } > RAM + + .uninitialized_data (NOLOAD): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + *(.sdata*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + *(.jcr) + . = ALIGN(4); + } > RAM AT> FLASH + + .tdata : { + . = ALIGN(4); + *(.tdata .tdata.* .gnu.linkonce.td.*) + /* All data end */ + __tdata_end = .; + } > RAM AT> FLASH + PROVIDE(__data_end__ = .); + + /* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */ + __etext = LOADADDR(.data); + + .tbss (NOLOAD) : { + . = ALIGN(4); + __bss_start__ = .; + __tls_base = .; + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + + __tls_end = .; + } > RAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __tbss_end = .; + + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + PROVIDE(__global_pointer$ = . + 2K); + *(.sbss*) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + /* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however + to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */ + . = ORIGIN(RAM) + LENGTH(RAM); + __HeapLimit = .; + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (NOLOAD): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (NOLOAD): + { + KEEP(*(.stack*)) + } > SCRATCH_Y + + .flash_end : { + KEEP(*(.embedded_end_block*)) + PROVIDE(__flash_binary_end = .); + } > FLASH =0xaa + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* picolibc and LLVM */ + PROVIDE (__heap_start = __end__); + PROVIDE (__heap_end = __HeapLimit); + PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) ); + PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1)); + PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) ); + + /* llvm-libc */ + PROVIDE (_end = __end__); + PROVIDE (__llvm_libc_heap_limit = __HeapLimit); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary") + ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary") + + /* todo assert on extra code */ +} diff --git a/Code/pico_multi_booter/sd_boot/CMakeLists.txt b/Code/pico_multi_booter/sd_boot/CMakeLists.txt new file mode 100644 index 0000000..8a34451 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/CMakeLists.txt @@ -0,0 +1,83 @@ + +include_directories( + ${CMAKE_CURRENT_LIST_DIR} +) + +set(APP_NAME sd_boot) + +add_subdirectory(i2ckbd) +add_subdirectory(lcdspi) +add_subdirectory(lib/pico-vfs) + +add_executable(${APP_NAME} + main.c + key_event.c + text_directory_ui.c +) + +target_link_libraries(${APP_NAME} + pico_stdlib + hardware_sync + hardware_flash + hardware_irq + hardware_adc + hardware_pwm + hardware_i2c + hardware_spi + hardware_dma + hardware_exception + hardware_pio + pico_multicore + i2ckbd + lcdspi + blockdevice_sd + filesystem_fat + filesystem_vfs +) + +target_compile_options(${APP_NAME} PRIVATE + -Os + -Wall + -Wno-unused-variable + -Wno-unused-function +) + +pico_enable_stdio_usb(${APP_NAME} 0) +pico_enable_stdio_uart(${APP_NAME} 1) + +pico_add_extra_outputs(${APP_NAME}) +pico_add_uf2_output(${APP_NAME}) + + +target_link_options(${APP_NAME} PRIVATE -Wl,--print-memory-usage) +target_link_options(${APP_NAME} PRIVATE -Wl,-z,max-page-size=4096) + +pico_set_linker_script(${APP_NAME} ${CMAKE_BINARY_DIR}/${APP_NAME}.ld) + + +set(UF2_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/${APP_NAME}.uf2) +set(UF2_DEST ${CMAKE_BINARY_DIR}/${APP_NAME}.uf2) + +add_custom_command( + OUTPUT ${UF2_DEST} + DEPENDS ${UF2_SOURCE} + COMMAND ${CMAKE_COMMAND} -E copy ${UF2_SOURCE} ${UF2_DEST} + COMMENT "Copying ${APP_NAME}.uf2 to top-level build dir" +) + + +add_custom_target(PREPARE_${APP_NAME} + COMMENT "Create Linker Script for '${APP_NAME}'" + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/applink.py PREPARE + ${CMAKE_BINARY_DIR} + ${APP_NAME}) + +add_custom_target(BUILT_${APP_NAME} + COMMENT "Record Build Details for '${APP_NAME}'" + DEPENDS ${UF2_DEST} + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/applink.py BUILT + ${CMAKE_BINARY_DIR} + ${APP_NAME}) + diff --git a/Code/pico_multi_booter/sd_boot/config.h b/Code/pico_multi_booter/sd_boot/config.h new file mode 100644 index 0000000..849f548 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/config.h @@ -0,0 +1,45 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// GPIOs for SPI interface (SD card) +#define SD_SPI0 0 +#define SD_SCLK_PIN 18 +#define SD_MOSI_PIN 19 +#define SD_MISO_PIN 16 +#define SD_CS_PIN 17 +#define SD_DET_PIN 22 + +#define LCD_SPI1 1 +#define LCD_SCK_PIN 10 +#define LCD_MOSI_PIN 11 +#define LCD_MISO_PIN 12 +#define LCD_CS_PIN 13 +#define LCD_DC_PIN 14 +#define LCD_RST_PIN 15 + +// GPIOs for audio output +#define AUDIO_LEFT 28 +#define AUDIO_RIGHT 27 + +// GPIOs for buttons +#define NEXT_BUTTON 2 +#define PART_BUTTON 3 + +// Pico-internal GPIOs +#define PICO_PS 23 +#define LED_PIN 25 + + + +// PicoCalc SD Firmware Loader +// SD_BOOT_FLASH_OFFSET is the offset in flash memory where the bootloader starts +// According to the applink.map ,with combined PicoMite, here is 920k +// This offset is used to ensure that the bootloader does not get overwritten +// when loading a new application from the SD card +#define SD_BOOT_FLASH_OFFSET (940 * 1024) + +// Maximum size of the application that can be loaded +// This ensures we don't overwrite the bootloader itself +#define MAX_APP_SIZE (PICO_FLASH_SIZE_BYTES - SD_BOOT_FLASH_OFFSET) + +#endif // CONFIG_H diff --git a/Code/pico_multi_booter/sd_boot/debug.h b/Code/pico_multi_booter/sd_boot/debug.h new file mode 100644 index 0000000..ca8c1b0 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/debug.h @@ -0,0 +1,11 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#ifdef ENABLE_DEBUG +#include +#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +#define DEBUG_PRINT(fmt, ...) +#endif + +#endif // DEBUG_H \ No newline at end of file diff --git a/Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt b/Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt new file mode 100644 index 0000000..cde140c --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt @@ -0,0 +1,21 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) + + +project(i2ckbd + VERSION 0.0.1 + DESCRIPTION "i2ckbd for picocalc." + ) + +add_library(i2ckbd INTERFACE) + +target_sources(i2ckbd INTERFACE + i2ckbd.c + ) + +target_link_libraries(i2ckbd INTERFACE pico_stdlib hardware_spi hardware_i2c) + +target_include_directories(i2ckbd INTERFACE ${CMAKE_CURRENT_LIST_DIR}) \ No newline at end of file diff --git a/Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c b/Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c new file mode 100644 index 0000000..1b32456 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c @@ -0,0 +1,85 @@ +#include +#include +#include "i2ckbd.h" +#include "debug.h" + +static uint8_t i2c_inited = 0; + +void init_i2c_kbd() { + gpio_set_function(I2C_KBD_SCL, GPIO_FUNC_I2C); + gpio_set_function(I2C_KBD_SDA, GPIO_FUNC_I2C); + i2c_init(I2C_KBD_MOD, I2C_KBD_SPEED); + gpio_pull_up(I2C_KBD_SCL); + gpio_pull_up(I2C_KBD_SDA); + + i2c_inited = 1; +} + +int read_i2c_kbd() { + int retval; + static int ctrlheld = 0; + uint16_t buff = 0; + unsigned char msg[2]; + int c = -1; + msg[0] = 0x09; + + if (i2c_inited == 0) return -1; + + retval = i2c_write_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, msg, 1, false, 500000); + if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) { + DEBUG_PRINT("I2C write err\n"); + return -1; + } + + sleep_ms(16); + retval = i2c_read_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, (unsigned char *) &buff, 2, false, 500000); + if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) { + DEBUG_PRINT("I2C read err\n"); + return -1; + } + + if (buff != 0) { + if (buff == 0x7e03)ctrlheld = 0; + else if (buff == 0x7e02) { + ctrlheld = 1; + } else if ((buff & 0xff) == 1) {//pressed + c = buff >> 8; + int realc = -1; + switch (c) { + default: + realc = c; + break; + } + c = realc; + if (c >= 'a' && c <= 'z' && ctrlheld)c = c - 'a' + 1; + } + return c; + } + return -1; +} + +int read_battery() { + int retval; + uint16_t buff = 0; + unsigned char msg[2]; + msg[0] = 0x0b; + + if (i2c_inited == 0) return -1; + + retval = i2c_write_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, msg, 1, false, 500000); + if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) { + DEBUG_PRINT("Batt I2C write err\n"); + return -1; + } + sleep_ms(16); + retval = i2c_read_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, (unsigned char *) &buff, 2, false, 500000); + if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) { + DEBUG_PRINT("Batt I2C read err\n"); + return -1; + } + + if (buff != 0) { + return buff; + } + return -1; +} diff --git a/Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h b/Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h new file mode 100644 index 0000000..b9414f2 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h @@ -0,0 +1,20 @@ +#ifndef I2C_KEYBOARD_H +#define I2C_KEYBOARD_H +#include +#include +#include +#include + +#define I2C_KBD_MOD i2c1 +#define I2C_KBD_SDA 6 +#define I2C_KBD_SCL 7 + +#define I2C_KBD_SPEED 10000 // if dual i2c, then the speed of keyboard i2c should be 10khz + +#define I2C_KBD_ADDR 0x1F + +void init_i2c_kbd(); +int read_i2c_kbd(); +int read_battery(); + +#endif \ No newline at end of file diff --git a/Code/pico_multi_booter/sd_boot/key_event.c b/Code/pico_multi_booter/sd_boot/key_event.c new file mode 100644 index 0000000..9466d67 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/key_event.c @@ -0,0 +1,143 @@ +/** + * PicoCalc SD Firmware Loader + * + * Author: Hsuan Han Lai + * Email: hsuan.han.lai@gmail.com + * Website: https://hsuanhanlai.com + * Year: 2025 + * + * key_event.c + * + * Wrapper for post processing dispatch keyboard events + * + */ + +#include "i2ckbd.h" +#include +#include +#include +#include "key_event.h" +#include "debug.h" + +void keypad_init(void) +{ + init_i2c_kbd(); +} + +int keypad_get_key(void) +{ + int r = read_i2c_kbd(); + if (r < 0) { + return 0; + } + + + int act_key = 0; + + /* Translate the keys to LVGL control characters according to your key definitions */ + switch (r) { + case 0xb5: // Arrow Up + act_key = KEY_ARROW_UP; + break; + case 0xb6: // Arrow Down + act_key = KEY_ARROW_DOWN; + break; + case 0xb4: // Arrow Left + act_key = KEY_ARROW_LEFT; + break; + case 0xb7: // Arrow Right + act_key = KEY_ARROW_RIGHT; + break; + case 0x0A: // Enter + act_key = KEY_ENTER; + break; + + // Special Keys + case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: + case 0x86: case 0x87: case 0x88: case 0x89: case 0x90: // F1-F10 Keys + DEBUG_PRINT("Warn: F-key unmapped\n"); + act_key = 0; + break; + + case 0xB1: // ESC + act_key = 0; + break; + case 0x09: // TAB + act_key = 0; + break; + case 0xC1: // Caps Lock + act_key = 0; + break; + case 0xD4: // DEL + act_key = 0; + break; + case 0x08: // Backspace + act_key = KEY_BACKSPACE; + break; + + case 0xD0: // brk + act_key = 0; + break; + case 0xD2: // Home + act_key = 0; + break; + case 0xD5: // End + act_key = 0; + break; + + case 0x60: case 0x2F: case 0x5C: case 0x2D: case 0x3D: + case 0x5B: case 0x5D: // `/\-=[] Keys + act_key = r; + break; + + case 0x7E: act_key = '~'; break; + case 0x3F: act_key = '?'; break; + case 0x7C: act_key = '|'; break; + case 0x5F: act_key = '_'; break; + case 0x2B: act_key = '+'; break; + case 0x7B: act_key = '{'; break; + case 0x7D: act_key = '}'; break; + + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: + case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // 0-9 Keys + act_key = r; + break; + + case 0x21: case 0x40: case 0x23: case 0x24: case 0x25: + case 0x5E: case 0x26: case 0x2A: case 0x28: case 0x29: // !@#$%^&*() Keys + act_key = r; + break; + + case 0xD1: // Insert + DEBUG_PRINT("Warn: Insert unmapped\n"); + act_key = 0; + break; + + case 0x3C: act_key = '<'; break; + case 0x3E: act_key = '>'; break; + + case 0x3B: case 0x27: case 0x3A: case 0x22: // ;:'"" Keys + act_key = r; + break; + case 0xA5: // CTL + DEBUG_PRINT("Warn: CTL unmapped\n"); + act_key = 0; + break; + case 0x20: // SPACE + act_key = r; + break; + case 0xA1: // ALT + DEBUG_PRINT("Warn: ALT unmapped\n"); + act_key = 0; + break; + case 0xA2: case 0xA3: // RIGHT/LEFT SHIFT + break; + + default: + act_key = r; + break; + + } + return act_key; +} + diff --git a/Code/pico_multi_booter/sd_boot/key_event.h b/Code/pico_multi_booter/sd_boot/key_event.h new file mode 100644 index 0000000..6f4e3f0 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/key_event.h @@ -0,0 +1,21 @@ +#ifndef KEY_EVENT_H +#define KEY_EVENT_H + +#include "i2ckbd.h" +#include +#include +#include + +typedef enum { + KEY_ARROW_UP = 0xB5, + KEY_ARROW_LEFT = 0xB4, + KEY_ARROW_RIGHT = 0xB7, + KEY_ARROW_DOWN = 0xB6, + KEY_BACKSPACE = 0x08, + KEY_ENTER = 0x0A, +} lv_key_t; + +void keypad_init(void); +int keypad_get_key(void); + +#endif // KEY_EVENT_H diff --git a/Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt b/Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt new file mode 100644 index 0000000..35f4078 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt @@ -0,0 +1,20 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) + +project(lcdspi + VERSION 0.0.1 + DESCRIPTION "lcdspi for rp2040." + ) + +add_library(lcdspi INTERFACE) + +target_sources(lcdspi INTERFACE + lcdspi.c + ) + +target_link_libraries(lcdspi INTERFACE pico_stdlib hardware_spi) + +target_include_directories(lcdspi INTERFACE ${CMAKE_CURRENT_LIST_DIR}) \ No newline at end of file diff --git a/Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h b/Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h new file mode 100644 index 0000000..749ad88 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h @@ -0,0 +1,233 @@ +// font1.c +// Font type : Full (223 characters) +// Font size : 8x12 pixels +// Memory usage : 2680 bytes +// Font adapted from: http://www.rinkydinkelectronics.com/r_fonts.php + +const unsigned char font1[] ={ + 0x08,0x0C,0x20,0xE0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(32) + 0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x10,0x10,0x00,0x00, // Chr$(33) ! + 0x00,0x24,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(34) " + 0x00,0x24,0x24,0x7E,0x24,0x24,0x24,0x7E,0x24,0x24,0x00,0x00, // Chr$(35) # + 0x00,0x10,0x3C,0x52,0x50,0x3C,0x12,0x52,0x3C,0x10,0x00,0x00, // Chr$(36) $ + 0x00,0x00,0x62,0x62,0x04,0x08,0x10,0x20,0x46,0x46,0x00,0x00, // Chr$(37) % + 0x00,0x00,0x38,0x44,0x44,0x38,0x4A,0x44,0x4C,0x3A,0x00,0x00, // Chr$(38) & + 0x00,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(39) ' + 0x00,0x0C,0x10,0x20,0x20,0x20,0x20,0x20,0x20,0x10,0x0C,0x00, // Chr$(40) ( + 0x00,0x30,0x08,0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x30,0x00, // Chr$(41) ) + 0x00,0x42,0x24,0x18,0x7E,0x18,0x24,0x42,0x00,0x00,0x00,0x00, // Chr$(42) * + 0x00,0x00,0x00,0x10,0x10,0x7C,0x10,0x10,0x00,0x00,0x00,0x00, // Chr$(43) + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x08,0x10,0x00, // Chr$(44) , + 0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(45) - + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // Chr$(46) . + 0x00,0x02,0x02,0x04,0x04,0x08,0x10,0x20,0x20,0x40,0x40,0x00, // Chr$(47) / + 0x00,0x3C,0x46,0x4E,0x4A,0x5A,0x52,0x72,0x62,0x3C,0x00,0x00, // Chr$(48) 0 + 0x00,0x10,0x30,0x50,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00, // Chr$(49) 1 + 0x00,0x3C,0x42,0x02,0x02,0x04,0x08,0x10,0x20,0x7E,0x00,0x00, // Chr$(50) 2 + 0x00,0x3C,0x42,0x02,0x02,0x1C,0x02,0x02,0x42,0x3C,0x00,0x00, // Chr$(51) 3 + 0x00,0x04,0x44,0x44,0x44,0x7E,0x04,0x04,0x04,0x04,0x00,0x00, // Chr$(52) 4 + 0x00,0x7E,0x40,0x40,0x7C,0x02,0x02,0x02,0x42,0x3C,0x00,0x00, // Chr$(53) 5 + 0x00,0x18,0x20,0x40,0x40,0x7C,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(54) 6 + 0x00,0x7E,0x42,0x02,0x02,0x04,0x08,0x10,0x10,0x10,0x00,0x00, // Chr$(55) 7 + 0x00,0x3C,0x42,0x42,0x42,0x3C,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(56) 8 + 0x00,0x3C,0x42,0x42,0x42,0x3E,0x02,0x04,0x08,0x30,0x00,0x00, // Chr$(57) 9 + 0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00, // Chr$(58) : + 0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x08,0x10,0x00, // Chr$(59) ; + 0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x00,0x00, // Chr$(60) < + 0x00,0x00,0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00,0x00,0x00, // Chr$(61) = + 0x00,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x00,0x00, // Chr$(62) > + 0x00,0x3C,0x42,0x02,0x06,0x08,0x10,0x10,0x00,0x10,0x10,0x00, // Chr$(63) ? + 0x00,0x3C,0x42,0x42,0x5E,0x56,0x5C,0x40,0x40,0x3C,0x00,0x00, // Chr$(64) @ + 0x00,0x18,0x24,0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x00,0x00, // Chr$(65) A + 0x00,0x7C,0x42,0x42,0x42,0x7C,0x42,0x42,0x42,0x7C,0x00,0x00, // Chr$(66) B + 0x00,0x3C,0x42,0x40,0x40,0x40,0x40,0x40,0x42,0x3C,0x00,0x00, // Chr$(67) C + 0x00,0x78,0x44,0x42,0x42,0x42,0x42,0x42,0x44,0x78,0x00,0x00, // Chr$(68) D + 0x00,0x7E,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x7E,0x00,0x00, // Chr$(69) E + 0x00,0x7E,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x40,0x00,0x00, // Chr$(70) F + 0x00,0x3C,0x42,0x40,0x40,0x40,0x4E,0x42,0x42,0x3E,0x00,0x00, // Chr$(71) G + 0x00,0x42,0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x42,0x00,0x00, // Chr$(72) H + 0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00, // Chr$(73) I + 0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x42,0x3C,0x00,0x00, // Chr$(74) J + 0x00,0x42,0x44,0x48,0x50,0x60,0x50,0x48,0x44,0x42,0x00,0x00, // Chr$(75) K + 0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7E,0x00,0x00, // Chr$(76) L + 0x00,0x42,0x66,0x66,0x5A,0x42,0x42,0x42,0x42,0x42,0x00,0x00, // Chr$(77) M + 0x00,0x42,0x62,0x62,0x52,0x5A,0x4A,0x46,0x46,0x42,0x00,0x00, // Chr$(78) N + 0x00,0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(79) O + 0x00,0x7C,0x42,0x42,0x42,0x7C,0x40,0x40,0x40,0x40,0x00,0x00, // Chr$(80) P + 0x00,0x3C,0x42,0x42,0x42,0x42,0x42,0x4A,0x44,0x3A,0x00,0x00, // Chr$(81) Q + 0x00,0x7C,0x42,0x42,0x42,0x7C,0x48,0x44,0x42,0x42,0x00,0x00, // Chr$(82) R + 0x00,0x3C,0x42,0x40,0x60,0x18,0x04,0x02,0x42,0x3C,0x00,0x00, // Chr$(83) S + 0x00,0x7E,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(84) T + 0x00,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(85) U + 0x00,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x24,0x18,0x00,0x00, // Chr$(86) V + 0x00,0x42,0x42,0x42,0x42,0x42,0x5A,0x24,0x24,0x24,0x00,0x00, // Chr$(87) W + 0x00,0x42,0x42,0x24,0x24,0x18,0x24,0x24,0x42,0x42,0x00,0x00, // Chr$(88) X + 0x00,0x44,0x44,0x44,0x44,0x38,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(89) Y + 0x00,0x7E,0x02,0x02,0x04,0x18,0x20,0x40,0x40,0x7E,0x00,0x00, // Chr$(90) Z + 0x00,0x3C,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3C,0x00,0x00, // Chr$(91) [ + 0x00,0x40,0x40,0x20,0x20,0x10,0x08,0x04,0x04,0x02,0x02,0x00, // Chr$(92) backslash + 0x00,0x3C,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x3C,0x00,0x00, // Chr$(93) ] + 0x00,0x10,0x28,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(94) ^ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00, // Chr$(95) _ + 0x00,0x18,0x24,0x24,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(96) ` + 0x00,0x00,0x00,0x00,0x3C,0x02,0x3E,0x42,0x42,0x3E,0x00,0x00, // Chr$(97) a + 0x00,0x40,0x40,0x40,0x7C,0x42,0x42,0x42,0x42,0x7C,0x00,0x00, // Chr$(98) b + 0x00,0x00,0x00,0x00,0x3C,0x40,0x40,0x40,0x40,0x3C,0x00,0x00, // Chr$(99) c + 0x00,0x02,0x02,0x02,0x3E,0x42,0x42,0x42,0x42,0x3E,0x00,0x00, // Chr$(100) d + 0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x7C,0x40,0x3C,0x00,0x00, // Chr$(101) e + 0x00,0x00,0x1C,0x20,0x20,0x7C,0x20,0x20,0x20,0x20,0x00,0x00, // Chr$(102) f + 0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x3E,0x02,0x3C,0x00, // Chr$(103) g + 0x00,0x40,0x40,0x40,0x7C,0x42,0x42,0x42,0x42,0x42,0x00,0x00, // Chr$(104) h + 0x00,0x00,0x10,0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(105) i + 0x00,0x00,0x04,0x00,0x04,0x04,0x04,0x04,0x04,0x44,0x38,0x00, // Chr$(106) j + 0x00,0x40,0x40,0x40,0x44,0x48,0x70,0x50,0x48,0x44,0x00,0x00, // Chr$(107) k + 0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x18,0x00,0x00, // Chr$(108) l + 0x00,0x00,0x00,0x00,0x74,0x4A,0x4A,0x42,0x42,0x42,0x00,0x00, // Chr$(109) m + 0x00,0x00,0x00,0x00,0x78,0x44,0x44,0x44,0x44,0x44,0x00,0x00, // Chr$(110) n + 0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(111) o + 0x00,0x00,0x00,0x00,0x7C,0x42,0x42,0x42,0x7C,0x40,0x40,0x00, // Chr$(112) p + 0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x3C,0x06,0x00, // Chr$(113) q + 0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x40,0x40,0x40,0x00,0x00, // Chr$(114) r + 0x00,0x00,0x00,0x00,0x1E,0x20,0x1C,0x02,0x02,0x3C,0x00,0x00, // Chr$(115) s + 0x00,0x00,0x00,0x10,0x7C,0x10,0x10,0x10,0x10,0x08,0x00,0x00, // Chr$(116) t + 0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(117) u + 0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00, // Chr$(118) v + 0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x5A,0x24,0x24,0x00,0x00, // Chr$(119) w + 0x00,0x00,0x00,0x00,0x42,0x24,0x18,0x18,0x24,0x42,0x00,0x00, // Chr$(120) x + 0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x3E,0x02,0x42,0x3C,0x00, // Chr$(121) y + 0x00,0x00,0x00,0x00,0x7E,0x04,0x08,0x10,0x20,0x7E,0x00,0x00, // Chr$(122) z + 0x00,0x1C,0x20,0x10,0x10,0x60,0x10,0x10,0x20,0x1C,0x00,0x00, // Chr$(123) { + 0x00,0x10,0x10,0x10,0x10,0x00,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(124) | + 0x00,0x38,0x04,0x08,0x08,0x06,0x08,0x08,0x04,0x38,0x00,0x00, // Chr$(125) } + 0x00,0x22,0x5A,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(126) ~ + 0x00,0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x7E,0x00,0x00,0x00, // Chr$(127)  + 0x00,0xFF,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0xFF,0x00, // Chr$(128) € + 0x00,0xFF,0x81,0x81,0x85,0x85,0xA9,0x91,0x81,0x81,0xFF,0x00, // Chr$(129)  + 0x00,0xFF,0x81,0xA5,0xA5,0x99,0x99,0xA5,0xA5,0x81,0xFF,0x00, // Chr$(130) ‚ + 0x00,0xFF,0x81,0x81,0x81,0x99,0x99,0x81,0x81,0x81,0xFF,0x00, // Chr$(131) ƒ + 0x00,0xFF,0x81,0x81,0x81,0x81,0x81,0xBD,0x81,0x81,0xFF,0x00, // Chr$(132) „ + 0x00,0xFF,0x81,0x99,0x99,0x99,0x99,0x81,0x99,0x81,0xFF,0x00, // Chr$(133) … + 0x00,0xFF,0x81,0x99,0xA5,0x89,0x89,0x81,0x89,0x81,0xFF,0x00, // Chr$(134) † + 0x00,0x3E,0x7F,0x7F,0x6B,0x7F,0x6B,0x77,0x7F,0x3E,0x00,0x00, // Chr$(135) ‡ + 0x00,0x3E,0x63,0x41,0x55,0x41,0x55,0x49,0x63,0x3E,0x00,0x00, // Chr$(136) ˆ + 0x00,0x00,0x08,0x1C,0x3E,0x7F,0x7F,0x3E,0x1C,0x08,0x00,0x00, // Chr$(137) ‰ + 0x00,0x00,0x00,0x18,0x18,0x66,0x66,0x18,0x18,0x3C,0x00,0x00, // Chr$(138) Š + 0x00,0x00,0x00,0x18,0x3C,0x7E,0x7E,0x18,0x18,0x3C,0x00,0x00, // Chr$(139) ‹ + 0x00,0x00,0x36,0x3E,0x7F,0x7F,0x7F,0x3E,0x1C,0x08,0x00,0x00, // Chr$(140) Œ + 0xFF,0xFF,0xC3,0x81,0x99,0xBD,0xBD,0x99,0x81,0xC3,0xFF,0xFF, // Chr$(141)  + 0x00,0x00,0x00,0x00,0x18,0x3C,0x3C,0x18,0x00,0x00,0x00,0x00, // Chr$(142) Ž + 0x00,0x1F,0x19,0x1F,0x18,0x18,0x18,0x38,0x78,0x30,0x00,0x00, // Chr$(143)  + 0x00,0x18,0x3C,0x7E,0x18,0x18,0x18,0x7E,0x3C,0x18,0x00,0x00, // Chr$(144)  + 0x00,0x00,0x00,0x24,0x66,0xFF,0xFF,0x66,0x24,0x00,0x00,0x00, // Chr$(145) ‘ + 0x00,0x18,0x3C,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00, // Chr$(146) ’ + 0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x3C,0x18,0x00,0x00, // Chr$(147) “ + 0x00,0x00,0x00,0x08,0x0C,0xFE,0xFE,0x0C,0x08,0x00,0x00,0x00, // Chr$(148) ” + 0x00,0x00,0x00,0x10,0x30,0x7F,0x7F,0x30,0x10,0x00,0x00,0x00, // Chr$(149) • + 0x00,0x00,0x08,0x2A,0x49,0x49,0x49,0x41,0x41,0x3E,0x00,0x00, // Chr$(150) – + 0x1C,0x22,0x41,0x5D,0x49,0x2A,0x1C,0x00,0x1C,0x00,0x1C,0x00, // Chr$(151) — + 0x18,0x18,0x00,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x18,0x00,0x00, // Chr$(152) ˜ + 0xF8,0x8C,0xBE,0x8F,0xE3,0x85,0xF5,0xF5,0xF3,0xFF,0x00,0x00, // Chr$(153) ™ + 0x00,0x18,0x24,0x42,0x42,0x42,0x7E,0x7E,0x24,0x24,0x24,0x24, // Chr$(154) š + 0x00,0x18,0x3C,0x7E,0x7E,0x7E,0x7E,0x7E,0x24,0x24,0x24,0x24, // Chr$(155) › + 0x00,0x20,0x64,0xE2,0xE9,0xE5,0xE9,0xE2,0x64,0x20,0x00,0x00, // Chr$(156) œ + 0x00,0x3F,0x4B,0x4B,0x4B,0x3B,0x0B,0x0B,0x0B,0x0B,0x00,0x00, // Chr$(157)  + 0x00,0x7E,0x44,0x48,0x6E,0x24,0x28,0x50,0x60,0x40,0x00,0x00, // Chr$(158) ž + 0x00,0x00,0x21,0x36,0x1E,0x3C,0xFE,0x1B,0x10,0x10,0x00,0x00, // Chr$(159) Ÿ + 0x00,0x3C,0x42,0x81,0xA5,0xA5,0xA5,0x81,0x42,0x3C,0x00,0x00, // Chr$(160)   + 0x00,0x3C,0x42,0x91,0x99,0x9D,0x99,0x91,0x42,0x3C,0x00,0x00, // Chr$(161) ¡ + 0x00,0x3C,0x42,0x81,0xBD,0xBD,0xBD,0x81,0x42,0x3C,0x00,0x00, // Chr$(162) ¢ + 0x00,0x00,0x38,0x44,0x44,0x44,0x3C,0x06,0x03,0x01,0x00,0x00, // Chr$(163) £ + 0x00,0x0C,0x10,0x20,0x7C,0x20,0x20,0x7C,0x20,0x10,0x0C,0x00, // Chr$(164) ¤ + 0x00,0x00,0x1A,0x26,0x42,0x81,0x42,0x42,0x42,0x7E,0x00,0x00, // Chr$(165) ¥ + 0x00,0x00,0x3C,0x24,0xFF,0x42,0x42,0x42,0x42,0x7E,0x00,0x00, // Chr$(166) ¦ + 0x00,0x18,0x66,0x42,0x81,0xC3,0xC3,0x42,0x00,0x00,0x00,0x00, // Chr$(167) § + 0x00,0x00,0xFF,0x81,0x81,0x81,0x81,0xFF,0x18,0x3C,0x00,0x00, // Chr$(168) ¨ + 0x00,0x18,0x3C,0x3C,0x3C,0x3C,0x18,0x00,0x18,0x18,0x00,0x00, // Chr$(169) © + 0x28,0x56,0x41,0x83,0xFE,0x0C,0x10,0x10,0x20,0x00,0x00,0x00, // Chr$(170) ª + 0x00,0x30,0x48,0x48,0x48,0x4A,0x32,0x02,0x02,0x02,0x02,0x00, // Chr$(171) « + 0x18,0x24,0x66,0x5A,0x66,0x18,0x38,0x18,0x38,0x18,0x38,0x00, // Chr$(172) ¬ + 0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF, // Chr$(173) ­ + 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55, // Chr$(174) ® + 0xCC,0xCC,0x33,0x33,0xCC,0xCC,0x33,0x33,0xCC,0xCC,0x33,0x33, // Chr$(175) ¯ + 0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49, // Chr$(176) ° + 0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA, // Chr$(177) ± + 0x6D,0xDB,0xB6,0x6D,0xDB,0xB6,0x6D,0xDB,0xB6,0x6D,0xDB,0xB6, // Chr$(178) ² + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(179) ³ + 0x18,0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(180) ´ + 0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0xF8,0x18,0x18,0x18,0x18, // Chr$(181) µ + 0x66,0x66,0x66,0x66,0x66,0xE6,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(182) ¶ + 0x00,0x00,0x00,0x00,0x00,0xFE,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(183) · + 0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0xF8,0x18,0x18,0x18,0x18, // Chr$(184) ¸ + 0x66,0x66,0x66,0x66,0xE6,0x06,0x06,0xE6,0x66,0x66,0x66,0x66, // Chr$(185) ¹ + 0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(186) º + 0x00,0x00,0x00,0x00,0xFE,0x06,0x06,0xE6,0x66,0x66,0x66,0x66, // Chr$(187) » + 0x66,0x66,0x66,0x66,0xE6,0x06,0x06,0xFE,0x00,0x00,0x00,0x00, // Chr$(188) ¼ + 0x66,0x66,0x66,0x66,0x66,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(189) ½ + 0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0xF8,0x00,0x00,0x00,0x00, // Chr$(190) ¾ + 0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(191) ¿ + 0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(192) À + 0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(193) Á + 0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(194)  + 0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(195) à + 0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(196) Ä + 0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(197) Å + 0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x1F,0x18,0x18,0x18,0x18, // Chr$(198) Æ + 0x66,0x66,0x66,0x66,0x66,0x67,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(199) Ç + 0x66,0x66,0x66,0x66,0x67,0x60,0x60,0x7F,0x00,0x00,0x00,0x00, // Chr$(200) È + 0x00,0x00,0x00,0x00,0x7F,0x60,0x60,0x67,0x66,0x66,0x66,0x66, // Chr$(201) É + 0x66,0x66,0x66,0x66,0xE7,0x00,0x00,0xFF,0x00,0x00,0x00,0x00, // Chr$(202) Ê + 0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xE7,0x66,0x66,0x66,0x66, // Chr$(203) Ë + 0x66,0x66,0x66,0x66,0x67,0x60,0x60,0x67,0x66,0x66,0x66,0x66, // Chr$(204) Ì + 0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0x00,0x00, // Chr$(205) Í + 0x66,0x66,0x66,0x66,0xE7,0x00,0x00,0xE7,0x66,0x66,0x66,0x66, // Chr$(206) Î + 0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0xFF,0x00,0x00,0x00,0x00, // Chr$(207) Ï + 0x66,0x66,0x66,0x66,0x66,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(208) Ð + 0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x18,0x18,0x18,0x18, // Chr$(209) Ñ + 0x00,0x00,0x00,0x00,0x00,0xFF,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(210) Ò + 0x66,0x66,0x66,0x66,0x66,0x7F,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(211) Ó + 0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x1F,0x00,0x00,0x00,0x00, // Chr$(212) Ô + 0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x1F,0x18,0x18,0x18,0x18, // Chr$(213) Õ + 0x00,0x00,0x00,0x00,0x00,0x7F,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(214) Ö + 0x66,0x66,0x66,0x66,0x66,0xE7,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(215) × + 0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0xFF,0x18,0x18,0x18,0x18, // Chr$(216) Ø + 0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(217) Ù + 0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(218) Ú + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // Chr$(219) Û + 0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // Chr$(220) Ü + 0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0, // Chr$(221) Ý + 0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // Chr$(222) Þ + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(223) ß + 0x00,0x00,0x00,0x00,0x3A,0x44,0x44,0x44,0x44,0x3A,0x00,0x00, // Chr$(224) à + 0x00,0x38,0x44,0x44,0x58,0x44,0x44,0x44,0x78,0x40,0x40,0x00, // Chr$(225) á + 0x00,0x00,0x7E,0x42,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00, // Chr$(226) â + 0x00,0x00,0x00,0x00,0x7C,0x28,0x28,0x28,0x28,0x68,0x00,0x00, // Chr$(227) ã + 0x00,0x7E,0x42,0x20,0x10,0x08,0x10,0x20,0x42,0x7E,0x00,0x00, // Chr$(228) ä + 0x00,0x00,0x00,0x00,0x3E,0x48,0x44,0x44,0x44,0x38,0x00,0x00, // Chr$(229) å + 0x00,0x00,0x00,0x24,0x24,0x24,0x24,0x3C,0x42,0x40,0x80,0x00, // Chr$(230) æ + 0x00,0x00,0x00,0x00,0x44,0xA8,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(231) ç + 0x00,0x00,0x7C,0x00,0x38,0x44,0x44,0x44,0x38,0x00,0x7C,0x00, // Chr$(232) è + 0x00,0x00,0x18,0x24,0x42,0x42,0x7E,0x42,0x42,0x24,0x18,0x00, // Chr$(233) é + 0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x6C,0x28,0x6C,0x00,0x00, // Chr$(234) ê + 0x00,0x00,0x00,0x1C,0x20,0x38,0x44,0x44,0x44,0x38,0x00,0x00, // Chr$(235) ë + 0x00,0x00,0x00,0x00,0x00,0x6C,0x92,0x92,0x6C,0x00,0x00,0x00, // Chr$(236) ì + 0x00,0x00,0x00,0x00,0x02,0x6C,0x9A,0xB2,0x6C,0x80,0x00,0x00, // Chr$(237) í + 0x00,0x18,0x20,0x40,0x40,0x78,0x40,0x40,0x20,0x18,0x00,0x00, // Chr$(238) î + 0x00,0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x00,0x00, // Chr$(239) ï + 0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x00, // Chr$(240) ð + 0x00,0x00,0x10,0x10,0x7C,0x10,0x10,0x00,0x7C,0x00,0x00,0x00, // Chr$(241) ñ + 0x00,0x20,0x10,0x08,0x04,0x08,0x10,0x20,0x00,0x7E,0x00,0x00, // Chr$(242) ò + 0x00,0x04,0x08,0x10,0x20,0x10,0x08,0x04,0x00,0x7E,0x00,0x00, // Chr$(243) ó + 0x40,0x41,0x42,0x44,0x48,0x10,0x26,0x49,0x02,0x04,0x0F,0x00, // Chr$(244) ô + 0x40,0x41,0x42,0x44,0x48,0x12,0x26,0x4A,0x0F,0x02,0x02,0x00, // Chr$(245) õ + 0x00,0x00,0x18,0x18,0x00,0x7E,0x00,0x18,0x18,0x00,0x00,0x00, // Chr$(246) ö + 0x00,0x00,0x22,0x52,0x4C,0x00,0x22,0x52,0x4C,0x00,0x00,0x00, // Chr$(247) ÷ + 0x00,0x20,0x10,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(248) ø + 0x00,0x00,0x00,0x00,0x1C,0x1C,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(249) ù + 0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(250) ú + 0x00,0x00,0x0E,0x08,0x08,0x08,0x08,0x48,0x28,0x18,0x08,0x00, // Chr$(251) û + 0x00,0x3C,0x12,0x12,0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(252) ü + 0x00,0x18,0x24,0x08,0x10,0x3C,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(253) ý + 0x00,0x00,0x00,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x00,0x00,0x00, // Chr$(254) þ + 0x00,0x00,0x10,0x54,0x28,0xC6,0x28,0x54,0x10,0x00,0x00,0x00 // Chr$(255) ÿ +}; diff --git a/Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c b/Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c new file mode 100644 index 0000000..59bbded --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c @@ -0,0 +1,754 @@ +#include +#include +#include + +#include +#include "hardware/timer.h" +#include +#include + +#include "lcdspi.h" +#include "i2ckbd.h" +#include "pico/multicore.h" +// Fonts + +#include "fonts/font1.h" +unsigned char *MainFont = (unsigned char *) font1; + +static int gui_fcolour; +static int gui_bcolour; +static short current_x = 0, current_y = 0; // the current default position for the next char to be written +static short gui_font_width, gui_font_height; +static short hres = 320; // Horizontal resolution for ILI9488 +static short vres = 320; // Vertical resolution for ILI9488 +static char s_height; +static char s_width; +int lcd_char_pos = 0; +unsigned char lcd_buffer[320 * 3] = {0};// 1440 = 480*3, 320*3 = 960 + +void __not_in_flash_func(spi_write_fast)(spi_inst_t *spi, const uint8_t *src, size_t len) { + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + for (size_t i = 0; i < len; ++i) { + while (!spi_is_writable(spi)) + tight_loop_contents(); + spi_get_hw(spi)->dr = (uint32_t) src[i]; + } +} + +void __not_in_flash_func(spi_finish)(spi_inst_t *spi) { + // Drain RX FIFO, then wait for shifting to finish (which may be *after* + // TX FIFO drains), then drain RX FIFO again + while (spi_is_readable(spi)) + (void) spi_get_hw(spi)->dr; + while (spi_get_hw(spi)->sr & SPI_SSPSR_BSY_BITS) + tight_loop_contents(); + while (spi_is_readable(spi)) + (void) spi_get_hw(spi)->dr; + + // Don't leave overrun flag set + spi_get_hw(spi)->icr = SPI_SSPICR_RORIC_BITS; +} + +void draw_line_spi(int x1, int y1, int x2, int y2, int color) { + int dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; + int dy = abs(y2 - y1), sy = y1 < y2 ? 1 : -1; + int err = (dx > dy ? dx : -dy) / 2, e2; + + while (1) { + draw_rect_spi(x1, y1, x1, y1, color); // Draw a single pixel + if (x1 == x2 && y1 == y2) break; + e2 = err; + if (e2 > -dx) { err -= dy; x1 += sx; } + if (e2 < dy) { err += dx; y1 += sy; } + } +} +void set_font() { + + gui_font_width = MainFont[0]; + gui_font_height = MainFont[1]; + + s_height = vres / gui_font_height; + s_width = hres / gui_font_width; +} + +void define_region_spi(int xstart, int ystart, int xend, int yend, int rw) { + unsigned char coord[4]; + lcd_spi_lower_cs(); + gpio_put(Pico_LCD_DC, 0); + hw_send_spi(&(uint8_t) {ILI9341_COLADDRSET}, 1); + gpio_put(Pico_LCD_DC, 1); + coord[0] = xstart >> 8; + coord[1] = xstart; + coord[2] = xend >> 8; + coord[3] = xend; + hw_send_spi(coord, 4); + gpio_put(Pico_LCD_DC, 0); + hw_send_spi(&(uint8_t) {ILI9341_PAGEADDRSET}, 1); + gpio_put(Pico_LCD_DC, 1); + coord[0] = ystart >> 8; + coord[1] = ystart; + coord[2] = yend >> 8; + coord[3] = yend; + hw_send_spi(coord, 4); + gpio_put(Pico_LCD_DC, 0); + if (rw) { + hw_send_spi(&(uint8_t) {ILI9341_MEMORYWRITE}, 1); + } else { + hw_send_spi(&(uint8_t) {ILI9341_RAMRD}, 1); + } + gpio_put(Pico_LCD_DC, 1); +} + +void read_buffer_spi(int x1, int y1, int x2, int y2, unsigned char *p) { + int r, N, t; + unsigned char h, l; + // make sure the coordinates are kept within the display area + if (x2 <= x1) { + t = x1; + x1 = x2; + x2 = t; + } + if (y2 <= y1) { + t = y1; + y1 = y2; + y2 = t; + } + if (x1 < 0) x1 = 0; + if (x1 >= hres) x1 = hres - 1; + if (x2 < 0) x2 = 0; + if (x2 >= hres) x2 = hres - 1; + if (y1 < 0) y1 = 0; + if (y1 >= vres) y1 = vres - 1; + if (y2 < 0) y2 = 0; + if (y2 >= vres) y2 = vres - 1; + N = (x2 - x1 + 1) * (y2 - y1 + 1) * 3; + + define_region_spi(x1, y1, x2, y2, 0); + + spi_set_baudrate(Pico_LCD_SPI_MOD, 6000000); + hw_read_spi((uint8_t *) p, 1); + r = 0; + hw_read_spi((uint8_t *) p, N); + gpio_put(Pico_LCD_DC, 0); + lcd_spi_raise_cs(); + spi_set_baudrate(Pico_LCD_SPI_MOD, LCD_SPI_SPEED); + r = 0; + + while (N) { + h = (uint8_t) p[r + 2]; + l = (uint8_t) p[r]; + p[r] = h;//(h & 0xF8); + p[r + 2] = l;//(l & 0xF8); + r += 3; + N -= 3; + } +} + +void draw_buffer_spi(int x1, int y1, int x2, int y2, unsigned char *p) { + int i, t; + unsigned char q[3]; + + // Boundary checking + if (x2 <= x1) { + t = x1; + x1 = x2; + x2 = t; + } + if (y2 <= y1) { + t = y1; + y1 = y2; + y2 = t; + } + if (x1 < 0) x1 = 0; + if (x1 >= hres) x1 = hres - 1; + if (x2 < 0) x2 = 0; + if (x2 >= hres) x2 = hres - 1; + if (y1 < 0) y1 = 0; + if (y1 >= vres) y1 = vres - 1; + if (y2 < 0) y2 = 0; + if (y2 >= vres) y2 = vres - 1; + + // Calculate total number of pixels + int pixelCount = (x2 - x1 + 1) * (y2 - y1 + 1); + uint16_t *pixelBuffer = (uint16_t *)p; + + define_region_spi(x1, y1, x2, y2, 1); + + for (i = 0; i < pixelCount; i++) { + uint16_t pixel = pixelBuffer[i]; + + // Extract RGB565 components + uint8_t r5 = (pixel >> 11) & 0x1F; + uint8_t g6 = (pixel >> 5) & 0x3F; + uint8_t b5 = pixel & 0x1F; + + // Convert to 8-bit values (scaling approximation) + uint8_t r8 = (r5 << 3) | (r5 >> 2); + uint8_t g8 = (g6 << 2) | (g6 >> 4); + uint8_t b8 = (b5 << 3) | (b5 >> 2); + +#ifdef ILI9488 + // Convert each RGB565 pixel to RGB888 (3 bytes per pixel) for ILI9488 + uint8_t rgb[3]; + rgb[0] = r8; // Red + rgb[1] = g8; // Green + rgb[2] = b8; // Blue + hw_send_spi(rgb, 3); +#else + // For other controllers or if using 16-bit mode, retain the original conversion + hw_send_spi(q, 2); +#endif + } + + lcd_spi_raise_cs(); +} + +//Print the bitmap of a char on the video output +// x, y - the top left of the char +// width, height - size of the char's bitmap +// scale - how much to scale the bitmap +// fc, bc - foreground and background colour +// bitmap - pointer to the bitmap +void draw_bitmap_spi(int x1, int y1, int width, int height, int scale, int fc, int bc, unsigned char *bitmap) { + int i, j, k, m, n; + char f[3], b[3]; + int vertCoord, horizCoord, XStart, XEnd, YEnd; + char *p = 0; + union colourmap { + char rgbbytes[4]; + unsigned int rgb; + } c; + + if (x1 >= hres || y1 >= vres || x1 + width * scale < 0 || y1 + height * scale < 0)return; + // adjust when part of the bitmap is outside the displayable coordinates + vertCoord = y1; + if (y1 < 0) y1 = 0; // the y coord is above the top of the screen + XStart = x1; + if (XStart < 0) XStart = 0; // the x coord is to the left of the left marginn + XEnd = x1 + (width * scale) - 1; + if (XEnd >= hres) XEnd = hres - 1; // the width of the bitmap will extend beyond the right margin + YEnd = y1 + (height * scale) - 1; + if (YEnd >= vres) YEnd = vres - 1;// the height of the bitmap will extend beyond the bottom margin + +#ifdef ILI9488 + // convert the colours to 565 format + f[0] = (fc >> 16); + f[1] = (fc >> 8) & 0xFF; + f[2] = (fc & 0xFF); + b[0] = (bc >> 16); + b[1] = (bc >> 8) & 0xFF; + b[2] = (bc & 0xFF); + +#endif + define_region_spi(XStart, y1, XEnd, YEnd, 1); + + n = 0; + for (i = 0; i < height; i++) { // step thru the font scan line by line + for (j = 0; j < scale; j++) { // repeat lines to scale the font + if (vertCoord++ < 0) continue; // we are above the top of the screen + if (vertCoord > vres) { // we have extended beyond the bottom of the screen + lcd_spi_raise_cs(); //set CS high + return; + } + horizCoord = x1; + for (k = 0; k < width; k++) { // step through each bit in a scan line + for (m = 0; m < scale; m++) { // repeat pixels to scale in the x axis + if (horizCoord++ < 0) continue; // we have not reached the left margin + if (horizCoord > hres) continue; // we are beyond the right margin + if ((bitmap[((i * width) + k) / 8] >> (((height * width) - ((i * width) + k) - 1) % 8)) & 1) { + hw_send_spi((uint8_t *) &f, 3); + } else { + if (bc == -1) { + c.rgbbytes[0] = p[n]; + c.rgbbytes[1] = p[n + 1]; + c.rgbbytes[2] = p[n + 2]; +#ifdef ILI9488 + b[0] = c.rgbbytes[2]; + b[1] = c.rgbbytes[1]; + b[2] = c.rgbbytes[0]; +#endif + } + hw_send_spi((uint8_t *) &b, 3); + } + n += 3; + } + } + } + } + lcd_spi_raise_cs(); //set CS high + +} + +// Draw a filled rectangle +// this is the basic drawing promitive used by most drawing routines +// x1, y1, x2, y2 - the coordinates +// c - the colour +void draw_rect_spi(int x1, int y1, int x2, int y2, int c) { + // convert the colours to 565 format + unsigned char col[3]; + if (x1 == x2 && y1 == y2) { + if (x1 < 0) return; + if (x1 >= hres) return; + if (y1 < 0) return; + if (y1 >= vres) return; + define_region_spi(x1, y1, x2, y2, 1); +#ifdef ILI9488 + col[0] = (c >> 16); + col[1] = (c >> 8) & 0xFF; + col[2] = (c & 0xFF); +#endif + hw_send_spi(col, 3); + } else { + int i, t, y; + unsigned char *p; + // make sure the coordinates are kept within the display area + if (x2 <= x1) { + t = x1; + x1 = x2; + x2 = t; + } + if (y2 <= y1) { + t = y1; + y1 = y2; + y2 = t; + } + if (x1 < 0) x1 = 0; + if (x1 >= hres) x1 = hres - 1; + if (x2 < 0) x2 = 0; + if (x2 >= hres) x2 = hres - 1; + if (y1 < 0) y1 = 0; + if (y1 >= vres) y1 = vres - 1; + if (y2 < 0) y2 = 0; + if (y2 >= vres) y2 = vres - 1; + define_region_spi(x1, y1, x2, y2, 1); +#ifdef ILI9488 + i = x2 - x1 + 1; + i *= 3; + p = lcd_buffer; + col[0] = (c >> 16); + col[1] = (c >> 8) & 0xFF; + col[2] = (c & 0xFF); + for (t = 0; t < i; t += 3) { + p[t] = col[0]; + p[t + 1] = col[1]; + p[t + 2] = col[2]; + } + for (y = y1; y <= y2; y++) { + spi_write_fast(Pico_LCD_SPI_MOD, p, i); + } +#endif + } + spi_finish(Pico_LCD_SPI_MOD); + lcd_spi_raise_cs(); +} + +/****************************************************************************************** + Print a char on the LCD display + Any characters not in the font will print as a space. + The char is printed at the current location defined by current_x and current_y +*****************************************************************************************/ +void lcd_print_char( int fc, int bc, char c, int orientation) { + unsigned char *p, *fp, *np = NULL; + int modx, mody, scale = 0x01; + int height, width; + + // to get the +, - and = chars for font 6 we fudge them by scaling up font 1 + fp = (unsigned char *) MainFont; + + height = fp[1]; + width = fp[0]; + modx = mody = 0; + + if (c >= fp[2] && c < fp[2] + fp[3]) { + p = fp + 4 + (int) (((c - fp[2]) * height * width) / 8); + np = p; + + draw_bitmap_spi(current_x + modx, current_y + mody, width, height, scale, fc, bc, np); + } else { + draw_rect_spi(current_x + modx, current_y + mody, current_x + modx + (width * scale), + current_y + mody + (height * scale), bc); + } + + if (orientation == ORIENT_NORMAL) current_x += width * scale; + +} + +unsigned char scrollbuff[LCD_WIDTH * 3]; + +void scroll_lcd_spi(int lines) { + if (lines == 0)return; + if (lines >= 0) { + for (int i = 0; i < vres - lines; i++) { + read_buffer_spi(0, i + lines, hres - 1, i + lines, scrollbuff); + draw_buffer_spi(0, i, hres - 1, i, scrollbuff); + } + draw_rect_spi(0, vres - lines, hres - 1, vres - 1, gui_bcolour); // erase the lines to be scrolled off + } else { + lines = -lines; + for (int i = vres - 1; i >= lines; i--) { + read_buffer_spi(0, i - lines, hres - 1, i - lines, scrollbuff); + draw_buffer_spi(0, i, hres - 1, i, scrollbuff); + } + draw_rect_spi(0, 0, hres - 1, lines - 1, gui_bcolour); // erase the lines introduced at the top + } + +} + +void display_put_c(char c) { + // if it is printable and it is going to take us off the right hand end of the screen do a CRLF + if (c >= MainFont[2] && c < MainFont[2] + MainFont[3]) { + if (current_x + gui_font_width > hres) { + display_put_c('\r'); + display_put_c('\n'); + } + } + + // handle the standard control chars + switch (c) { + case '\b': + current_x -= gui_font_width; + if (current_x < 0) { //Go to end of previous line + current_y -= gui_font_height; //Go up one line + if (current_y < 0) current_y = 0; + current_x = (s_width - 1) * gui_font_width; //go to last character + } + return; + case '\r': + current_x = 0; + return; + case '\n': + current_x = 0; + current_y += gui_font_height; + if (current_y + gui_font_height >= vres) { + scroll_lcd_spi(current_y + gui_font_height - vres); + current_y -= (current_y + gui_font_height - vres); + } + return; + case '\t': + do { + display_put_c(' '); + } while ((current_x / gui_font_width) % 2);// 2 3 4 8 + return; + } + lcd_print_char(gui_fcolour, gui_bcolour, c, ORIENT_NORMAL);// print it +} + +char lcd_put_char(char c, int flush) { + lcd_putc(0, c); + if (isprint(c)) lcd_char_pos++; + if (c == '\r') { + lcd_char_pos = 1; + } + return c; +} + +void lcd_print_string(char *s) { + while (*s) { + if (s[1])lcd_put_char(*s, 0); + else lcd_put_char(*s, 1); + s++; + } + fflush(stdout); +} + +void lcd_print_string_color(char *s, int fg, int bg) { + int old_fg = gui_fcolour; + int old_bg = gui_bcolour; + + gui_fcolour = fg; + gui_bcolour = bg; + + while (*s) { + if (s[1]) lcd_put_char(*s, 0); + else lcd_put_char(*s, 1); + s++; + } + + gui_fcolour = old_fg; + gui_bcolour = old_bg; + + fflush(stdout); +} + +void lcd_clear() { + draw_rect_spi(0, 0, hres - 1, vres - 1, BLACK); +} + +void lcd_putc(uint8_t devn, uint8_t c) { + display_put_c(c); +} + +int lcd_getc(uint8_t devn) { + //i2c keyboard + int c = read_i2c_kbd(); + return c; +} + +unsigned char __not_in_flash_func(hw1_swap_spi)(unsigned char data_out) { + unsigned char data_in = 0; + spi_write_read_blocking(spi1, &data_out, &data_in, 1); + return data_in; +} + +void hw_read_spi(unsigned char *buff, int cnt) { + spi_read_blocking(Pico_LCD_SPI_MOD, 0xff, buff, cnt); +} + +void hw_send_spi(const unsigned char *buff, int cnt) { + + spi_write_blocking(Pico_LCD_SPI_MOD, buff, cnt); + +} + +void pin_set_bit(int pin, unsigned int offset) { + switch (offset) { + case LATCLR: + gpio_set_pulls(pin, false, false); + gpio_pull_down(pin); + gpio_put(pin, 0); + return; + case LATSET: + gpio_set_pulls(pin, false, false); + gpio_pull_up(pin); + gpio_put(pin, 1); + return; + case LATINV: + gpio_xor_mask(1 << pin); + return; + case TRISSET: + gpio_set_dir(pin, GPIO_IN); + sleep_us(2); + return; + case TRISCLR: + gpio_set_dir(pin, GPIO_OUT); + gpio_set_drive_strength(pin, GPIO_DRIVE_STRENGTH_12MA); + sleep_us(2); + return; + case CNPUSET: + gpio_set_pulls(pin, true, false); + return; + case CNPDSET: + gpio_set_pulls(pin, false, true); + return; + case CNPUCLR: + case CNPDCLR: + gpio_set_pulls(pin, false, false); + return; + case ODCCLR: + gpio_set_dir(pin, GPIO_OUT); + gpio_put(pin, 0); + sleep_us(2); + return; + case ODCSET: + gpio_set_pulls(pin, true, false); + gpio_set_dir(pin, GPIO_IN); + sleep_us(2); + return; + case ANSELCLR: + gpio_set_function(pin, GPIO_FUNC_SIO); + gpio_set_dir(pin, GPIO_IN); + return; + default: + break; + } +} + +//important for read lcd memory +void reset_controller(void) { + pin_set_bit(Pico_LCD_RST, LATSET); + sleep_us(10000); + pin_set_bit(Pico_LCD_RST, LATCLR); + sleep_us(10000); + pin_set_bit(Pico_LCD_RST, LATSET); + sleep_us(200000); +} + + +void pico_lcd_init() { +#ifdef ILI9488 + reset_controller(); + + hres = 320; + vres = 320; + + spi_write_command(0xE0); // Positive Gamma Control + spi_write_data(0x00); + spi_write_data(0x03); + spi_write_data(0x09); + spi_write_data(0x08); + spi_write_data(0x16); + spi_write_data(0x0A); + spi_write_data(0x3F); + spi_write_data(0x78); + spi_write_data(0x4C); + spi_write_data(0x09); + spi_write_data(0x0A); + spi_write_data(0x08); + spi_write_data(0x16); + spi_write_data(0x1A); + spi_write_data(0x0F); + + spi_write_command(0XE1); // Negative Gamma Control + spi_write_data(0x00); + spi_write_data(0x16); + spi_write_data(0x19); + spi_write_data(0x03); + spi_write_data(0x0F); + spi_write_data(0x05); + spi_write_data(0x32); + spi_write_data(0x45); + spi_write_data(0x46); + spi_write_data(0x04); + spi_write_data(0x0E); + spi_write_data(0x0D); + spi_write_data(0x35); + spi_write_data(0x37); + spi_write_data(0x0F); + + spi_write_command(0XC0); // Power Control 1 + spi_write_data(0x17); + spi_write_data(0x15); + + spi_write_command(0xC1); // Power Control 2 + spi_write_data(0x41); + + spi_write_command(0xC5); // VCOM Control + spi_write_data(0x00); + spi_write_data(0x12); + spi_write_data(0x80); + + spi_write_command(TFT_MADCTL); // Memory Access Control + spi_write_data(0x48); // MX, BGR + + spi_write_command(0x3A); // Pixel Interface Format + spi_write_data(0x66); // 18/24-bit colour for SPI (RGB666/RGB888) + + spi_write_command(0xB0); // Interface Mode Control + spi_write_data(0x00); + + spi_write_command(0xB1); // Frame Rate Control + spi_write_data(0xA0); + + spi_write_command(TFT_INVON); + + spi_write_command(0xB4); // Display Inversion Control + spi_write_data(0x02); + + spi_write_command(0xB6); // Display Function Control + spi_write_data(0x02); + spi_write_data(0x02); + spi_write_data(0x3B); + + spi_write_command(0xB7); // Entry Mode Set + spi_write_data(0xC6); + spi_write_command(0xE9); + spi_write_data(0x00); + + spi_write_command(0xF7); // Adjust Control 3 + spi_write_data(0xA9); + spi_write_data(0x51); + spi_write_data(0x2C); + spi_write_data(0x82); + + spi_write_command(TFT_SLPOUT); //Exit Sleep + sleep_ms(120); + + spi_write_command(TFT_DISPON); //Display on + sleep_ms(120); + + spi_write_command(TFT_MADCTL); + spi_write_cd(ILI9341_MEMCONTROL, 1, ILI9341_Portrait); +#endif +} + +void lcd_spi_raise_cs(void) { + gpio_put(Pico_LCD_CS, 1); +} + +void lcd_set_cursor(int x, int y) { + current_x = x; + current_y = y; +} + +void lcd_spi_lower_cs(void) { + + gpio_put(Pico_LCD_CS, 0); + +} + +void spi_write_data(unsigned char data) { + gpio_put(Pico_LCD_DC, 1); + lcd_spi_lower_cs(); + hw_send_spi(&data, 1); + lcd_spi_raise_cs(); +} + +void spi_write_data24(uint32_t data) { + uint8_t data_array[3]; + data_array[0] = data >> 16; + data_array[1] = (data >> 8) & 0xFF; + data_array[2] = data & 0xFF; + + + gpio_put(Pico_LCD_DC, 1); // Data mode + gpio_put(Pico_LCD_CS, 0); + spi_write_blocking(Pico_LCD_SPI_MOD, data_array, 3); + gpio_put(Pico_LCD_CS, 1); +} + +void spi_write_command(unsigned char data) { + gpio_put(Pico_LCD_DC, 0); + gpio_put(Pico_LCD_CS, 0); + + spi_write_blocking(Pico_LCD_SPI_MOD, &data, 1); + + gpio_put(Pico_LCD_CS, 1); +} + +void spi_write_cd(unsigned char command, int data, ...) { + int i; + va_list ap; + va_start(ap, data); + spi_write_command(command); + for (i = 0; i < data; i++) spi_write_data((char) va_arg(ap, int)); + va_end(ap); +} + +void lcd_spi_init() { + // init GPIO + gpio_init(Pico_LCD_SCK); + gpio_init(Pico_LCD_TX); + gpio_init(Pico_LCD_RX); + gpio_init(Pico_LCD_CS); + gpio_init(Pico_LCD_DC); + gpio_init(Pico_LCD_RST); + + gpio_set_dir(Pico_LCD_SCK, GPIO_OUT); + gpio_set_dir(Pico_LCD_TX, GPIO_OUT); + gpio_set_dir(Pico_LCD_CS, GPIO_OUT); + gpio_set_dir(Pico_LCD_DC, GPIO_OUT); + gpio_set_dir(Pico_LCD_RST, GPIO_OUT); + + // init SPI + spi_init(Pico_LCD_SPI_MOD, LCD_SPI_SPEED); + gpio_set_function(Pico_LCD_SCK, GPIO_FUNC_SPI); + gpio_set_function(Pico_LCD_TX, GPIO_FUNC_SPI); + gpio_set_function(Pico_LCD_RX, GPIO_FUNC_SPI); + gpio_set_input_hysteresis_enabled(Pico_LCD_RX, true); + + gpio_put(Pico_LCD_CS, 1); + gpio_put(Pico_LCD_RST, 1); +} + + +void lcd_init() { + lcd_spi_init(); + pico_lcd_init(); + + set_font(); + gui_fcolour = WHITE; + gui_bcolour = GRAY; + +} diff --git a/Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h b/Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h new file mode 100644 index 0000000..7775995 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h @@ -0,0 +1,148 @@ +#ifndef LCDSPI_H +#define LCDSPI_H +#include "pico/multicore.h" +#include + +//#define LCD_SPI_SPEED 6000000 +#define LCD_SPI_SPEED 25000000 +//#define LCD_SPI_SPEED 50000000 + +#define Pico_LCD_SCK 10 // +#define Pico_LCD_TX 11 // MOSI +#define Pico_LCD_RX 12 // MISO +#define Pico_LCD_CS 13 // +#define Pico_LCD_DC 14 +#define Pico_LCD_RST 15 + +#define ILI9488 1 +#ifdef ILI9488 +#define LCD_WIDTH 320 +#define LCD_HEIGHT 320 +#endif + +#define PIXFMT_BGR 1 + +#define TFT_SLPOUT 0x11 +#define TFT_INVOFF 0x20 +#define TFT_INVON 0x21 + +#define TFT_DISPOFF 0x28 +#define TFT_DISPON 0x29 +#define TFT_MADCTL 0x36 + +#define ILI9341_MEMCONTROL 0x36 +#define ILI9341_MADCTL_MX 0x40 +#define ILI9341_MADCTL_BGR 0x08 + +#define ILI9341_COLADDRSET 0x2A +#define ILI9341_PAGEADDRSET 0x2B +#define ILI9341_MEMORYWRITE 0x2C +#define ILI9341_RAMRD 0x2E + +#define ILI9341_Portrait ILI9341_MADCTL_MX | ILI9341_MADCTL_BGR + +#define ORIENT_NORMAL 0 + +#define RGB(red, green, blue) (unsigned int) (((red & 0b11111111) << 16) | ((green & 0b11111111) << 8) | (blue & 0b11111111)) +#define WHITE RGB(255, 255, 255) //0b1111 +#define YELLOW RGB(255, 255, 0) //0b1110 +#define LILAC RGB(255, 128, 255) //0b1101 +#define BROWN RGB(255, 128, 0) //0b1100 +#define FUCHSIA RGB(255, 64, 255) //0b1011 +#define RUST RGB(255, 64, 0) //0b1010 +#define MAGENTA RGB(255, 0, 255) //0b1001 +#define RED RGB(255, 0, 0) //0b1000 +#define CYAN RGB(0, 255, 255) //0b0111 +#define GREEN RGB(0, 255, 0) //0b0110 +#define CERULEAN RGB(0, 128, 255) //0b0101 +#define MIDGREEN RGB(0, 128, 0) //0b0100 +#define COBALT RGB(0, 64, 255) //0b0011 +#define MYRTLE RGB(0, 64, 0) //0b0010 +#define BLUE RGB(0, 0, 255) //0b0001 +#define BLACK RGB(0, 0, 0) //0b0000 +#define BROWN RGB(255, 128, 0) +#define GRAY RGB(128, 128, 128) +#define LITEGRAY RGB(210, 210, 210) +#define ORANGE RGB(0xff, 0xA5, 0) +#define PINK RGB(0xFF, 0xA0, 0xAB) +#define GOLD RGB(0xFF, 0xD7, 0x00) +#define SALMON RGB(0xFA, 0x80, 0x72) +#define BEIGE RGB(0xF5, 0xF5, 0xDC) + +//Pico spi0 or spi1 must match GPIO pins used above. +#define Pico_LCD_SPI_MOD spi1 +#define nop asm("NOP") +//xmit_byte_multi == HW1SendSPI + + +#define PORTCLR 1 +#define PORTSET 2 +#define PORTINV 3 +#define LAT 4 +#define LATCLR 5 +#define LATSET 6 +#define LATINV 7 +#define ODC 8 +#define ODCCLR 9 +#define ODCSET 10 +#define CNPU 12 +#define CNPUCLR 13 +#define CNPUSET 14 +#define CNPUINV 15 +#define CNPD 16 +#define CNPDCLR 17 +#define CNPDSET 18 + +#define ANSELCLR -7 +#define ANSELSET -6 +#define ANSELINV -5 +#define TRIS -4 +#define TRISCLR -3 +#define TRISSET -2 + +extern void __not_in_flash_func(spi_write_fast)(spi_inst_t *spi, const uint8_t *src, size_t len); +extern void __not_in_flash_func(spi_finish)(spi_inst_t *spi); +extern void hw_read_spi(unsigned char *buff, int cnt); +extern void hw_send_spi(const unsigned char *buff, int cnt); +extern unsigned char __not_in_flash_func(hw1_swap_spi)(unsigned char data_out); + +extern void lcd_spi_raise_cs(void); +extern void lcd_spi_lower_cs(void); +extern void spi_write_data(unsigned char data); +extern void spi_write_command(unsigned char data); +extern void spi_write_cd(unsigned char command, int data, ...); +extern void spi_write_data24(uint32_t data); + +extern void spi_draw_pixel(uint16_t x, uint16_t y, uint16_t color) ; +extern void lcd_putc(uint8_t devn, uint8_t c); +extern int lcd_getc(uint8_t devn); +extern void lcd_sleeping(uint8_t devn); + + +void draw_rect_spi(int x1, int y1, int x2, int y2, int c); +void define_region_spi(int xstart, int ystart, int xend, int yend, int rw); +void draw_line_spi(int x1, int y1, int x2, int y2, int color); +void lcd_print_string_color(char *s, int fg, int bg); + +//Print the bitmap of a char on the video output +// x, y - the top left of the char +// width, height - size of the char's bitmap +// scale - how much to scale the bitmap +// fc, bc - foreground and background colour +// bitmap - pointer to the bitmap +void draw_bitmap_spi(int x1, int y1, int width, int height, int scale, int fc, int bc, unsigned char *bitmap); +void draw_buffer_spi(int x1, int y1, int x2, int y2, unsigned char *p); + + +extern char lcd_put_char(char c, int flush); +extern void lcd_print_string(char* s); + +extern void lcd_spi_init(); +extern void lcd_init(); +extern void lcd_clear(); +extern void reset_controller(void); +extern void pin_set_bit(int pin, unsigned int offset); + +extern void lcd_set_cursor(int x, int y); + +#endif diff --git a/Code/pico_multi_booter/sd_boot/main.c b/Code/pico_multi_booter/sd_boot/main.c new file mode 100644 index 0000000..4ece188 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/main.c @@ -0,0 +1,332 @@ +/** + * PicoCalc SD Firmware Loader + * + * Author: Hsuan Han Lai + * Email: hsuan.han.lai@gmail.com + * Website: https://hsuanhanlai.com + * Year: 2025 + * + * + * This project is a bootloader for the PicoCalc device, designed to load and execute + * firmware applications from an SD card. + * + */ + +#include +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/clocks.h" +#include "debug.h" +#include "i2ckbd.h" +#include "lcdspi.h" +#include +#include +#include +#include "config.h" + +#include "blockdevice/sd.h" +#include "filesystem/fat.h" +#include "filesystem/vfs.h" +#include "text_directory_ui.h" +#include "key_event.h" + +const uint LEDPIN = 25; + +// Vector and RAM offset +#if PICO_RP2040 +#define VTOR_OFFSET M0PLUS_VTOR_OFFSET +#define MAX_RAM 0x20040000 +#elif PICO_RP2350 +#define VTOR_OFFSET M33_VTOR_OFFSET +#define MAX_RAM 0x20080000 +#endif + +bool sd_card_inserted(void) +{ + // Active low detection - returns true when pin is low + return !gpio_get(SD_DET_PIN); +} + +bool fs_init(void) +{ + DEBUG_PRINT("fs init SD\n"); + blockdevice_t *sd = blockdevice_sd_create(spi0, + SD_MOSI_PIN, + SD_MISO_PIN, + SD_SCLK_PIN, + SD_CS_PIN, + 125000000 / 2 / 4, // 15.6MHz + true); + filesystem_t *fat = filesystem_fat_create(); + int err = fs_mount("/", fat, sd); + if (err == -1) + { + DEBUG_PRINT("format /\n"); + err = fs_format(fat, sd); + if (err == -1) + { + DEBUG_PRINT("format err: %s\n", strerror(errno)); + return false; + } + err = fs_mount("/", fat, sd); + if (err == -1) + { + DEBUG_PRINT("mount err: %s\n", strerror(errno)); + return false; + } + } + return true; +} + +static bool __not_in_flash_func(is_same_existing_program)(FILE *fp) +{ + uint8_t buffer[FLASH_SECTOR_SIZE] = {0}; + size_t program_size = 0; + size_t len = 0; + while ((len = fread(buffer, 1, sizeof(buffer), fp)) > 0) + { + uint8_t *flash = (uint8_t *)(XIP_BASE + SD_BOOT_FLASH_OFFSET + program_size); + if (memcmp(buffer, flash, len) != 0) + return false; + program_size += len; + } + return true; +} + +// This function must run from RAM since it erases and programs flash memory +static bool __not_in_flash_func(load_program)(const char *filename) +{ + FILE *fp = fopen(filename, "r"); + if (fp == NULL) + { + DEBUG_PRINT("open %s fail: %s\n", filename, strerror(errno)); + return false; + } + if (is_same_existing_program(fp)) + { + // Program is up to date + } + + // Check file size to ensure it doesn't exceed the available flash space + if (fseek(fp, 0, SEEK_END) == -1) + { + DEBUG_PRINT("seek err: %s\n", strerror(errno)); + fclose(fp); + return false; + } + + long file_size = ftell(fp); + if (file_size <= 0) + { + DEBUG_PRINT("invalid size: %ld\n", file_size); + fclose(fp); + return false; + } + + if (file_size > MAX_APP_SIZE) + { + DEBUG_PRINT("file too large: %ld > %d\n", file_size, MAX_APP_SIZE); + fclose(fp); + return false; + } + + DEBUG_PRINT("updating: %ld bytes\n", file_size); + if (fseek(fp, 0, SEEK_SET) == -1) + { + DEBUG_PRINT("seek err: %s\n", strerror(errno)); + fclose(fp); + return false; + } + + size_t program_size = 0; + uint8_t buffer[FLASH_SECTOR_SIZE] = {0}; + size_t len = 0; + + // Erase and program flash in FLASH_SECTOR_SIZE chunks + while ((len = fread(buffer, 1, sizeof(buffer), fp)) > 0) + { + // Ensure we don't write beyond the application area + if ((program_size + len) > MAX_APP_SIZE) + { + DEBUG_PRINT("err: write beyond app area\n"); + fclose(fp); + return false; + } + + uint32_t ints = save_and_disable_interrupts(); + flash_range_erase(SD_BOOT_FLASH_OFFSET + program_size, FLASH_SECTOR_SIZE); + flash_range_program(SD_BOOT_FLASH_OFFSET + program_size, buffer, len); + restore_interrupts(ints); + + program_size += len; + } + DEBUG_PRINT("program loaded\n"); + fclose(fp); + return true; +} + +// This function jumps to the application entry point +// It must update the vector table and stack pointer before jumping +void __not_in_flash_func(launch_application_from)(uint32_t *app_location) +{ + // https://vanhunteradams.com/Pico/Bootloader/Bootloader.html + uint32_t *new_vector_table = app_location; + volatile uint32_t *vtor = (uint32_t *)(PPB_BASE + VTOR_OFFSET); + *vtor = (uint32_t)new_vector_table; + asm volatile( + "msr msp, %0\n" + "bx %1\n" + : + : "r"(new_vector_table[0]), "r"(new_vector_table[1]) + :); +} + +// Check if a valid application exists in flash by examining the vector table +static bool is_valid_application(uint32_t *app_location) +{ + // Check that the initial stack pointer is within a plausible RAM region (assumed range for Pico: 0x20000000 to 0x20040000) + uint32_t stack_pointer = app_location[0]; + if (stack_pointer < 0x20000000 || stack_pointer > MAX_RAM) + { + return false; + } + + // Check that the reset vector is within the valid flash application area + uint32_t reset_vector = app_location[1]; + if (reset_vector < (0x10000000 + SD_BOOT_FLASH_OFFSET) || reset_vector > (0x10000000 + PICO_FLASH_SIZE_BYTES)) + { + return false; + } + return true; +} + +int load_firmware_by_path(const char *path) +{ + text_directory_ui_set_status("STAT: loading app..."); + + // Attempt to load the application from the SD card + // bool load_success = load_program(FIRMWARE_PATH); + bool load_success = load_program(path); + + // Get the pointer to the application flash area + uint32_t *app_location = (uint32_t *)(XIP_BASE + SD_BOOT_FLASH_OFFSET); + + // Check if there is an already valid application in flash + bool has_valid_app = is_valid_application(app_location); + + + + if (load_success || has_valid_app) + { + text_directory_ui_set_status("STAT: launching app..."); + DEBUG_PRINT("launching app\n"); + // Small delay to allow printf to complete + sleep_ms(100); + launch_application_from(app_location); + } + else + { + text_directory_ui_set_status("ERR: No valid app"); + DEBUG_PRINT("no valid app, halting\n"); + + sleep_ms(2000); + + // Trigger a watchdog reboot + watchdog_reboot(0, 0, 0); + } + + // We should never reach here + while (1) + { + tight_loop_contents(); + } +} + +void final_selection_callback(const char *path) +{ + // Trigger firmware loading with the selected path + DEBUG_PRINT("selected: %s\n", path); + + char status_message[128]; + const char *extension = ".bin"; + size_t path_len = strlen(path); + size_t ext_len = strlen(extension); + + if (path_len < ext_len || strcmp(path + path_len - ext_len, extension) != 0) + { + DEBUG_PRINT("not a bin: %s\n", path); + snprintf(status_message, sizeof(status_message), "Err: FILE is not a .bin file"); + text_directory_ui_set_status(status_message); + return; + } + + snprintf(status_message, sizeof(status_message), "SEL: %s", path); + text_directory_ui_set_status(status_message); + + sleep_ms(200); + + load_firmware_by_path(path); +} + +int main() +{ + char buf[64]; + stdio_init_all(); + + uart_init(uart0, 115200); + uart_set_format(uart0, 8, 1, UART_PARITY_NONE); // 8-N-1 + uart_set_fifo_enabled(uart0, false); + + // Initialize SD card detection pin + gpio_init(SD_DET_PIN); + gpio_set_dir(SD_DET_PIN, GPIO_IN); + gpio_pull_up(SD_DET_PIN); // Enable pull-up resistor + + keypad_init(); + lcd_init(); + lcd_clear(); + text_directory_ui_init(); + + // Check for SD card presence + DEBUG_PRINT("Checking for SD card...\n"); + if (!sd_card_inserted()) + { + DEBUG_PRINT("SD card not detected\n"); + text_directory_ui_set_status("SD card not detected. \nPlease insert SD card."); + + // Poll until SD card is inserted + while (!sd_card_inserted()) + { + sleep_ms(100); + } + + // Card detected, wait for it to stabilize + DEBUG_PRINT("SD card detected\n"); + text_directory_ui_set_status("SD card detected. Mounting..."); + sleep_ms(1500); // Wait for card to stabilize + } + else + { + // If SD card is detected at boot, wait for stabilization + DEBUG_PRINT("SD card stabilization delay on boot\n"); + text_directory_ui_set_status("Stabilizing SD card..."); + sleep_ms(1500); // Delay to allow the SD card to fully power up and stabilize + } + + // Initialize filesystem + if (!fs_init()) + { + text_directory_ui_set_status("Failed to mount SD card!"); + DEBUG_PRINT("Failed to mount SD card\n"); + sleep_ms(2000); + watchdog_reboot(0, 0, 0); + } + + sleep_ms(500); + lcd_clear(); + + text_directory_ui_init(); + text_directory_ui_set_final_callback(final_selection_callback); + text_directory_ui_run(); +} diff --git a/Code/pico_multi_booter/sd_boot/text_directory_ui.c b/Code/pico_multi_booter/sd_boot/text_directory_ui.c new file mode 100644 index 0000000..f07c2e8 --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/text_directory_ui.c @@ -0,0 +1,516 @@ +/** + * PicoCalc SD Firmware Loader + * + * Author: Hsuan Han Lai + * Email: hsuan.han.lai@gmail.com + * Website: https://hsuanhanlai.com + * Year: 2025 + * + * text_directory_ui.c + * + * Implementation for the Text Directory UI Navigator. + * + * This module provides a text-based UI for navigating directories and files on an SD card. + * It uses lcdspi APIs for rendering, key_event APIs for input handling, and pico-vfs/standard POSIX APIs + * for filesystem operations. + * + * Features: + * - UI Initialization: Sets up the display, input handling, and mounts the SD card filesystem. + * - Directory Navigation: Allows navigation through directories and files using arrow keys. + * - File Selection: Invokes a callback when a file is selected. + * - Status Messages: Displays temporary status messages at the bottom of the UI. + */ + +#include +#include +#include +#include +#include "pico/stdlib.h" +#include "lcdspi/lcdspi.h" +#include "key_event.h" +#include "text_directory_ui.h" +#include "debug.h" +#include +#include + +// External functions for SD card handling +extern bool sd_card_inserted(void); +extern bool fs_init(void); + +// UI Layout Constants +#define UI_WIDTH 280 +#define UI_HEIGHT 280 +#define UI_X 20 // Offset from top-left +#define UI_Y 20 +#define HEADER_TITLE_HEIGHT 20 // Height for the title header +#define PATH_HEADER_HEIGHT 16 // Height for the current path display +#define STATUS_BAR_HEIGHT 16 // Height for the status bar + +// UI Colors +#define COLOR_BG BLACK +#define COLOR_FG WHITE +#define COLOR_HIGHLIGHT GRAY + +// Maximum number of directory entries +#define MAX_ENTRIES 128 + +// Data structure for directory entries +typedef struct +{ + char name[256]; + int is_dir; // 1 if directory, 0 if file + off_t file_size; // Size of the file in bytes +} dir_entry_t; + +// UI Layout Constants for file display +#define FILE_NAME_X (UI_X + 4) +#define FILE_NAME_AREA_WIDTH 200 +#define FILE_SIZE_X (UI_X + UI_WIDTH - 70) +#define FILE_SIZE_AREA_WIDTH 60 +#define CHAR_WIDTH 8 +#define FILE_NAME_VISIBLE_CHARS (FILE_NAME_AREA_WIDTH / CHAR_WIDTH) +#define SCROLL_DELAY_MS 300 + +// Global variables for UI state +static char current_path[512] = "/sd"; // Current directory path +static dir_entry_t entries[MAX_ENTRIES]; // Directory entries +static int entry_count = 0; // Number of entries in the current directory +static int selected_index = 0; // Currently selected entry index +static char status_message[256] = ""; // Status message +static uint32_t status_timestamp = 0; // Timestamp for status message +static final_selection_callback_t final_callback = NULL; // Callback for file selection +static uint32_t last_scrolling = 0; // for text scrolling in selected entry +// Forward declarations +static void ui_refresh(void); +static void load_directory(const char *path); +static void process_key_event(int key); +static void ui_draw_title(void); +static void ui_draw_path_header(void); +static void ui_draw_directory_list(void); +static void ui_draw_directory_entry(int entry_idx, int posY, int font_height, int is_selected); +static void ui_update_selected_entry(void); +static void ui_draw_status_bar(void); +static void format_file_size(off_t size, int is_dir, char *buf, size_t buf_size); +static void get_scrolling_text(const char *text, char *out, size_t out_size, int visible_chars); + +// Helper: Draw a filled rectangle +static void draw_filled_rect(int x, int y, int width, int height, int color) +{ + draw_rect_spi(x, y, x + width - 1, y + height - 1, color); +} + +// Helper: Draw text at a specific position +static void draw_text(int x, int y, const char *text, int foreground, int background) +{ + lcd_set_cursor(x, y); + lcd_print_string_color((char *)text, foreground, background); +} + +/** + * Format file size into human-readable string + * Converts raw byte count to KB or MB with appropriate suffix + */ +static void format_file_size(off_t size, int is_dir, char *buf, size_t buf_size) +{ + if (is_dir) + { + snprintf(buf, buf_size, "DIR"); + } + else if (size >= 1024 * 1024) + { + double mb = size / (1024.0 * 1024.0); + snprintf(buf, buf_size, "%.1fMB", mb); + } + else + { + int kb = size / 1024; + if (kb < 1) + kb = 1; + snprintf(buf, buf_size, "%dKB", kb); + } +} + +/** + * Create scrolling text for long filenames + * Creates a continuous scroll effect for text that exceeds visible area + */ +static void get_scrolling_text(const char *text, char *out, size_t out_size, int visible_chars) +{ + char scroll_buffer[512]; + snprintf(scroll_buffer, sizeof(scroll_buffer), "%s %s", text, text); + int scroll_len = strlen(scroll_buffer); + uint32_t time_ms = (time_us_64() / 1000) - last_scrolling; + int offset = (time_ms / SCROLL_DELAY_MS) % scroll_len; + + int i; + for (i = 0; i < visible_chars && i < out_size - 1; i++) + { + int idx = (offset + i) % scroll_len; + out[i] = scroll_buffer[idx]; + } + out[i] = '\0'; +} + +bool has_suffix(const char *filename, const char *suffix) { + size_t len_filename = strlen(filename); + size_t len_suffix = strlen(suffix); + if (len_filename < len_suffix) return false; + return strcmp(filename + len_filename - len_suffix, suffix) == 0; +} + +// Load directory entries into the global entries array +static void load_directory(const char *path) +{ + DIR *dir = opendir(path); + if (dir == NULL) + { + entry_count = 0; + return; + } + entry_count = 0; + struct dirent *ent; + while ((ent = readdir(dir)) != NULL && entry_count < MAX_ENTRIES) + { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + if( has_suffix(ent->d_name,".bin") == false) + continue; + strncpy(entries[entry_count].name, ent->d_name, sizeof(entries[entry_count].name) - 1); + entries[entry_count].name[sizeof(entries[entry_count].name) - 1] = '\0'; + + // Build full path for stat + char full_path[512]; + snprintf(full_path, sizeof(full_path), "%s/%s", path, ent->d_name); + + // Determine if the entry is a directory and get file size + if (ent->d_type != DT_UNKNOWN) + { + entries[entry_count].is_dir = (ent->d_type == DT_DIR) ? 1 : 0; + + // Get file size using stat even if we know the type from d_type + struct stat statbuf; + if (stat(full_path, &statbuf) == 0) + { + entries[entry_count].file_size = entries[entry_count].is_dir ? 0 : statbuf.st_size; + } + else + { + entries[entry_count].file_size = 0; + } + } + else + { + struct stat statbuf; + if (stat(full_path, &statbuf) == 0) + { + entries[entry_count].is_dir = S_ISDIR(statbuf.st_mode) ? 1 : 0; + entries[entry_count].file_size = entries[entry_count].is_dir ? 0 : statbuf.st_size; + } + else + { + entries[entry_count].is_dir = 0; + entries[entry_count].file_size = 0; + } + } + entry_count++; + } + closedir(dir); + selected_index = 0; + if(entry_count == 0) { + text_directory_ui_set_status("No firmware found."); + } + +} + +// Draw the title header +static void ui_draw_title(void) +{ + draw_rect_spi(UI_X, UI_Y, UI_X + UI_WIDTH - 1, UI_Y + HEADER_TITLE_HEIGHT, BLACK); + draw_text(UI_X + 2, UI_Y + 2, "PicoCalc SD Firmware Loader", WHITE, BLACK); +} + +// Draw the current path header +static void ui_draw_path_header(void) +{ + char path_header[300]; + snprintf(path_header, sizeof(path_header), "Path: %s", current_path); + int y = UI_Y + HEADER_TITLE_HEIGHT; + draw_rect_spi(UI_X, y, UI_X + UI_WIDTH - 1, y + PATH_HEADER_HEIGHT - 1, COLOR_BG); + draw_text(UI_X + 2, y + 2, path_header, COLOR_FG, COLOR_BG); + draw_line_spi(UI_X, y + PATH_HEADER_HEIGHT - 2, UI_X + UI_WIDTH - 1, y + PATH_HEADER_HEIGHT - 2, COLOR_FG); +} + +/** + * Draw a single directory entry + * + * @param entry_idx Index of the entry in the entries array + * @param posY Vertical position to draw the entry + * @param font_height Height of the font + * @param is_selected Whether this entry is currently selected + */ +static void ui_draw_directory_entry(int entry_idx, int posY, int font_height, int is_selected) +{ + // Highlight background for selected item + if (is_selected) + { + draw_rect_spi(UI_X, posY - 1, UI_X + UI_WIDTH - 1, posY + font_height, COLOR_HIGHLIGHT); + } + + // Prepare filename with directory indicator + char full_file_name[300]; + snprintf(full_file_name, sizeof(full_file_name), "%s%s", + entries[entry_idx].name, + entries[entry_idx].is_dir ? "/" : ""); + + // Prepare display text with scrolling for selected items + char display_buffer[300]; + if (is_selected && strlen(full_file_name) > FILE_NAME_VISIBLE_CHARS) + { + // Use scrolling text for selected long filenames + get_scrolling_text(full_file_name, display_buffer, sizeof(display_buffer), FILE_NAME_VISIBLE_CHARS); + } + else + { + // For non-selected or short filenames + if (strlen(full_file_name) > FILE_NAME_VISIBLE_CHARS) + { + // Truncate with ellipsis + strncpy(display_buffer, full_file_name, FILE_NAME_VISIBLE_CHARS - 3); + display_buffer[FILE_NAME_VISIBLE_CHARS - 3] = '\0'; + strcat(display_buffer, "..."); + } + else + { + strncpy(display_buffer, full_file_name, sizeof(display_buffer) - 1); + display_buffer[sizeof(display_buffer) - 1] = '\0'; + } + } + + // Format and display file size + char size_buffer[20]; + format_file_size(entries[entry_idx].file_size, entries[entry_idx].is_dir, + size_buffer, sizeof(size_buffer)); + + // Draw filename and file size + draw_text(FILE_NAME_X, posY, display_buffer, is_selected?COLOR_BG:COLOR_FG , is_selected ? COLOR_HIGHLIGHT : COLOR_BG); + draw_text(FILE_SIZE_X, posY, size_buffer, is_selected?COLOR_BG:COLOR_FG, is_selected ? COLOR_HIGHLIGHT : COLOR_BG); +} + +/** + * Update only the selected entry row + * This is an optimization to avoid redrawing the entire directory list + * when only the selected entry needs to be updated (e.g., for scrolling text) + */ +static void ui_update_selected_entry(void) +{ + const int font_height = 12; + const int entry_padding = 2; + int y_start = UI_Y + HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT; + int available_height = UI_HEIGHT - (HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT + STATUS_BAR_HEIGHT); + int max_visible = available_height / (font_height + entry_padding); + int start_index = (selected_index >= max_visible) ? selected_index - max_visible + 1 : 0; + + // Calculate the position of the selected entry + int visible_index = selected_index - start_index; + if (visible_index >= 0 && visible_index < max_visible) { + int posY = y_start + visible_index * (font_height + entry_padding); + + // Clear just the selected row + //draw_rect_spi(UI_X, posY - 1, UI_X + UI_WIDTH - 1, posY + font_height, COLOR_BG); + + // Redraw just the selected entry + ui_draw_directory_entry(selected_index, posY, font_height, 1); + } +} + +// Draw the directory list +static void ui_draw_directory_list(void) +{ + const int font_height = 12; + const int entry_padding = 2; + int y_start = UI_Y + HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT; + int available_height = UI_HEIGHT - (HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT + STATUS_BAR_HEIGHT); + int max_visible = available_height / (font_height + entry_padding); + int start_index = (selected_index >= max_visible) ? selected_index - max_visible + 1 : 0; + + draw_rect_spi(UI_X, y_start, UI_X + UI_WIDTH - 1, UI_Y + UI_HEIGHT - STATUS_BAR_HEIGHT - 1, COLOR_BG); + last_scrolling = time_us_64() /1000; + + for (int i = 0; i < max_visible && (i + start_index) < entry_count; i++) + { + int posY = y_start + i * (font_height + entry_padding); + int entry_idx = i + start_index; + int is_selected = (entry_idx == selected_index); + + // Draw the entry using the helper function + ui_draw_directory_entry(entry_idx, posY, font_height, is_selected); + } +} + +// Draw the status bar +static void ui_draw_status_bar(void) +{ + int y = UI_Y + UI_HEIGHT - STATUS_BAR_HEIGHT; + draw_rect_spi(UI_X, y, UI_X + UI_WIDTH - 1, UI_Y + UI_HEIGHT - 1, COLOR_BG); + draw_line_spi(UI_X, y, UI_X + UI_WIDTH - 1, y, COLOR_FG); + char truncated_message[UI_WIDTH / 8]; + strncpy(truncated_message, status_message, sizeof(truncated_message) - 1); + truncated_message[sizeof(truncated_message) - 1] = '\0'; + draw_text(UI_X + 2, y + 2, truncated_message, COLOR_FG, COLOR_BG); +} + +// Refresh the entire UI +static void ui_refresh(void) +{ + ui_draw_title(); + ui_draw_path_header(); + ui_draw_directory_list(); + ui_draw_status_bar(); + + if (status_message[0] != '\0' && ((time_us_64() / 1000) - status_timestamp) > 3000) + { + status_message[0] = '\0'; + ui_draw_status_bar(); + } +} + +// Handle key events for navigation and selection +static void process_key_event(int key) +{ + switch (key) + { + case KEY_ARROW_UP: + if (selected_index > 0) + selected_index--; + ui_draw_directory_list(); + break; + case KEY_ARROW_DOWN: + if (selected_index < entry_count - 1) + selected_index++; + ui_draw_directory_list(); + break; + case KEY_ENTER: + if (entry_count > 0) + { + char new_path[512]; + if (entries[selected_index].is_dir) + { + snprintf(new_path, sizeof(new_path), "%s/%s", current_path, entries[selected_index].name); + strncpy(current_path, new_path, sizeof(current_path) - 1); + load_directory(current_path); + ui_draw_path_header(); + ui_draw_directory_list(); + } + else if (final_callback) + { + char final_selected[512]; + snprintf(final_selected, sizeof(final_selected), "%s/%s", current_path, entries[selected_index].name); + final_callback(final_selected); + } + } + break; + case KEY_BACKSPACE: + if (strcmp(current_path, "/sd") != 0) + { + char *last_slash = strrchr(current_path, '/'); + if (last_slash) + *last_slash = '\0'; + if (current_path[0] == '\0') + strncpy(current_path, "/sd", sizeof(current_path) - 1); + load_directory(current_path); + ui_draw_path_header(); + ui_draw_directory_list(); + } + break; + default: + break; + } + ui_draw_status_bar(); +} + +// Public API: Set the final selection callback +void text_directory_ui_set_final_callback(final_selection_callback_t callback) +{ + final_callback = callback; +} + +// Public API: Initialize the UI +bool text_directory_ui_init(void) +{ + draw_filled_rect(UI_X, UI_Y, UI_WIDTH, UI_HEIGHT, COLOR_BG); + strncpy(current_path, "/sd", sizeof(current_path)); + load_directory(current_path); + ui_refresh(); + last_scrolling = time_us_64()/1000; + return true; +} + +// Public API: Set a status message +void text_directory_ui_set_status(const char *msg) +{ + strncpy(status_message, msg, sizeof(status_message) - 1); + status_message[sizeof(status_message) - 1] = '\0'; + status_timestamp = (time_us_64() / 1000); + ui_draw_status_bar(); +} + +// Public API: Main event loop for the UI +void text_directory_ui_run(void) +{ + uint32_t last_scroll_update = 0; + const uint32_t SCROLL_UPDATE_MS = 500; // Update scrolling text every 100ms + + while (true) + { + int key = keypad_get_key(); + if (key != 0) + process_key_event(key); + + uint32_t current_time = time_us_64() / 1000; + + // Update scrolling text periodically + if (current_time - last_scroll_update > SCROLL_UPDATE_MS) + { + // Only update the selected entry row if there are entries and a selected item might need scrolling + if (entry_count > 0 && selected_index >= 0 && + strlen(entries[selected_index].name) + (entries[selected_index].is_dir ? 1 : 0) > FILE_NAME_VISIBLE_CHARS) + { + ui_update_selected_entry(); + } + last_scroll_update = current_time; + } + + // Clear status message after timeout + if (status_message[0] != '\0' && (current_time - status_timestamp) > 3000) + { + status_message[0] = '\0'; + ui_draw_status_bar(); + } + + // Check for SD card removal during runtime + if (!sd_card_inserted()) { + text_directory_ui_set_status("SD card removed. Please reinsert card."); + + // Wait until the SD card is reinserted + while (!sd_card_inserted()) { + sleep_ms(100); + } + + // Once reinserted, update the UI and reinitialize filesystem + text_directory_ui_set_status("SD card detected. Remounting..."); + if (!fs_init()) { + text_directory_ui_set_status("Failed to remount SD card!"); + sleep_ms(2000); + watchdog_reboot(0, 0, 0); + } + + // Refresh the directory listing + load_directory(current_path); + ui_draw_path_header(); + ui_draw_directory_list(); + text_directory_ui_set_status("SD card remounted successfully."); + } + + sleep_ms(20); // Shorter sleep to make scrolling smoother + } +} diff --git a/Code/pico_multi_booter/sd_boot/text_directory_ui.h b/Code/pico_multi_booter/sd_boot/text_directory_ui.h new file mode 100644 index 0000000..eeebe6e --- /dev/null +++ b/Code/pico_multi_booter/sd_boot/text_directory_ui.h @@ -0,0 +1,28 @@ +/* + * text_directory_ui.h + * + */ + +#ifndef TEXT_DIRECTORY_UI_H +#define TEXT_DIRECTORY_UI_H + +#include + +// Callback type: invoked when the user makes a final selection. The selected path is passed as an argument. +typedef void (*final_selection_callback_t)(const char *selected_path); + +// Initialize the text directory UI. This sets up the SD card filesystem and the display UI. +// Returns true if initialization succeeded, false otherwise. +bool text_directory_ui_init(void); + +// Run the main event loop for the directory navigation UI. This function polls for key events, +// updates the selection cursor, and processes directory changes. +void text_directory_ui_run(void); + +// Register a callback that will be called when the final selection is made. +void text_directory_ui_set_final_callback(final_selection_callback_t callback); + +// Public API: Set a status or error message to be displayed in the status bar (auto-clears after 3 seconds) +void text_directory_ui_set_status(const char *msg); + +#endif // TEXT_DIRECTORY_UI_H