add pico_multi_booter code

This commit is contained in:
cuu 2025-05-19 14:41:19 +08:00
parent bf95745428
commit 4c74e3b3fe
23 changed files with 4006 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
build
cmake-build-*
.idea

3
.gitmodules vendored
View File

@ -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

View 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 *
# ***************************************************************************

View 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.

View 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()

View 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);
}
}

View 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 */
}

View 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 */
}

View 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})

View 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

View 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

View 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})

View 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;
}

View 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

View 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;
}

View 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

View 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})

View 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) ÿ
};

View 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;
}

View 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

View 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();
}

View 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
}
}

View 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