mirror of
https://github.com/clockworkpi/PicoCalc.git
synced 2025-12-12 10:18:54 +01:00
add pico_multi_booter code
This commit is contained in:
parent
bf95745428
commit
4c74e3b3fe
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
build
|
||||||
|
cmake-build-*
|
||||||
|
|
||||||
|
.idea
|
||||||
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -16,3 +16,6 @@
|
|||||||
[submodule "Bin/PicoMite/PicoMiteAllVersions"]
|
[submodule "Bin/PicoMite/PicoMiteAllVersions"]
|
||||||
path = Bin/PicoMite/PicoMiteAllVersions
|
path = Bin/PicoMite/PicoMiteAllVersions
|
||||||
url = https://github.com/madcock/PicoMiteAllVersions.git
|
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
|
||||||
|
|||||||
104
Code/pico_multi_booter/CMakeLists.txt
Normal file
104
Code/pico_multi_booter/CMakeLists.txt
Normal file
@ -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 *
|
||||||
|
# ***************************************************************************
|
||||||
87
Code/pico_multi_booter/README.md
Normal file
87
Code/pico_multi_booter/README.md
Normal file
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
630
Code/pico_multi_booter/applink.py
Normal file
630
Code/pico_multi_booter/applink.py
Normal file
@ -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()
|
||||||
196
Code/pico_multi_booter/boot.c
Normal file
196
Code/pico_multi_booter/boot.c
Normal file
@ -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 <stdio.h>
|
||||||
|
#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 <stark3y@gmail.com>
|
||||||
|
// 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 <stark3y@gmail.com>
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
219
Code/pico_multi_booter/memmap_default_rp2040.ld
Normal file
219
Code/pico_multi_booter/memmap_default_rp2040.ld
Normal file
@ -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 */
|
||||||
|
}
|
||||||
|
|
||||||
302
Code/pico_multi_booter/memmap_default_rp2350.ld
Normal file
302
Code/pico_multi_booter/memmap_default_rp2350.ld
Normal file
@ -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 */
|
||||||
|
}
|
||||||
83
Code/pico_multi_booter/sd_boot/CMakeLists.txt
Normal file
83
Code/pico_multi_booter/sd_boot/CMakeLists.txt
Normal file
@ -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})
|
||||||
|
|
||||||
45
Code/pico_multi_booter/sd_boot/config.h
Normal file
45
Code/pico_multi_booter/sd_boot/config.h
Normal file
@ -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
|
||||||
11
Code/pico_multi_booter/sd_boot/debug.h
Normal file
11
Code/pico_multi_booter/sd_boot/debug.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef DEBUG_H
|
||||||
|
#define DEBUG_H
|
||||||
|
|
||||||
|
#ifdef ENABLE_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define DEBUG_PRINT(fmt, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // DEBUG_H
|
||||||
21
Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt
Normal file
21
Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt
Normal file
@ -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})
|
||||||
85
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c
Normal file
85
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <pico/stdio.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
20
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h
Normal file
20
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef I2C_KEYBOARD_H
|
||||||
|
#define I2C_KEYBOARD_H
|
||||||
|
#include <pico/stdlib.h>
|
||||||
|
#include <pico/platform.h>
|
||||||
|
#include <hardware/gpio.h>
|
||||||
|
#include <hardware/i2c.h>
|
||||||
|
|
||||||
|
#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
|
||||||
143
Code/pico_multi_booter/sd_boot/key_event.c
Normal file
143
Code/pico_multi_booter/sd_boot/key_event.c
Normal file
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <pico/stdio.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
21
Code/pico_multi_booter/sd_boot/key_event.h
Normal file
21
Code/pico_multi_booter/sd_boot/key_event.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#ifndef KEY_EVENT_H
|
||||||
|
#define KEY_EVENT_H
|
||||||
|
|
||||||
|
#include "i2ckbd.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <pico/stdio.h>
|
||||||
|
|
||||||
|
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
|
||||||
20
Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt
Normal file
20
Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt
Normal file
@ -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})
|
||||||
233
Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h
Normal file
233
Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h
Normal file
@ -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) ÿ
|
||||||
|
};
|
||||||
754
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c
Normal file
754
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c
Normal file
@ -0,0 +1,754 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include <hardware/spi.h>
|
||||||
|
#include "hardware/timer.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
}
|
||||||
148
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h
Normal file
148
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#ifndef LCDSPI_H
|
||||||
|
#define LCDSPI_H
|
||||||
|
#include "pico/multicore.h"
|
||||||
|
#include <hardware/spi.h>
|
||||||
|
|
||||||
|
//#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
|
||||||
332
Code/pico_multi_booter/sd_boot/main.c
Normal file
332
Code/pico_multi_booter/sd_boot/main.c
Normal file
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "i2ckbd.h"
|
||||||
|
#include "lcdspi.h"
|
||||||
|
#include <hardware/flash.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <hardware/watchdog.h>
|
||||||
|
#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();
|
||||||
|
}
|
||||||
516
Code/pico_multi_booter/sd_boot/text_directory_ui.c
Normal file
516
Code/pico_multi_booter/sd_boot/text_directory_ui.c
Normal file
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <hardware/watchdog.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "lcdspi/lcdspi.h"
|
||||||
|
#include "key_event.h"
|
||||||
|
#include "text_directory_ui.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Code/pico_multi_booter/sd_boot/text_directory_ui.h
Normal file
28
Code/pico_multi_booter/sd_boot/text_directory_ui.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* text_directory_ui.h
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TEXT_DIRECTORY_UI_H
|
||||||
|
#define TEXT_DIRECTORY_UI_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// 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
|
||||||
Loading…
x
Reference in New Issue
Block a user