mirror of
https://github.com/clockworkpi/PicoCalc.git
synced 2026-03-20 02:52:39 +01:00
add pico_multi_booter code
This commit is contained in:
83
Code/pico_multi_booter/sd_boot/CMakeLists.txt
Normal file
83
Code/pico_multi_booter/sd_boot/CMakeLists.txt
Normal file
@@ -0,0 +1,83 @@
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
set(APP_NAME sd_boot)
|
||||
|
||||
add_subdirectory(i2ckbd)
|
||||
add_subdirectory(lcdspi)
|
||||
add_subdirectory(lib/pico-vfs)
|
||||
|
||||
add_executable(${APP_NAME}
|
||||
main.c
|
||||
key_event.c
|
||||
text_directory_ui.c
|
||||
)
|
||||
|
||||
target_link_libraries(${APP_NAME}
|
||||
pico_stdlib
|
||||
hardware_sync
|
||||
hardware_flash
|
||||
hardware_irq
|
||||
hardware_adc
|
||||
hardware_pwm
|
||||
hardware_i2c
|
||||
hardware_spi
|
||||
hardware_dma
|
||||
hardware_exception
|
||||
hardware_pio
|
||||
pico_multicore
|
||||
i2ckbd
|
||||
lcdspi
|
||||
blockdevice_sd
|
||||
filesystem_fat
|
||||
filesystem_vfs
|
||||
)
|
||||
|
||||
target_compile_options(${APP_NAME} PRIVATE
|
||||
-Os
|
||||
-Wall
|
||||
-Wno-unused-variable
|
||||
-Wno-unused-function
|
||||
)
|
||||
|
||||
pico_enable_stdio_usb(${APP_NAME} 0)
|
||||
pico_enable_stdio_uart(${APP_NAME} 1)
|
||||
|
||||
pico_add_extra_outputs(${APP_NAME})
|
||||
pico_add_uf2_output(${APP_NAME})
|
||||
|
||||
|
||||
target_link_options(${APP_NAME} PRIVATE -Wl,--print-memory-usage)
|
||||
target_link_options(${APP_NAME} PRIVATE -Wl,-z,max-page-size=4096)
|
||||
|
||||
pico_set_linker_script(${APP_NAME} ${CMAKE_BINARY_DIR}/${APP_NAME}.ld)
|
||||
|
||||
|
||||
set(UF2_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/${APP_NAME}.uf2)
|
||||
set(UF2_DEST ${CMAKE_BINARY_DIR}/${APP_NAME}.uf2)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${UF2_DEST}
|
||||
DEPENDS ${UF2_SOURCE}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${UF2_SOURCE} ${UF2_DEST}
|
||||
COMMENT "Copying ${APP_NAME}.uf2 to top-level build dir"
|
||||
)
|
||||
|
||||
|
||||
add_custom_target(PREPARE_${APP_NAME}
|
||||
COMMENT "Create Linker Script for '${APP_NAME}'"
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_SOURCE_DIR}/applink.py PREPARE
|
||||
${CMAKE_BINARY_DIR}
|
||||
${APP_NAME})
|
||||
|
||||
add_custom_target(BUILT_${APP_NAME}
|
||||
COMMENT "Record Build Details for '${APP_NAME}'"
|
||||
DEPENDS ${UF2_DEST}
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_SOURCE_DIR}/applink.py BUILT
|
||||
${CMAKE_BINARY_DIR}
|
||||
${APP_NAME})
|
||||
|
||||
45
Code/pico_multi_booter/sd_boot/config.h
Normal file
45
Code/pico_multi_booter/sd_boot/config.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
// GPIOs for SPI interface (SD card)
|
||||
#define SD_SPI0 0
|
||||
#define SD_SCLK_PIN 18
|
||||
#define SD_MOSI_PIN 19
|
||||
#define SD_MISO_PIN 16
|
||||
#define SD_CS_PIN 17
|
||||
#define SD_DET_PIN 22
|
||||
|
||||
#define LCD_SPI1 1
|
||||
#define LCD_SCK_PIN 10
|
||||
#define LCD_MOSI_PIN 11
|
||||
#define LCD_MISO_PIN 12
|
||||
#define LCD_CS_PIN 13
|
||||
#define LCD_DC_PIN 14
|
||||
#define LCD_RST_PIN 15
|
||||
|
||||
// GPIOs for audio output
|
||||
#define AUDIO_LEFT 28
|
||||
#define AUDIO_RIGHT 27
|
||||
|
||||
// GPIOs for buttons
|
||||
#define NEXT_BUTTON 2
|
||||
#define PART_BUTTON 3
|
||||
|
||||
// Pico-internal GPIOs
|
||||
#define PICO_PS 23
|
||||
#define LED_PIN 25
|
||||
|
||||
|
||||
|
||||
// PicoCalc SD Firmware Loader
|
||||
// SD_BOOT_FLASH_OFFSET is the offset in flash memory where the bootloader starts
|
||||
// According to the applink.map ,with combined PicoMite, here is 920k
|
||||
// This offset is used to ensure that the bootloader does not get overwritten
|
||||
// when loading a new application from the SD card
|
||||
#define SD_BOOT_FLASH_OFFSET (940 * 1024)
|
||||
|
||||
// Maximum size of the application that can be loaded
|
||||
// This ensures we don't overwrite the bootloader itself
|
||||
#define MAX_APP_SIZE (PICO_FLASH_SIZE_BYTES - SD_BOOT_FLASH_OFFSET)
|
||||
|
||||
#endif // CONFIG_H
|
||||
11
Code/pico_multi_booter/sd_boot/debug.h
Normal file
11
Code/pico_multi_booter/sd_boot/debug.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef DEBUG_H
|
||||
#define DEBUG_H
|
||||
|
||||
#ifdef ENABLE_DEBUG
|
||||
#include <stdio.h>
|
||||
#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINT(fmt, ...)
|
||||
#endif
|
||||
|
||||
#endif // DEBUG_H
|
||||
21
Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt
Normal file
21
Code/pico_multi_booter/sd_boot/i2ckbd/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated Cmake Pico project file
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
|
||||
project(i2ckbd
|
||||
VERSION 0.0.1
|
||||
DESCRIPTION "i2ckbd for picocalc."
|
||||
)
|
||||
|
||||
add_library(i2ckbd INTERFACE)
|
||||
|
||||
target_sources(i2ckbd INTERFACE
|
||||
i2ckbd.c
|
||||
)
|
||||
|
||||
target_link_libraries(i2ckbd INTERFACE pico_stdlib hardware_spi hardware_i2c)
|
||||
|
||||
target_include_directories(i2ckbd INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
85
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c
Normal file
85
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.c
Normal file
@@ -0,0 +1,85 @@
|
||||
#include <stdio.h>
|
||||
#include <pico/stdio.h>
|
||||
#include "i2ckbd.h"
|
||||
#include "debug.h"
|
||||
|
||||
static uint8_t i2c_inited = 0;
|
||||
|
||||
void init_i2c_kbd() {
|
||||
gpio_set_function(I2C_KBD_SCL, GPIO_FUNC_I2C);
|
||||
gpio_set_function(I2C_KBD_SDA, GPIO_FUNC_I2C);
|
||||
i2c_init(I2C_KBD_MOD, I2C_KBD_SPEED);
|
||||
gpio_pull_up(I2C_KBD_SCL);
|
||||
gpio_pull_up(I2C_KBD_SDA);
|
||||
|
||||
i2c_inited = 1;
|
||||
}
|
||||
|
||||
int read_i2c_kbd() {
|
||||
int retval;
|
||||
static int ctrlheld = 0;
|
||||
uint16_t buff = 0;
|
||||
unsigned char msg[2];
|
||||
int c = -1;
|
||||
msg[0] = 0x09;
|
||||
|
||||
if (i2c_inited == 0) return -1;
|
||||
|
||||
retval = i2c_write_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, msg, 1, false, 500000);
|
||||
if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) {
|
||||
DEBUG_PRINT("I2C write err\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sleep_ms(16);
|
||||
retval = i2c_read_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, (unsigned char *) &buff, 2, false, 500000);
|
||||
if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) {
|
||||
DEBUG_PRINT("I2C read err\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buff != 0) {
|
||||
if (buff == 0x7e03)ctrlheld = 0;
|
||||
else if (buff == 0x7e02) {
|
||||
ctrlheld = 1;
|
||||
} else if ((buff & 0xff) == 1) {//pressed
|
||||
c = buff >> 8;
|
||||
int realc = -1;
|
||||
switch (c) {
|
||||
default:
|
||||
realc = c;
|
||||
break;
|
||||
}
|
||||
c = realc;
|
||||
if (c >= 'a' && c <= 'z' && ctrlheld)c = c - 'a' + 1;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read_battery() {
|
||||
int retval;
|
||||
uint16_t buff = 0;
|
||||
unsigned char msg[2];
|
||||
msg[0] = 0x0b;
|
||||
|
||||
if (i2c_inited == 0) return -1;
|
||||
|
||||
retval = i2c_write_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, msg, 1, false, 500000);
|
||||
if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) {
|
||||
DEBUG_PRINT("Batt I2C write err\n");
|
||||
return -1;
|
||||
}
|
||||
sleep_ms(16);
|
||||
retval = i2c_read_timeout_us(I2C_KBD_MOD, I2C_KBD_ADDR, (unsigned char *) &buff, 2, false, 500000);
|
||||
if (retval == PICO_ERROR_GENERIC || retval == PICO_ERROR_TIMEOUT) {
|
||||
DEBUG_PRINT("Batt I2C read err\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buff != 0) {
|
||||
return buff;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
20
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h
Normal file
20
Code/pico_multi_booter/sd_boot/i2ckbd/i2ckbd.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef I2C_KEYBOARD_H
|
||||
#define I2C_KEYBOARD_H
|
||||
#include <pico/stdlib.h>
|
||||
#include <pico/platform.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/i2c.h>
|
||||
|
||||
#define I2C_KBD_MOD i2c1
|
||||
#define I2C_KBD_SDA 6
|
||||
#define I2C_KBD_SCL 7
|
||||
|
||||
#define I2C_KBD_SPEED 10000 // if dual i2c, then the speed of keyboard i2c should be 10khz
|
||||
|
||||
#define I2C_KBD_ADDR 0x1F
|
||||
|
||||
void init_i2c_kbd();
|
||||
int read_i2c_kbd();
|
||||
int read_battery();
|
||||
|
||||
#endif
|
||||
143
Code/pico_multi_booter/sd_boot/key_event.c
Normal file
143
Code/pico_multi_booter/sd_boot/key_event.c
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* PicoCalc SD Firmware Loader
|
||||
*
|
||||
* Author: Hsuan Han Lai
|
||||
* Email: hsuan.han.lai@gmail.com
|
||||
* Website: https://hsuanhanlai.com
|
||||
* Year: 2025
|
||||
*
|
||||
* key_event.c
|
||||
*
|
||||
* Wrapper for post processing dispatch keyboard events
|
||||
*
|
||||
*/
|
||||
|
||||
#include "i2ckbd.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <pico/stdio.h>
|
||||
#include "key_event.h"
|
||||
#include "debug.h"
|
||||
|
||||
void keypad_init(void)
|
||||
{
|
||||
init_i2c_kbd();
|
||||
}
|
||||
|
||||
int keypad_get_key(void)
|
||||
{
|
||||
int r = read_i2c_kbd();
|
||||
if (r < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int act_key = 0;
|
||||
|
||||
/* Translate the keys to LVGL control characters according to your key definitions */
|
||||
switch (r) {
|
||||
case 0xb5: // Arrow Up
|
||||
act_key = KEY_ARROW_UP;
|
||||
break;
|
||||
case 0xb6: // Arrow Down
|
||||
act_key = KEY_ARROW_DOWN;
|
||||
break;
|
||||
case 0xb4: // Arrow Left
|
||||
act_key = KEY_ARROW_LEFT;
|
||||
break;
|
||||
case 0xb7: // Arrow Right
|
||||
act_key = KEY_ARROW_RIGHT;
|
||||
break;
|
||||
case 0x0A: // Enter
|
||||
act_key = KEY_ENTER;
|
||||
break;
|
||||
|
||||
// Special Keys
|
||||
case 0x81: case 0x82: case 0x83: case 0x84: case 0x85:
|
||||
case 0x86: case 0x87: case 0x88: case 0x89: case 0x90: // F1-F10 Keys
|
||||
DEBUG_PRINT("Warn: F-key unmapped\n");
|
||||
act_key = 0;
|
||||
break;
|
||||
|
||||
case 0xB1: // ESC
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0x09: // TAB
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0xC1: // Caps Lock
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0xD4: // DEL
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0x08: // Backspace
|
||||
act_key = KEY_BACKSPACE;
|
||||
break;
|
||||
|
||||
case 0xD0: // brk
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0xD2: // Home
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0xD5: // End
|
||||
act_key = 0;
|
||||
break;
|
||||
|
||||
case 0x60: case 0x2F: case 0x5C: case 0x2D: case 0x3D:
|
||||
case 0x5B: case 0x5D: // `/\-=[] Keys
|
||||
act_key = r;
|
||||
break;
|
||||
|
||||
case 0x7E: act_key = '~'; break;
|
||||
case 0x3F: act_key = '?'; break;
|
||||
case 0x7C: act_key = '|'; break;
|
||||
case 0x5F: act_key = '_'; break;
|
||||
case 0x2B: act_key = '+'; break;
|
||||
case 0x7B: act_key = '{'; break;
|
||||
case 0x7D: act_key = '}'; break;
|
||||
|
||||
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
|
||||
case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // 0-9 Keys
|
||||
act_key = r;
|
||||
break;
|
||||
|
||||
case 0x21: case 0x40: case 0x23: case 0x24: case 0x25:
|
||||
case 0x5E: case 0x26: case 0x2A: case 0x28: case 0x29: // !@#$%^&*() Keys
|
||||
act_key = r;
|
||||
break;
|
||||
|
||||
case 0xD1: // Insert
|
||||
DEBUG_PRINT("Warn: Insert unmapped\n");
|
||||
act_key = 0;
|
||||
break;
|
||||
|
||||
case 0x3C: act_key = '<'; break;
|
||||
case 0x3E: act_key = '>'; break;
|
||||
|
||||
case 0x3B: case 0x27: case 0x3A: case 0x22: // ;:'"" Keys
|
||||
act_key = r;
|
||||
break;
|
||||
case 0xA5: // CTL
|
||||
DEBUG_PRINT("Warn: CTL unmapped\n");
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0x20: // SPACE
|
||||
act_key = r;
|
||||
break;
|
||||
case 0xA1: // ALT
|
||||
DEBUG_PRINT("Warn: ALT unmapped\n");
|
||||
act_key = 0;
|
||||
break;
|
||||
case 0xA2: case 0xA3: // RIGHT/LEFT SHIFT
|
||||
break;
|
||||
|
||||
default:
|
||||
act_key = r;
|
||||
break;
|
||||
|
||||
}
|
||||
return act_key;
|
||||
}
|
||||
|
||||
21
Code/pico_multi_booter/sd_boot/key_event.h
Normal file
21
Code/pico_multi_booter/sd_boot/key_event.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef KEY_EVENT_H
|
||||
#define KEY_EVENT_H
|
||||
|
||||
#include "i2ckbd.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <pico/stdio.h>
|
||||
|
||||
typedef enum {
|
||||
KEY_ARROW_UP = 0xB5,
|
||||
KEY_ARROW_LEFT = 0xB4,
|
||||
KEY_ARROW_RIGHT = 0xB7,
|
||||
KEY_ARROW_DOWN = 0xB6,
|
||||
KEY_BACKSPACE = 0x08,
|
||||
KEY_ENTER = 0x0A,
|
||||
} lv_key_t;
|
||||
|
||||
void keypad_init(void);
|
||||
int keypad_get_key(void);
|
||||
|
||||
#endif // KEY_EVENT_H
|
||||
20
Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt
Normal file
20
Code/pico_multi_booter/sd_boot/lcdspi/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated Cmake Pico project file
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
project(lcdspi
|
||||
VERSION 0.0.1
|
||||
DESCRIPTION "lcdspi for rp2040."
|
||||
)
|
||||
|
||||
add_library(lcdspi INTERFACE)
|
||||
|
||||
target_sources(lcdspi INTERFACE
|
||||
lcdspi.c
|
||||
)
|
||||
|
||||
target_link_libraries(lcdspi INTERFACE pico_stdlib hardware_spi)
|
||||
|
||||
target_include_directories(lcdspi INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
233
Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h
Normal file
233
Code/pico_multi_booter/sd_boot/lcdspi/fonts/font1.h
Normal file
@@ -0,0 +1,233 @@
|
||||
// font1.c
|
||||
// Font type : Full (223 characters)
|
||||
// Font size : 8x12 pixels
|
||||
// Memory usage : 2680 bytes
|
||||
// Font adapted from: http://www.rinkydinkelectronics.com/r_fonts.php
|
||||
|
||||
const unsigned char font1[] ={
|
||||
0x08,0x0C,0x20,0xE0,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(32)
|
||||
0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x10,0x10,0x00,0x00, // Chr$(33) !
|
||||
0x00,0x24,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(34) "
|
||||
0x00,0x24,0x24,0x7E,0x24,0x24,0x24,0x7E,0x24,0x24,0x00,0x00, // Chr$(35) #
|
||||
0x00,0x10,0x3C,0x52,0x50,0x3C,0x12,0x52,0x3C,0x10,0x00,0x00, // Chr$(36) $
|
||||
0x00,0x00,0x62,0x62,0x04,0x08,0x10,0x20,0x46,0x46,0x00,0x00, // Chr$(37) %
|
||||
0x00,0x00,0x38,0x44,0x44,0x38,0x4A,0x44,0x4C,0x3A,0x00,0x00, // Chr$(38) &
|
||||
0x00,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(39) '
|
||||
0x00,0x0C,0x10,0x20,0x20,0x20,0x20,0x20,0x20,0x10,0x0C,0x00, // Chr$(40) (
|
||||
0x00,0x30,0x08,0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x30,0x00, // Chr$(41) )
|
||||
0x00,0x42,0x24,0x18,0x7E,0x18,0x24,0x42,0x00,0x00,0x00,0x00, // Chr$(42) *
|
||||
0x00,0x00,0x00,0x10,0x10,0x7C,0x10,0x10,0x00,0x00,0x00,0x00, // Chr$(43) +
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x08,0x10,0x00, // Chr$(44) ,
|
||||
0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(45) -
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // Chr$(46) .
|
||||
0x00,0x02,0x02,0x04,0x04,0x08,0x10,0x20,0x20,0x40,0x40,0x00, // Chr$(47) /
|
||||
0x00,0x3C,0x46,0x4E,0x4A,0x5A,0x52,0x72,0x62,0x3C,0x00,0x00, // Chr$(48) 0
|
||||
0x00,0x10,0x30,0x50,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00, // Chr$(49) 1
|
||||
0x00,0x3C,0x42,0x02,0x02,0x04,0x08,0x10,0x20,0x7E,0x00,0x00, // Chr$(50) 2
|
||||
0x00,0x3C,0x42,0x02,0x02,0x1C,0x02,0x02,0x42,0x3C,0x00,0x00, // Chr$(51) 3
|
||||
0x00,0x04,0x44,0x44,0x44,0x7E,0x04,0x04,0x04,0x04,0x00,0x00, // Chr$(52) 4
|
||||
0x00,0x7E,0x40,0x40,0x7C,0x02,0x02,0x02,0x42,0x3C,0x00,0x00, // Chr$(53) 5
|
||||
0x00,0x18,0x20,0x40,0x40,0x7C,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(54) 6
|
||||
0x00,0x7E,0x42,0x02,0x02,0x04,0x08,0x10,0x10,0x10,0x00,0x00, // Chr$(55) 7
|
||||
0x00,0x3C,0x42,0x42,0x42,0x3C,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(56) 8
|
||||
0x00,0x3C,0x42,0x42,0x42,0x3E,0x02,0x04,0x08,0x30,0x00,0x00, // Chr$(57) 9
|
||||
0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00, // Chr$(58) :
|
||||
0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x08,0x10,0x00, // Chr$(59) ;
|
||||
0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x00,0x00, // Chr$(60) <
|
||||
0x00,0x00,0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00,0x00,0x00, // Chr$(61) =
|
||||
0x00,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x00,0x00, // Chr$(62) >
|
||||
0x00,0x3C,0x42,0x02,0x06,0x08,0x10,0x10,0x00,0x10,0x10,0x00, // Chr$(63) ?
|
||||
0x00,0x3C,0x42,0x42,0x5E,0x56,0x5C,0x40,0x40,0x3C,0x00,0x00, // Chr$(64) @
|
||||
0x00,0x18,0x24,0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x00,0x00, // Chr$(65) A
|
||||
0x00,0x7C,0x42,0x42,0x42,0x7C,0x42,0x42,0x42,0x7C,0x00,0x00, // Chr$(66) B
|
||||
0x00,0x3C,0x42,0x40,0x40,0x40,0x40,0x40,0x42,0x3C,0x00,0x00, // Chr$(67) C
|
||||
0x00,0x78,0x44,0x42,0x42,0x42,0x42,0x42,0x44,0x78,0x00,0x00, // Chr$(68) D
|
||||
0x00,0x7E,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x7E,0x00,0x00, // Chr$(69) E
|
||||
0x00,0x7E,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x40,0x00,0x00, // Chr$(70) F
|
||||
0x00,0x3C,0x42,0x40,0x40,0x40,0x4E,0x42,0x42,0x3E,0x00,0x00, // Chr$(71) G
|
||||
0x00,0x42,0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x42,0x00,0x00, // Chr$(72) H
|
||||
0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00, // Chr$(73) I
|
||||
0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x42,0x42,0x3C,0x00,0x00, // Chr$(74) J
|
||||
0x00,0x42,0x44,0x48,0x50,0x60,0x50,0x48,0x44,0x42,0x00,0x00, // Chr$(75) K
|
||||
0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7E,0x00,0x00, // Chr$(76) L
|
||||
0x00,0x42,0x66,0x66,0x5A,0x42,0x42,0x42,0x42,0x42,0x00,0x00, // Chr$(77) M
|
||||
0x00,0x42,0x62,0x62,0x52,0x5A,0x4A,0x46,0x46,0x42,0x00,0x00, // Chr$(78) N
|
||||
0x00,0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(79) O
|
||||
0x00,0x7C,0x42,0x42,0x42,0x7C,0x40,0x40,0x40,0x40,0x00,0x00, // Chr$(80) P
|
||||
0x00,0x3C,0x42,0x42,0x42,0x42,0x42,0x4A,0x44,0x3A,0x00,0x00, // Chr$(81) Q
|
||||
0x00,0x7C,0x42,0x42,0x42,0x7C,0x48,0x44,0x42,0x42,0x00,0x00, // Chr$(82) R
|
||||
0x00,0x3C,0x42,0x40,0x60,0x18,0x04,0x02,0x42,0x3C,0x00,0x00, // Chr$(83) S
|
||||
0x00,0x7E,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(84) T
|
||||
0x00,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(85) U
|
||||
0x00,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x24,0x18,0x00,0x00, // Chr$(86) V
|
||||
0x00,0x42,0x42,0x42,0x42,0x42,0x5A,0x24,0x24,0x24,0x00,0x00, // Chr$(87) W
|
||||
0x00,0x42,0x42,0x24,0x24,0x18,0x24,0x24,0x42,0x42,0x00,0x00, // Chr$(88) X
|
||||
0x00,0x44,0x44,0x44,0x44,0x38,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(89) Y
|
||||
0x00,0x7E,0x02,0x02,0x04,0x18,0x20,0x40,0x40,0x7E,0x00,0x00, // Chr$(90) Z
|
||||
0x00,0x3C,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3C,0x00,0x00, // Chr$(91) [
|
||||
0x00,0x40,0x40,0x20,0x20,0x10,0x08,0x04,0x04,0x02,0x02,0x00, // Chr$(92) backslash
|
||||
0x00,0x3C,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x3C,0x00,0x00, // Chr$(93) ]
|
||||
0x00,0x10,0x28,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(94) ^
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00, // Chr$(95) _
|
||||
0x00,0x18,0x24,0x24,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(96) `
|
||||
0x00,0x00,0x00,0x00,0x3C,0x02,0x3E,0x42,0x42,0x3E,0x00,0x00, // Chr$(97) a
|
||||
0x00,0x40,0x40,0x40,0x7C,0x42,0x42,0x42,0x42,0x7C,0x00,0x00, // Chr$(98) b
|
||||
0x00,0x00,0x00,0x00,0x3C,0x40,0x40,0x40,0x40,0x3C,0x00,0x00, // Chr$(99) c
|
||||
0x00,0x02,0x02,0x02,0x3E,0x42,0x42,0x42,0x42,0x3E,0x00,0x00, // Chr$(100) d
|
||||
0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x7C,0x40,0x3C,0x00,0x00, // Chr$(101) e
|
||||
0x00,0x00,0x1C,0x20,0x20,0x7C,0x20,0x20,0x20,0x20,0x00,0x00, // Chr$(102) f
|
||||
0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x3E,0x02,0x3C,0x00, // Chr$(103) g
|
||||
0x00,0x40,0x40,0x40,0x7C,0x42,0x42,0x42,0x42,0x42,0x00,0x00, // Chr$(104) h
|
||||
0x00,0x00,0x10,0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(105) i
|
||||
0x00,0x00,0x04,0x00,0x04,0x04,0x04,0x04,0x04,0x44,0x38,0x00, // Chr$(106) j
|
||||
0x00,0x40,0x40,0x40,0x44,0x48,0x70,0x50,0x48,0x44,0x00,0x00, // Chr$(107) k
|
||||
0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x18,0x00,0x00, // Chr$(108) l
|
||||
0x00,0x00,0x00,0x00,0x74,0x4A,0x4A,0x42,0x42,0x42,0x00,0x00, // Chr$(109) m
|
||||
0x00,0x00,0x00,0x00,0x78,0x44,0x44,0x44,0x44,0x44,0x00,0x00, // Chr$(110) n
|
||||
0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(111) o
|
||||
0x00,0x00,0x00,0x00,0x7C,0x42,0x42,0x42,0x7C,0x40,0x40,0x00, // Chr$(112) p
|
||||
0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x3C,0x06,0x00, // Chr$(113) q
|
||||
0x00,0x00,0x00,0x00,0x3C,0x42,0x42,0x40,0x40,0x40,0x00,0x00, // Chr$(114) r
|
||||
0x00,0x00,0x00,0x00,0x1E,0x20,0x1C,0x02,0x02,0x3C,0x00,0x00, // Chr$(115) s
|
||||
0x00,0x00,0x00,0x10,0x7C,0x10,0x10,0x10,0x10,0x08,0x00,0x00, // Chr$(116) t
|
||||
0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // Chr$(117) u
|
||||
0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00, // Chr$(118) v
|
||||
0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x5A,0x24,0x24,0x00,0x00, // Chr$(119) w
|
||||
0x00,0x00,0x00,0x00,0x42,0x24,0x18,0x18,0x24,0x42,0x00,0x00, // Chr$(120) x
|
||||
0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x3E,0x02,0x42,0x3C,0x00, // Chr$(121) y
|
||||
0x00,0x00,0x00,0x00,0x7E,0x04,0x08,0x10,0x20,0x7E,0x00,0x00, // Chr$(122) z
|
||||
0x00,0x1C,0x20,0x10,0x10,0x60,0x10,0x10,0x20,0x1C,0x00,0x00, // Chr$(123) {
|
||||
0x00,0x10,0x10,0x10,0x10,0x00,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(124) |
|
||||
0x00,0x38,0x04,0x08,0x08,0x06,0x08,0x08,0x04,0x38,0x00,0x00, // Chr$(125) }
|
||||
0x00,0x22,0x5A,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(126) ~
|
||||
0x00,0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x7E,0x00,0x00,0x00, // Chr$(127)
|
||||
0x00,0xFF,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0xFF,0x00, // Chr$(128) €
|
||||
0x00,0xFF,0x81,0x81,0x85,0x85,0xA9,0x91,0x81,0x81,0xFF,0x00, // Chr$(129)
|
||||
0x00,0xFF,0x81,0xA5,0xA5,0x99,0x99,0xA5,0xA5,0x81,0xFF,0x00, // Chr$(130) ‚
|
||||
0x00,0xFF,0x81,0x81,0x81,0x99,0x99,0x81,0x81,0x81,0xFF,0x00, // Chr$(131) ƒ
|
||||
0x00,0xFF,0x81,0x81,0x81,0x81,0x81,0xBD,0x81,0x81,0xFF,0x00, // Chr$(132) „
|
||||
0x00,0xFF,0x81,0x99,0x99,0x99,0x99,0x81,0x99,0x81,0xFF,0x00, // Chr$(133) …
|
||||
0x00,0xFF,0x81,0x99,0xA5,0x89,0x89,0x81,0x89,0x81,0xFF,0x00, // Chr$(134) †
|
||||
0x00,0x3E,0x7F,0x7F,0x6B,0x7F,0x6B,0x77,0x7F,0x3E,0x00,0x00, // Chr$(135) ‡
|
||||
0x00,0x3E,0x63,0x41,0x55,0x41,0x55,0x49,0x63,0x3E,0x00,0x00, // Chr$(136) ˆ
|
||||
0x00,0x00,0x08,0x1C,0x3E,0x7F,0x7F,0x3E,0x1C,0x08,0x00,0x00, // Chr$(137) ‰
|
||||
0x00,0x00,0x00,0x18,0x18,0x66,0x66,0x18,0x18,0x3C,0x00,0x00, // Chr$(138) Š
|
||||
0x00,0x00,0x00,0x18,0x3C,0x7E,0x7E,0x18,0x18,0x3C,0x00,0x00, // Chr$(139) ‹
|
||||
0x00,0x00,0x36,0x3E,0x7F,0x7F,0x7F,0x3E,0x1C,0x08,0x00,0x00, // Chr$(140) Œ
|
||||
0xFF,0xFF,0xC3,0x81,0x99,0xBD,0xBD,0x99,0x81,0xC3,0xFF,0xFF, // Chr$(141)
|
||||
0x00,0x00,0x00,0x00,0x18,0x3C,0x3C,0x18,0x00,0x00,0x00,0x00, // Chr$(142) Ž
|
||||
0x00,0x1F,0x19,0x1F,0x18,0x18,0x18,0x38,0x78,0x30,0x00,0x00, // Chr$(143)
|
||||
0x00,0x18,0x3C,0x7E,0x18,0x18,0x18,0x7E,0x3C,0x18,0x00,0x00, // Chr$(144)
|
||||
0x00,0x00,0x00,0x24,0x66,0xFF,0xFF,0x66,0x24,0x00,0x00,0x00, // Chr$(145) ‘
|
||||
0x00,0x18,0x3C,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00, // Chr$(146) ’
|
||||
0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x3C,0x18,0x00,0x00, // Chr$(147) “
|
||||
0x00,0x00,0x00,0x08,0x0C,0xFE,0xFE,0x0C,0x08,0x00,0x00,0x00, // Chr$(148) ”
|
||||
0x00,0x00,0x00,0x10,0x30,0x7F,0x7F,0x30,0x10,0x00,0x00,0x00, // Chr$(149) •
|
||||
0x00,0x00,0x08,0x2A,0x49,0x49,0x49,0x41,0x41,0x3E,0x00,0x00, // Chr$(150) –
|
||||
0x1C,0x22,0x41,0x5D,0x49,0x2A,0x1C,0x00,0x1C,0x00,0x1C,0x00, // Chr$(151) —
|
||||
0x18,0x18,0x00,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x18,0x00,0x00, // Chr$(152) ˜
|
||||
0xF8,0x8C,0xBE,0x8F,0xE3,0x85,0xF5,0xF5,0xF3,0xFF,0x00,0x00, // Chr$(153) ™
|
||||
0x00,0x18,0x24,0x42,0x42,0x42,0x7E,0x7E,0x24,0x24,0x24,0x24, // Chr$(154) š
|
||||
0x00,0x18,0x3C,0x7E,0x7E,0x7E,0x7E,0x7E,0x24,0x24,0x24,0x24, // Chr$(155) ›
|
||||
0x00,0x20,0x64,0xE2,0xE9,0xE5,0xE9,0xE2,0x64,0x20,0x00,0x00, // Chr$(156) œ
|
||||
0x00,0x3F,0x4B,0x4B,0x4B,0x3B,0x0B,0x0B,0x0B,0x0B,0x00,0x00, // Chr$(157)
|
||||
0x00,0x7E,0x44,0x48,0x6E,0x24,0x28,0x50,0x60,0x40,0x00,0x00, // Chr$(158) ž
|
||||
0x00,0x00,0x21,0x36,0x1E,0x3C,0xFE,0x1B,0x10,0x10,0x00,0x00, // Chr$(159) Ÿ
|
||||
0x00,0x3C,0x42,0x81,0xA5,0xA5,0xA5,0x81,0x42,0x3C,0x00,0x00, // Chr$(160)
|
||||
0x00,0x3C,0x42,0x91,0x99,0x9D,0x99,0x91,0x42,0x3C,0x00,0x00, // Chr$(161) ¡
|
||||
0x00,0x3C,0x42,0x81,0xBD,0xBD,0xBD,0x81,0x42,0x3C,0x00,0x00, // Chr$(162) ¢
|
||||
0x00,0x00,0x38,0x44,0x44,0x44,0x3C,0x06,0x03,0x01,0x00,0x00, // Chr$(163) £
|
||||
0x00,0x0C,0x10,0x20,0x7C,0x20,0x20,0x7C,0x20,0x10,0x0C,0x00, // Chr$(164) ¤
|
||||
0x00,0x00,0x1A,0x26,0x42,0x81,0x42,0x42,0x42,0x7E,0x00,0x00, // Chr$(165) ¥
|
||||
0x00,0x00,0x3C,0x24,0xFF,0x42,0x42,0x42,0x42,0x7E,0x00,0x00, // Chr$(166) ¦
|
||||
0x00,0x18,0x66,0x42,0x81,0xC3,0xC3,0x42,0x00,0x00,0x00,0x00, // Chr$(167) §
|
||||
0x00,0x00,0xFF,0x81,0x81,0x81,0x81,0xFF,0x18,0x3C,0x00,0x00, // Chr$(168) ¨
|
||||
0x00,0x18,0x3C,0x3C,0x3C,0x3C,0x18,0x00,0x18,0x18,0x00,0x00, // Chr$(169) ©
|
||||
0x28,0x56,0x41,0x83,0xFE,0x0C,0x10,0x10,0x20,0x00,0x00,0x00, // Chr$(170) ª
|
||||
0x00,0x30,0x48,0x48,0x48,0x4A,0x32,0x02,0x02,0x02,0x02,0x00, // Chr$(171) «
|
||||
0x18,0x24,0x66,0x5A,0x66,0x18,0x38,0x18,0x38,0x18,0x38,0x00, // Chr$(172) ¬
|
||||
0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF, // Chr$(173)
|
||||
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55, // Chr$(174) ®
|
||||
0xCC,0xCC,0x33,0x33,0xCC,0xCC,0x33,0x33,0xCC,0xCC,0x33,0x33, // Chr$(175) ¯
|
||||
0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49,0x24,0x92,0x49, // Chr$(176) °
|
||||
0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA, // Chr$(177) ±
|
||||
0x6D,0xDB,0xB6,0x6D,0xDB,0xB6,0x6D,0xDB,0xB6,0x6D,0xDB,0xB6, // Chr$(178) ²
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(179) ³
|
||||
0x18,0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(180) ´
|
||||
0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0xF8,0x18,0x18,0x18,0x18, // Chr$(181) µ
|
||||
0x66,0x66,0x66,0x66,0x66,0xE6,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(182) ¶
|
||||
0x00,0x00,0x00,0x00,0x00,0xFE,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(183) ·
|
||||
0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0xF8,0x18,0x18,0x18,0x18, // Chr$(184) ¸
|
||||
0x66,0x66,0x66,0x66,0xE6,0x06,0x06,0xE6,0x66,0x66,0x66,0x66, // Chr$(185) ¹
|
||||
0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(186) º
|
||||
0x00,0x00,0x00,0x00,0xFE,0x06,0x06,0xE6,0x66,0x66,0x66,0x66, // Chr$(187) »
|
||||
0x66,0x66,0x66,0x66,0xE6,0x06,0x06,0xFE,0x00,0x00,0x00,0x00, // Chr$(188) ¼
|
||||
0x66,0x66,0x66,0x66,0x66,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(189) ½
|
||||
0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0xF8,0x00,0x00,0x00,0x00, // Chr$(190) ¾
|
||||
0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(191) ¿
|
||||
0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(192) À
|
||||
0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(193) Á
|
||||
0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(194) Â
|
||||
0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(195) Ã
|
||||
0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(196) Ä
|
||||
0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(197) Å
|
||||
0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x1F,0x18,0x18,0x18,0x18, // Chr$(198) Æ
|
||||
0x66,0x66,0x66,0x66,0x66,0x67,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(199) Ç
|
||||
0x66,0x66,0x66,0x66,0x67,0x60,0x60,0x7F,0x00,0x00,0x00,0x00, // Chr$(200) È
|
||||
0x00,0x00,0x00,0x00,0x7F,0x60,0x60,0x67,0x66,0x66,0x66,0x66, // Chr$(201) É
|
||||
0x66,0x66,0x66,0x66,0xE7,0x00,0x00,0xFF,0x00,0x00,0x00,0x00, // Chr$(202) Ê
|
||||
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xE7,0x66,0x66,0x66,0x66, // Chr$(203) Ë
|
||||
0x66,0x66,0x66,0x66,0x67,0x60,0x60,0x67,0x66,0x66,0x66,0x66, // Chr$(204) Ì
|
||||
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0x00,0x00, // Chr$(205) Í
|
||||
0x66,0x66,0x66,0x66,0xE7,0x00,0x00,0xE7,0x66,0x66,0x66,0x66, // Chr$(206) Î
|
||||
0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0xFF,0x00,0x00,0x00,0x00, // Chr$(207) Ï
|
||||
0x66,0x66,0x66,0x66,0x66,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(208) Ð
|
||||
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x18,0x18,0x18,0x18, // Chr$(209) Ñ
|
||||
0x00,0x00,0x00,0x00,0x00,0xFF,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(210) Ò
|
||||
0x66,0x66,0x66,0x66,0x66,0x7F,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(211) Ó
|
||||
0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x1F,0x00,0x00,0x00,0x00, // Chr$(212) Ô
|
||||
0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x1F,0x18,0x18,0x18,0x18, // Chr$(213) Õ
|
||||
0x00,0x00,0x00,0x00,0x00,0x7F,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(214) Ö
|
||||
0x66,0x66,0x66,0x66,0x66,0xE7,0x66,0x66,0x66,0x66,0x66,0x66, // Chr$(215) ×
|
||||
0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0xFF,0x18,0x18,0x18,0x18, // Chr$(216) Ø
|
||||
0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(217) Ù
|
||||
0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18, // Chr$(218) Ú
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // Chr$(219) Û
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // Chr$(220) Ü
|
||||
0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0, // Chr$(221) Ý
|
||||
0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F, // Chr$(222) Þ
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(223) ß
|
||||
0x00,0x00,0x00,0x00,0x3A,0x44,0x44,0x44,0x44,0x3A,0x00,0x00, // Chr$(224) à
|
||||
0x00,0x38,0x44,0x44,0x58,0x44,0x44,0x44,0x78,0x40,0x40,0x00, // Chr$(225) á
|
||||
0x00,0x00,0x7E,0x42,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00, // Chr$(226) â
|
||||
0x00,0x00,0x00,0x00,0x7C,0x28,0x28,0x28,0x28,0x68,0x00,0x00, // Chr$(227) ã
|
||||
0x00,0x7E,0x42,0x20,0x10,0x08,0x10,0x20,0x42,0x7E,0x00,0x00, // Chr$(228) ä
|
||||
0x00,0x00,0x00,0x00,0x3E,0x48,0x44,0x44,0x44,0x38,0x00,0x00, // Chr$(229) å
|
||||
0x00,0x00,0x00,0x24,0x24,0x24,0x24,0x3C,0x42,0x40,0x80,0x00, // Chr$(230) æ
|
||||
0x00,0x00,0x00,0x00,0x44,0xA8,0x10,0x10,0x10,0x10,0x00,0x00, // Chr$(231) ç
|
||||
0x00,0x00,0x7C,0x00,0x38,0x44,0x44,0x44,0x38,0x00,0x7C,0x00, // Chr$(232) è
|
||||
0x00,0x00,0x18,0x24,0x42,0x42,0x7E,0x42,0x42,0x24,0x18,0x00, // Chr$(233) é
|
||||
0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x6C,0x28,0x6C,0x00,0x00, // Chr$(234) ê
|
||||
0x00,0x00,0x00,0x1C,0x20,0x38,0x44,0x44,0x44,0x38,0x00,0x00, // Chr$(235) ë
|
||||
0x00,0x00,0x00,0x00,0x00,0x6C,0x92,0x92,0x6C,0x00,0x00,0x00, // Chr$(236) ì
|
||||
0x00,0x00,0x00,0x00,0x02,0x6C,0x9A,0xB2,0x6C,0x80,0x00,0x00, // Chr$(237) í
|
||||
0x00,0x18,0x20,0x40,0x40,0x78,0x40,0x40,0x20,0x18,0x00,0x00, // Chr$(238) î
|
||||
0x00,0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x00,0x00, // Chr$(239) ï
|
||||
0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x00, // Chr$(240) ð
|
||||
0x00,0x00,0x10,0x10,0x7C,0x10,0x10,0x00,0x7C,0x00,0x00,0x00, // Chr$(241) ñ
|
||||
0x00,0x20,0x10,0x08,0x04,0x08,0x10,0x20,0x00,0x7E,0x00,0x00, // Chr$(242) ò
|
||||
0x00,0x04,0x08,0x10,0x20,0x10,0x08,0x04,0x00,0x7E,0x00,0x00, // Chr$(243) ó
|
||||
0x40,0x41,0x42,0x44,0x48,0x10,0x26,0x49,0x02,0x04,0x0F,0x00, // Chr$(244) ô
|
||||
0x40,0x41,0x42,0x44,0x48,0x12,0x26,0x4A,0x0F,0x02,0x02,0x00, // Chr$(245) õ
|
||||
0x00,0x00,0x18,0x18,0x00,0x7E,0x00,0x18,0x18,0x00,0x00,0x00, // Chr$(246) ö
|
||||
0x00,0x00,0x22,0x52,0x4C,0x00,0x22,0x52,0x4C,0x00,0x00,0x00, // Chr$(247) ÷
|
||||
0x00,0x20,0x10,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(248) ø
|
||||
0x00,0x00,0x00,0x00,0x1C,0x1C,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(249) ù
|
||||
0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(250) ú
|
||||
0x00,0x00,0x0E,0x08,0x08,0x08,0x08,0x48,0x28,0x18,0x08,0x00, // Chr$(251) û
|
||||
0x00,0x3C,0x12,0x12,0x12,0x12,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(252) ü
|
||||
0x00,0x18,0x24,0x08,0x10,0x3C,0x00,0x00,0x00,0x00,0x00,0x00, // Chr$(253) ý
|
||||
0x00,0x00,0x00,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x00,0x00,0x00, // Chr$(254) þ
|
||||
0x00,0x00,0x10,0x54,0x28,0xC6,0x28,0x54,0x10,0x00,0x00,0x00 // Chr$(255) ÿ
|
||||
};
|
||||
754
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c
Normal file
754
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.c
Normal file
@@ -0,0 +1,754 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <hardware/spi.h>
|
||||
#include "hardware/timer.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "lcdspi.h"
|
||||
#include "i2ckbd.h"
|
||||
#include "pico/multicore.h"
|
||||
// Fonts
|
||||
|
||||
#include "fonts/font1.h"
|
||||
unsigned char *MainFont = (unsigned char *) font1;
|
||||
|
||||
static int gui_fcolour;
|
||||
static int gui_bcolour;
|
||||
static short current_x = 0, current_y = 0; // the current default position for the next char to be written
|
||||
static short gui_font_width, gui_font_height;
|
||||
static short hres = 320; // Horizontal resolution for ILI9488
|
||||
static short vres = 320; // Vertical resolution for ILI9488
|
||||
static char s_height;
|
||||
static char s_width;
|
||||
int lcd_char_pos = 0;
|
||||
unsigned char lcd_buffer[320 * 3] = {0};// 1440 = 480*3, 320*3 = 960
|
||||
|
||||
void __not_in_flash_func(spi_write_fast)(spi_inst_t *spi, const uint8_t *src, size_t len) {
|
||||
// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
|
||||
// is full, PL022 inhibits RX pushes, and sets a sticky flag on
|
||||
// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
while (!spi_is_writable(spi))
|
||||
tight_loop_contents();
|
||||
spi_get_hw(spi)->dr = (uint32_t) src[i];
|
||||
}
|
||||
}
|
||||
|
||||
void __not_in_flash_func(spi_finish)(spi_inst_t *spi) {
|
||||
// Drain RX FIFO, then wait for shifting to finish (which may be *after*
|
||||
// TX FIFO drains), then drain RX FIFO again
|
||||
while (spi_is_readable(spi))
|
||||
(void) spi_get_hw(spi)->dr;
|
||||
while (spi_get_hw(spi)->sr & SPI_SSPSR_BSY_BITS)
|
||||
tight_loop_contents();
|
||||
while (spi_is_readable(spi))
|
||||
(void) spi_get_hw(spi)->dr;
|
||||
|
||||
// Don't leave overrun flag set
|
||||
spi_get_hw(spi)->icr = SPI_SSPICR_RORIC_BITS;
|
||||
}
|
||||
|
||||
void draw_line_spi(int x1, int y1, int x2, int y2, int color) {
|
||||
int dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
|
||||
int dy = abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
||||
int err = (dx > dy ? dx : -dy) / 2, e2;
|
||||
|
||||
while (1) {
|
||||
draw_rect_spi(x1, y1, x1, y1, color); // Draw a single pixel
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x1 += sx; }
|
||||
if (e2 < dy) { err += dx; y1 += sy; }
|
||||
}
|
||||
}
|
||||
void set_font() {
|
||||
|
||||
gui_font_width = MainFont[0];
|
||||
gui_font_height = MainFont[1];
|
||||
|
||||
s_height = vres / gui_font_height;
|
||||
s_width = hres / gui_font_width;
|
||||
}
|
||||
|
||||
void define_region_spi(int xstart, int ystart, int xend, int yend, int rw) {
|
||||
unsigned char coord[4];
|
||||
lcd_spi_lower_cs();
|
||||
gpio_put(Pico_LCD_DC, 0);
|
||||
hw_send_spi(&(uint8_t) {ILI9341_COLADDRSET}, 1);
|
||||
gpio_put(Pico_LCD_DC, 1);
|
||||
coord[0] = xstart >> 8;
|
||||
coord[1] = xstart;
|
||||
coord[2] = xend >> 8;
|
||||
coord[3] = xend;
|
||||
hw_send_spi(coord, 4);
|
||||
gpio_put(Pico_LCD_DC, 0);
|
||||
hw_send_spi(&(uint8_t) {ILI9341_PAGEADDRSET}, 1);
|
||||
gpio_put(Pico_LCD_DC, 1);
|
||||
coord[0] = ystart >> 8;
|
||||
coord[1] = ystart;
|
||||
coord[2] = yend >> 8;
|
||||
coord[3] = yend;
|
||||
hw_send_spi(coord, 4);
|
||||
gpio_put(Pico_LCD_DC, 0);
|
||||
if (rw) {
|
||||
hw_send_spi(&(uint8_t) {ILI9341_MEMORYWRITE}, 1);
|
||||
} else {
|
||||
hw_send_spi(&(uint8_t) {ILI9341_RAMRD}, 1);
|
||||
}
|
||||
gpio_put(Pico_LCD_DC, 1);
|
||||
}
|
||||
|
||||
void read_buffer_spi(int x1, int y1, int x2, int y2, unsigned char *p) {
|
||||
int r, N, t;
|
||||
unsigned char h, l;
|
||||
// make sure the coordinates are kept within the display area
|
||||
if (x2 <= x1) {
|
||||
t = x1;
|
||||
x1 = x2;
|
||||
x2 = t;
|
||||
}
|
||||
if (y2 <= y1) {
|
||||
t = y1;
|
||||
y1 = y2;
|
||||
y2 = t;
|
||||
}
|
||||
if (x1 < 0) x1 = 0;
|
||||
if (x1 >= hres) x1 = hres - 1;
|
||||
if (x2 < 0) x2 = 0;
|
||||
if (x2 >= hres) x2 = hres - 1;
|
||||
if (y1 < 0) y1 = 0;
|
||||
if (y1 >= vres) y1 = vres - 1;
|
||||
if (y2 < 0) y2 = 0;
|
||||
if (y2 >= vres) y2 = vres - 1;
|
||||
N = (x2 - x1 + 1) * (y2 - y1 + 1) * 3;
|
||||
|
||||
define_region_spi(x1, y1, x2, y2, 0);
|
||||
|
||||
spi_set_baudrate(Pico_LCD_SPI_MOD, 6000000);
|
||||
hw_read_spi((uint8_t *) p, 1);
|
||||
r = 0;
|
||||
hw_read_spi((uint8_t *) p, N);
|
||||
gpio_put(Pico_LCD_DC, 0);
|
||||
lcd_spi_raise_cs();
|
||||
spi_set_baudrate(Pico_LCD_SPI_MOD, LCD_SPI_SPEED);
|
||||
r = 0;
|
||||
|
||||
while (N) {
|
||||
h = (uint8_t) p[r + 2];
|
||||
l = (uint8_t) p[r];
|
||||
p[r] = h;//(h & 0xF8);
|
||||
p[r + 2] = l;//(l & 0xF8);
|
||||
r += 3;
|
||||
N -= 3;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_buffer_spi(int x1, int y1, int x2, int y2, unsigned char *p) {
|
||||
int i, t;
|
||||
unsigned char q[3];
|
||||
|
||||
// Boundary checking
|
||||
if (x2 <= x1) {
|
||||
t = x1;
|
||||
x1 = x2;
|
||||
x2 = t;
|
||||
}
|
||||
if (y2 <= y1) {
|
||||
t = y1;
|
||||
y1 = y2;
|
||||
y2 = t;
|
||||
}
|
||||
if (x1 < 0) x1 = 0;
|
||||
if (x1 >= hres) x1 = hres - 1;
|
||||
if (x2 < 0) x2 = 0;
|
||||
if (x2 >= hres) x2 = hres - 1;
|
||||
if (y1 < 0) y1 = 0;
|
||||
if (y1 >= vres) y1 = vres - 1;
|
||||
if (y2 < 0) y2 = 0;
|
||||
if (y2 >= vres) y2 = vres - 1;
|
||||
|
||||
// Calculate total number of pixels
|
||||
int pixelCount = (x2 - x1 + 1) * (y2 - y1 + 1);
|
||||
uint16_t *pixelBuffer = (uint16_t *)p;
|
||||
|
||||
define_region_spi(x1, y1, x2, y2, 1);
|
||||
|
||||
for (i = 0; i < pixelCount; i++) {
|
||||
uint16_t pixel = pixelBuffer[i];
|
||||
|
||||
// Extract RGB565 components
|
||||
uint8_t r5 = (pixel >> 11) & 0x1F;
|
||||
uint8_t g6 = (pixel >> 5) & 0x3F;
|
||||
uint8_t b5 = pixel & 0x1F;
|
||||
|
||||
// Convert to 8-bit values (scaling approximation)
|
||||
uint8_t r8 = (r5 << 3) | (r5 >> 2);
|
||||
uint8_t g8 = (g6 << 2) | (g6 >> 4);
|
||||
uint8_t b8 = (b5 << 3) | (b5 >> 2);
|
||||
|
||||
#ifdef ILI9488
|
||||
// Convert each RGB565 pixel to RGB888 (3 bytes per pixel) for ILI9488
|
||||
uint8_t rgb[3];
|
||||
rgb[0] = r8; // Red
|
||||
rgb[1] = g8; // Green
|
||||
rgb[2] = b8; // Blue
|
||||
hw_send_spi(rgb, 3);
|
||||
#else
|
||||
// For other controllers or if using 16-bit mode, retain the original conversion
|
||||
hw_send_spi(q, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
lcd_spi_raise_cs();
|
||||
}
|
||||
|
||||
//Print the bitmap of a char on the video output
|
||||
// x, y - the top left of the char
|
||||
// width, height - size of the char's bitmap
|
||||
// scale - how much to scale the bitmap
|
||||
// fc, bc - foreground and background colour
|
||||
// bitmap - pointer to the bitmap
|
||||
void draw_bitmap_spi(int x1, int y1, int width, int height, int scale, int fc, int bc, unsigned char *bitmap) {
|
||||
int i, j, k, m, n;
|
||||
char f[3], b[3];
|
||||
int vertCoord, horizCoord, XStart, XEnd, YEnd;
|
||||
char *p = 0;
|
||||
union colourmap {
|
||||
char rgbbytes[4];
|
||||
unsigned int rgb;
|
||||
} c;
|
||||
|
||||
if (x1 >= hres || y1 >= vres || x1 + width * scale < 0 || y1 + height * scale < 0)return;
|
||||
// adjust when part of the bitmap is outside the displayable coordinates
|
||||
vertCoord = y1;
|
||||
if (y1 < 0) y1 = 0; // the y coord is above the top of the screen
|
||||
XStart = x1;
|
||||
if (XStart < 0) XStart = 0; // the x coord is to the left of the left marginn
|
||||
XEnd = x1 + (width * scale) - 1;
|
||||
if (XEnd >= hres) XEnd = hres - 1; // the width of the bitmap will extend beyond the right margin
|
||||
YEnd = y1 + (height * scale) - 1;
|
||||
if (YEnd >= vres) YEnd = vres - 1;// the height of the bitmap will extend beyond the bottom margin
|
||||
|
||||
#ifdef ILI9488
|
||||
// convert the colours to 565 format
|
||||
f[0] = (fc >> 16);
|
||||
f[1] = (fc >> 8) & 0xFF;
|
||||
f[2] = (fc & 0xFF);
|
||||
b[0] = (bc >> 16);
|
||||
b[1] = (bc >> 8) & 0xFF;
|
||||
b[2] = (bc & 0xFF);
|
||||
|
||||
#endif
|
||||
define_region_spi(XStart, y1, XEnd, YEnd, 1);
|
||||
|
||||
n = 0;
|
||||
for (i = 0; i < height; i++) { // step thru the font scan line by line
|
||||
for (j = 0; j < scale; j++) { // repeat lines to scale the font
|
||||
if (vertCoord++ < 0) continue; // we are above the top of the screen
|
||||
if (vertCoord > vres) { // we have extended beyond the bottom of the screen
|
||||
lcd_spi_raise_cs(); //set CS high
|
||||
return;
|
||||
}
|
||||
horizCoord = x1;
|
||||
for (k = 0; k < width; k++) { // step through each bit in a scan line
|
||||
for (m = 0; m < scale; m++) { // repeat pixels to scale in the x axis
|
||||
if (horizCoord++ < 0) continue; // we have not reached the left margin
|
||||
if (horizCoord > hres) continue; // we are beyond the right margin
|
||||
if ((bitmap[((i * width) + k) / 8] >> (((height * width) - ((i * width) + k) - 1) % 8)) & 1) {
|
||||
hw_send_spi((uint8_t *) &f, 3);
|
||||
} else {
|
||||
if (bc == -1) {
|
||||
c.rgbbytes[0] = p[n];
|
||||
c.rgbbytes[1] = p[n + 1];
|
||||
c.rgbbytes[2] = p[n + 2];
|
||||
#ifdef ILI9488
|
||||
b[0] = c.rgbbytes[2];
|
||||
b[1] = c.rgbbytes[1];
|
||||
b[2] = c.rgbbytes[0];
|
||||
#endif
|
||||
}
|
||||
hw_send_spi((uint8_t *) &b, 3);
|
||||
}
|
||||
n += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lcd_spi_raise_cs(); //set CS high
|
||||
|
||||
}
|
||||
|
||||
// Draw a filled rectangle
|
||||
// this is the basic drawing promitive used by most drawing routines
|
||||
// x1, y1, x2, y2 - the coordinates
|
||||
// c - the colour
|
||||
void draw_rect_spi(int x1, int y1, int x2, int y2, int c) {
|
||||
// convert the colours to 565 format
|
||||
unsigned char col[3];
|
||||
if (x1 == x2 && y1 == y2) {
|
||||
if (x1 < 0) return;
|
||||
if (x1 >= hres) return;
|
||||
if (y1 < 0) return;
|
||||
if (y1 >= vres) return;
|
||||
define_region_spi(x1, y1, x2, y2, 1);
|
||||
#ifdef ILI9488
|
||||
col[0] = (c >> 16);
|
||||
col[1] = (c >> 8) & 0xFF;
|
||||
col[2] = (c & 0xFF);
|
||||
#endif
|
||||
hw_send_spi(col, 3);
|
||||
} else {
|
||||
int i, t, y;
|
||||
unsigned char *p;
|
||||
// make sure the coordinates are kept within the display area
|
||||
if (x2 <= x1) {
|
||||
t = x1;
|
||||
x1 = x2;
|
||||
x2 = t;
|
||||
}
|
||||
if (y2 <= y1) {
|
||||
t = y1;
|
||||
y1 = y2;
|
||||
y2 = t;
|
||||
}
|
||||
if (x1 < 0) x1 = 0;
|
||||
if (x1 >= hres) x1 = hres - 1;
|
||||
if (x2 < 0) x2 = 0;
|
||||
if (x2 >= hres) x2 = hres - 1;
|
||||
if (y1 < 0) y1 = 0;
|
||||
if (y1 >= vres) y1 = vres - 1;
|
||||
if (y2 < 0) y2 = 0;
|
||||
if (y2 >= vres) y2 = vres - 1;
|
||||
define_region_spi(x1, y1, x2, y2, 1);
|
||||
#ifdef ILI9488
|
||||
i = x2 - x1 + 1;
|
||||
i *= 3;
|
||||
p = lcd_buffer;
|
||||
col[0] = (c >> 16);
|
||||
col[1] = (c >> 8) & 0xFF;
|
||||
col[2] = (c & 0xFF);
|
||||
for (t = 0; t < i; t += 3) {
|
||||
p[t] = col[0];
|
||||
p[t + 1] = col[1];
|
||||
p[t + 2] = col[2];
|
||||
}
|
||||
for (y = y1; y <= y2; y++) {
|
||||
spi_write_fast(Pico_LCD_SPI_MOD, p, i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
spi_finish(Pico_LCD_SPI_MOD);
|
||||
lcd_spi_raise_cs();
|
||||
}
|
||||
|
||||
/******************************************************************************************
|
||||
Print a char on the LCD display
|
||||
Any characters not in the font will print as a space.
|
||||
The char is printed at the current location defined by current_x and current_y
|
||||
*****************************************************************************************/
|
||||
void lcd_print_char( int fc, int bc, char c, int orientation) {
|
||||
unsigned char *p, *fp, *np = NULL;
|
||||
int modx, mody, scale = 0x01;
|
||||
int height, width;
|
||||
|
||||
// to get the +, - and = chars for font 6 we fudge them by scaling up font 1
|
||||
fp = (unsigned char *) MainFont;
|
||||
|
||||
height = fp[1];
|
||||
width = fp[0];
|
||||
modx = mody = 0;
|
||||
|
||||
if (c >= fp[2] && c < fp[2] + fp[3]) {
|
||||
p = fp + 4 + (int) (((c - fp[2]) * height * width) / 8);
|
||||
np = p;
|
||||
|
||||
draw_bitmap_spi(current_x + modx, current_y + mody, width, height, scale, fc, bc, np);
|
||||
} else {
|
||||
draw_rect_spi(current_x + modx, current_y + mody, current_x + modx + (width * scale),
|
||||
current_y + mody + (height * scale), bc);
|
||||
}
|
||||
|
||||
if (orientation == ORIENT_NORMAL) current_x += width * scale;
|
||||
|
||||
}
|
||||
|
||||
unsigned char scrollbuff[LCD_WIDTH * 3];
|
||||
|
||||
void scroll_lcd_spi(int lines) {
|
||||
if (lines == 0)return;
|
||||
if (lines >= 0) {
|
||||
for (int i = 0; i < vres - lines; i++) {
|
||||
read_buffer_spi(0, i + lines, hres - 1, i + lines, scrollbuff);
|
||||
draw_buffer_spi(0, i, hres - 1, i, scrollbuff);
|
||||
}
|
||||
draw_rect_spi(0, vres - lines, hres - 1, vres - 1, gui_bcolour); // erase the lines to be scrolled off
|
||||
} else {
|
||||
lines = -lines;
|
||||
for (int i = vres - 1; i >= lines; i--) {
|
||||
read_buffer_spi(0, i - lines, hres - 1, i - lines, scrollbuff);
|
||||
draw_buffer_spi(0, i, hres - 1, i, scrollbuff);
|
||||
}
|
||||
draw_rect_spi(0, 0, hres - 1, lines - 1, gui_bcolour); // erase the lines introduced at the top
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void display_put_c(char c) {
|
||||
// if it is printable and it is going to take us off the right hand end of the screen do a CRLF
|
||||
if (c >= MainFont[2] && c < MainFont[2] + MainFont[3]) {
|
||||
if (current_x + gui_font_width > hres) {
|
||||
display_put_c('\r');
|
||||
display_put_c('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// handle the standard control chars
|
||||
switch (c) {
|
||||
case '\b':
|
||||
current_x -= gui_font_width;
|
||||
if (current_x < 0) { //Go to end of previous line
|
||||
current_y -= gui_font_height; //Go up one line
|
||||
if (current_y < 0) current_y = 0;
|
||||
current_x = (s_width - 1) * gui_font_width; //go to last character
|
||||
}
|
||||
return;
|
||||
case '\r':
|
||||
current_x = 0;
|
||||
return;
|
||||
case '\n':
|
||||
current_x = 0;
|
||||
current_y += gui_font_height;
|
||||
if (current_y + gui_font_height >= vres) {
|
||||
scroll_lcd_spi(current_y + gui_font_height - vres);
|
||||
current_y -= (current_y + gui_font_height - vres);
|
||||
}
|
||||
return;
|
||||
case '\t':
|
||||
do {
|
||||
display_put_c(' ');
|
||||
} while ((current_x / gui_font_width) % 2);// 2 3 4 8
|
||||
return;
|
||||
}
|
||||
lcd_print_char(gui_fcolour, gui_bcolour, c, ORIENT_NORMAL);// print it
|
||||
}
|
||||
|
||||
char lcd_put_char(char c, int flush) {
|
||||
lcd_putc(0, c);
|
||||
if (isprint(c)) lcd_char_pos++;
|
||||
if (c == '\r') {
|
||||
lcd_char_pos = 1;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void lcd_print_string(char *s) {
|
||||
while (*s) {
|
||||
if (s[1])lcd_put_char(*s, 0);
|
||||
else lcd_put_char(*s, 1);
|
||||
s++;
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void lcd_print_string_color(char *s, int fg, int bg) {
|
||||
int old_fg = gui_fcolour;
|
||||
int old_bg = gui_bcolour;
|
||||
|
||||
gui_fcolour = fg;
|
||||
gui_bcolour = bg;
|
||||
|
||||
while (*s) {
|
||||
if (s[1]) lcd_put_char(*s, 0);
|
||||
else lcd_put_char(*s, 1);
|
||||
s++;
|
||||
}
|
||||
|
||||
gui_fcolour = old_fg;
|
||||
gui_bcolour = old_bg;
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void lcd_clear() {
|
||||
draw_rect_spi(0, 0, hres - 1, vres - 1, BLACK);
|
||||
}
|
||||
|
||||
void lcd_putc(uint8_t devn, uint8_t c) {
|
||||
display_put_c(c);
|
||||
}
|
||||
|
||||
int lcd_getc(uint8_t devn) {
|
||||
//i2c keyboard
|
||||
int c = read_i2c_kbd();
|
||||
return c;
|
||||
}
|
||||
|
||||
unsigned char __not_in_flash_func(hw1_swap_spi)(unsigned char data_out) {
|
||||
unsigned char data_in = 0;
|
||||
spi_write_read_blocking(spi1, &data_out, &data_in, 1);
|
||||
return data_in;
|
||||
}
|
||||
|
||||
void hw_read_spi(unsigned char *buff, int cnt) {
|
||||
spi_read_blocking(Pico_LCD_SPI_MOD, 0xff, buff, cnt);
|
||||
}
|
||||
|
||||
void hw_send_spi(const unsigned char *buff, int cnt) {
|
||||
|
||||
spi_write_blocking(Pico_LCD_SPI_MOD, buff, cnt);
|
||||
|
||||
}
|
||||
|
||||
void pin_set_bit(int pin, unsigned int offset) {
|
||||
switch (offset) {
|
||||
case LATCLR:
|
||||
gpio_set_pulls(pin, false, false);
|
||||
gpio_pull_down(pin);
|
||||
gpio_put(pin, 0);
|
||||
return;
|
||||
case LATSET:
|
||||
gpio_set_pulls(pin, false, false);
|
||||
gpio_pull_up(pin);
|
||||
gpio_put(pin, 1);
|
||||
return;
|
||||
case LATINV:
|
||||
gpio_xor_mask(1 << pin);
|
||||
return;
|
||||
case TRISSET:
|
||||
gpio_set_dir(pin, GPIO_IN);
|
||||
sleep_us(2);
|
||||
return;
|
||||
case TRISCLR:
|
||||
gpio_set_dir(pin, GPIO_OUT);
|
||||
gpio_set_drive_strength(pin, GPIO_DRIVE_STRENGTH_12MA);
|
||||
sleep_us(2);
|
||||
return;
|
||||
case CNPUSET:
|
||||
gpio_set_pulls(pin, true, false);
|
||||
return;
|
||||
case CNPDSET:
|
||||
gpio_set_pulls(pin, false, true);
|
||||
return;
|
||||
case CNPUCLR:
|
||||
case CNPDCLR:
|
||||
gpio_set_pulls(pin, false, false);
|
||||
return;
|
||||
case ODCCLR:
|
||||
gpio_set_dir(pin, GPIO_OUT);
|
||||
gpio_put(pin, 0);
|
||||
sleep_us(2);
|
||||
return;
|
||||
case ODCSET:
|
||||
gpio_set_pulls(pin, true, false);
|
||||
gpio_set_dir(pin, GPIO_IN);
|
||||
sleep_us(2);
|
||||
return;
|
||||
case ANSELCLR:
|
||||
gpio_set_function(pin, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(pin, GPIO_IN);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//important for read lcd memory
|
||||
void reset_controller(void) {
|
||||
pin_set_bit(Pico_LCD_RST, LATSET);
|
||||
sleep_us(10000);
|
||||
pin_set_bit(Pico_LCD_RST, LATCLR);
|
||||
sleep_us(10000);
|
||||
pin_set_bit(Pico_LCD_RST, LATSET);
|
||||
sleep_us(200000);
|
||||
}
|
||||
|
||||
|
||||
void pico_lcd_init() {
|
||||
#ifdef ILI9488
|
||||
reset_controller();
|
||||
|
||||
hres = 320;
|
||||
vres = 320;
|
||||
|
||||
spi_write_command(0xE0); // Positive Gamma Control
|
||||
spi_write_data(0x00);
|
||||
spi_write_data(0x03);
|
||||
spi_write_data(0x09);
|
||||
spi_write_data(0x08);
|
||||
spi_write_data(0x16);
|
||||
spi_write_data(0x0A);
|
||||
spi_write_data(0x3F);
|
||||
spi_write_data(0x78);
|
||||
spi_write_data(0x4C);
|
||||
spi_write_data(0x09);
|
||||
spi_write_data(0x0A);
|
||||
spi_write_data(0x08);
|
||||
spi_write_data(0x16);
|
||||
spi_write_data(0x1A);
|
||||
spi_write_data(0x0F);
|
||||
|
||||
spi_write_command(0XE1); // Negative Gamma Control
|
||||
spi_write_data(0x00);
|
||||
spi_write_data(0x16);
|
||||
spi_write_data(0x19);
|
||||
spi_write_data(0x03);
|
||||
spi_write_data(0x0F);
|
||||
spi_write_data(0x05);
|
||||
spi_write_data(0x32);
|
||||
spi_write_data(0x45);
|
||||
spi_write_data(0x46);
|
||||
spi_write_data(0x04);
|
||||
spi_write_data(0x0E);
|
||||
spi_write_data(0x0D);
|
||||
spi_write_data(0x35);
|
||||
spi_write_data(0x37);
|
||||
spi_write_data(0x0F);
|
||||
|
||||
spi_write_command(0XC0); // Power Control 1
|
||||
spi_write_data(0x17);
|
||||
spi_write_data(0x15);
|
||||
|
||||
spi_write_command(0xC1); // Power Control 2
|
||||
spi_write_data(0x41);
|
||||
|
||||
spi_write_command(0xC5); // VCOM Control
|
||||
spi_write_data(0x00);
|
||||
spi_write_data(0x12);
|
||||
spi_write_data(0x80);
|
||||
|
||||
spi_write_command(TFT_MADCTL); // Memory Access Control
|
||||
spi_write_data(0x48); // MX, BGR
|
||||
|
||||
spi_write_command(0x3A); // Pixel Interface Format
|
||||
spi_write_data(0x66); // 18/24-bit colour for SPI (RGB666/RGB888)
|
||||
|
||||
spi_write_command(0xB0); // Interface Mode Control
|
||||
spi_write_data(0x00);
|
||||
|
||||
spi_write_command(0xB1); // Frame Rate Control
|
||||
spi_write_data(0xA0);
|
||||
|
||||
spi_write_command(TFT_INVON);
|
||||
|
||||
spi_write_command(0xB4); // Display Inversion Control
|
||||
spi_write_data(0x02);
|
||||
|
||||
spi_write_command(0xB6); // Display Function Control
|
||||
spi_write_data(0x02);
|
||||
spi_write_data(0x02);
|
||||
spi_write_data(0x3B);
|
||||
|
||||
spi_write_command(0xB7); // Entry Mode Set
|
||||
spi_write_data(0xC6);
|
||||
spi_write_command(0xE9);
|
||||
spi_write_data(0x00);
|
||||
|
||||
spi_write_command(0xF7); // Adjust Control 3
|
||||
spi_write_data(0xA9);
|
||||
spi_write_data(0x51);
|
||||
spi_write_data(0x2C);
|
||||
spi_write_data(0x82);
|
||||
|
||||
spi_write_command(TFT_SLPOUT); //Exit Sleep
|
||||
sleep_ms(120);
|
||||
|
||||
spi_write_command(TFT_DISPON); //Display on
|
||||
sleep_ms(120);
|
||||
|
||||
spi_write_command(TFT_MADCTL);
|
||||
spi_write_cd(ILI9341_MEMCONTROL, 1, ILI9341_Portrait);
|
||||
#endif
|
||||
}
|
||||
|
||||
void lcd_spi_raise_cs(void) {
|
||||
gpio_put(Pico_LCD_CS, 1);
|
||||
}
|
||||
|
||||
void lcd_set_cursor(int x, int y) {
|
||||
current_x = x;
|
||||
current_y = y;
|
||||
}
|
||||
|
||||
void lcd_spi_lower_cs(void) {
|
||||
|
||||
gpio_put(Pico_LCD_CS, 0);
|
||||
|
||||
}
|
||||
|
||||
void spi_write_data(unsigned char data) {
|
||||
gpio_put(Pico_LCD_DC, 1);
|
||||
lcd_spi_lower_cs();
|
||||
hw_send_spi(&data, 1);
|
||||
lcd_spi_raise_cs();
|
||||
}
|
||||
|
||||
void spi_write_data24(uint32_t data) {
|
||||
uint8_t data_array[3];
|
||||
data_array[0] = data >> 16;
|
||||
data_array[1] = (data >> 8) & 0xFF;
|
||||
data_array[2] = data & 0xFF;
|
||||
|
||||
|
||||
gpio_put(Pico_LCD_DC, 1); // Data mode
|
||||
gpio_put(Pico_LCD_CS, 0);
|
||||
spi_write_blocking(Pico_LCD_SPI_MOD, data_array, 3);
|
||||
gpio_put(Pico_LCD_CS, 1);
|
||||
}
|
||||
|
||||
void spi_write_command(unsigned char data) {
|
||||
gpio_put(Pico_LCD_DC, 0);
|
||||
gpio_put(Pico_LCD_CS, 0);
|
||||
|
||||
spi_write_blocking(Pico_LCD_SPI_MOD, &data, 1);
|
||||
|
||||
gpio_put(Pico_LCD_CS, 1);
|
||||
}
|
||||
|
||||
void spi_write_cd(unsigned char command, int data, ...) {
|
||||
int i;
|
||||
va_list ap;
|
||||
va_start(ap, data);
|
||||
spi_write_command(command);
|
||||
for (i = 0; i < data; i++) spi_write_data((char) va_arg(ap, int));
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void lcd_spi_init() {
|
||||
// init GPIO
|
||||
gpio_init(Pico_LCD_SCK);
|
||||
gpio_init(Pico_LCD_TX);
|
||||
gpio_init(Pico_LCD_RX);
|
||||
gpio_init(Pico_LCD_CS);
|
||||
gpio_init(Pico_LCD_DC);
|
||||
gpio_init(Pico_LCD_RST);
|
||||
|
||||
gpio_set_dir(Pico_LCD_SCK, GPIO_OUT);
|
||||
gpio_set_dir(Pico_LCD_TX, GPIO_OUT);
|
||||
gpio_set_dir(Pico_LCD_CS, GPIO_OUT);
|
||||
gpio_set_dir(Pico_LCD_DC, GPIO_OUT);
|
||||
gpio_set_dir(Pico_LCD_RST, GPIO_OUT);
|
||||
|
||||
// init SPI
|
||||
spi_init(Pico_LCD_SPI_MOD, LCD_SPI_SPEED);
|
||||
gpio_set_function(Pico_LCD_SCK, GPIO_FUNC_SPI);
|
||||
gpio_set_function(Pico_LCD_TX, GPIO_FUNC_SPI);
|
||||
gpio_set_function(Pico_LCD_RX, GPIO_FUNC_SPI);
|
||||
gpio_set_input_hysteresis_enabled(Pico_LCD_RX, true);
|
||||
|
||||
gpio_put(Pico_LCD_CS, 1);
|
||||
gpio_put(Pico_LCD_RST, 1);
|
||||
}
|
||||
|
||||
|
||||
void lcd_init() {
|
||||
lcd_spi_init();
|
||||
pico_lcd_init();
|
||||
|
||||
set_font();
|
||||
gui_fcolour = WHITE;
|
||||
gui_bcolour = GRAY;
|
||||
|
||||
}
|
||||
148
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h
Normal file
148
Code/pico_multi_booter/sd_boot/lcdspi/lcdspi.h
Normal file
@@ -0,0 +1,148 @@
|
||||
#ifndef LCDSPI_H
|
||||
#define LCDSPI_H
|
||||
#include "pico/multicore.h"
|
||||
#include <hardware/spi.h>
|
||||
|
||||
//#define LCD_SPI_SPEED 6000000
|
||||
#define LCD_SPI_SPEED 25000000
|
||||
//#define LCD_SPI_SPEED 50000000
|
||||
|
||||
#define Pico_LCD_SCK 10 //
|
||||
#define Pico_LCD_TX 11 // MOSI
|
||||
#define Pico_LCD_RX 12 // MISO
|
||||
#define Pico_LCD_CS 13 //
|
||||
#define Pico_LCD_DC 14
|
||||
#define Pico_LCD_RST 15
|
||||
|
||||
#define ILI9488 1
|
||||
#ifdef ILI9488
|
||||
#define LCD_WIDTH 320
|
||||
#define LCD_HEIGHT 320
|
||||
#endif
|
||||
|
||||
#define PIXFMT_BGR 1
|
||||
|
||||
#define TFT_SLPOUT 0x11
|
||||
#define TFT_INVOFF 0x20
|
||||
#define TFT_INVON 0x21
|
||||
|
||||
#define TFT_DISPOFF 0x28
|
||||
#define TFT_DISPON 0x29
|
||||
#define TFT_MADCTL 0x36
|
||||
|
||||
#define ILI9341_MEMCONTROL 0x36
|
||||
#define ILI9341_MADCTL_MX 0x40
|
||||
#define ILI9341_MADCTL_BGR 0x08
|
||||
|
||||
#define ILI9341_COLADDRSET 0x2A
|
||||
#define ILI9341_PAGEADDRSET 0x2B
|
||||
#define ILI9341_MEMORYWRITE 0x2C
|
||||
#define ILI9341_RAMRD 0x2E
|
||||
|
||||
#define ILI9341_Portrait ILI9341_MADCTL_MX | ILI9341_MADCTL_BGR
|
||||
|
||||
#define ORIENT_NORMAL 0
|
||||
|
||||
#define RGB(red, green, blue) (unsigned int) (((red & 0b11111111) << 16) | ((green & 0b11111111) << 8) | (blue & 0b11111111))
|
||||
#define WHITE RGB(255, 255, 255) //0b1111
|
||||
#define YELLOW RGB(255, 255, 0) //0b1110
|
||||
#define LILAC RGB(255, 128, 255) //0b1101
|
||||
#define BROWN RGB(255, 128, 0) //0b1100
|
||||
#define FUCHSIA RGB(255, 64, 255) //0b1011
|
||||
#define RUST RGB(255, 64, 0) //0b1010
|
||||
#define MAGENTA RGB(255, 0, 255) //0b1001
|
||||
#define RED RGB(255, 0, 0) //0b1000
|
||||
#define CYAN RGB(0, 255, 255) //0b0111
|
||||
#define GREEN RGB(0, 255, 0) //0b0110
|
||||
#define CERULEAN RGB(0, 128, 255) //0b0101
|
||||
#define MIDGREEN RGB(0, 128, 0) //0b0100
|
||||
#define COBALT RGB(0, 64, 255) //0b0011
|
||||
#define MYRTLE RGB(0, 64, 0) //0b0010
|
||||
#define BLUE RGB(0, 0, 255) //0b0001
|
||||
#define BLACK RGB(0, 0, 0) //0b0000
|
||||
#define BROWN RGB(255, 128, 0)
|
||||
#define GRAY RGB(128, 128, 128)
|
||||
#define LITEGRAY RGB(210, 210, 210)
|
||||
#define ORANGE RGB(0xff, 0xA5, 0)
|
||||
#define PINK RGB(0xFF, 0xA0, 0xAB)
|
||||
#define GOLD RGB(0xFF, 0xD7, 0x00)
|
||||
#define SALMON RGB(0xFA, 0x80, 0x72)
|
||||
#define BEIGE RGB(0xF5, 0xF5, 0xDC)
|
||||
|
||||
//Pico spi0 or spi1 must match GPIO pins used above.
|
||||
#define Pico_LCD_SPI_MOD spi1
|
||||
#define nop asm("NOP")
|
||||
//xmit_byte_multi == HW1SendSPI
|
||||
|
||||
|
||||
#define PORTCLR 1
|
||||
#define PORTSET 2
|
||||
#define PORTINV 3
|
||||
#define LAT 4
|
||||
#define LATCLR 5
|
||||
#define LATSET 6
|
||||
#define LATINV 7
|
||||
#define ODC 8
|
||||
#define ODCCLR 9
|
||||
#define ODCSET 10
|
||||
#define CNPU 12
|
||||
#define CNPUCLR 13
|
||||
#define CNPUSET 14
|
||||
#define CNPUINV 15
|
||||
#define CNPD 16
|
||||
#define CNPDCLR 17
|
||||
#define CNPDSET 18
|
||||
|
||||
#define ANSELCLR -7
|
||||
#define ANSELSET -6
|
||||
#define ANSELINV -5
|
||||
#define TRIS -4
|
||||
#define TRISCLR -3
|
||||
#define TRISSET -2
|
||||
|
||||
extern void __not_in_flash_func(spi_write_fast)(spi_inst_t *spi, const uint8_t *src, size_t len);
|
||||
extern void __not_in_flash_func(spi_finish)(spi_inst_t *spi);
|
||||
extern void hw_read_spi(unsigned char *buff, int cnt);
|
||||
extern void hw_send_spi(const unsigned char *buff, int cnt);
|
||||
extern unsigned char __not_in_flash_func(hw1_swap_spi)(unsigned char data_out);
|
||||
|
||||
extern void lcd_spi_raise_cs(void);
|
||||
extern void lcd_spi_lower_cs(void);
|
||||
extern void spi_write_data(unsigned char data);
|
||||
extern void spi_write_command(unsigned char data);
|
||||
extern void spi_write_cd(unsigned char command, int data, ...);
|
||||
extern void spi_write_data24(uint32_t data);
|
||||
|
||||
extern void spi_draw_pixel(uint16_t x, uint16_t y, uint16_t color) ;
|
||||
extern void lcd_putc(uint8_t devn, uint8_t c);
|
||||
extern int lcd_getc(uint8_t devn);
|
||||
extern void lcd_sleeping(uint8_t devn);
|
||||
|
||||
|
||||
void draw_rect_spi(int x1, int y1, int x2, int y2, int c);
|
||||
void define_region_spi(int xstart, int ystart, int xend, int yend, int rw);
|
||||
void draw_line_spi(int x1, int y1, int x2, int y2, int color);
|
||||
void lcd_print_string_color(char *s, int fg, int bg);
|
||||
|
||||
//Print the bitmap of a char on the video output
|
||||
// x, y - the top left of the char
|
||||
// width, height - size of the char's bitmap
|
||||
// scale - how much to scale the bitmap
|
||||
// fc, bc - foreground and background colour
|
||||
// bitmap - pointer to the bitmap
|
||||
void draw_bitmap_spi(int x1, int y1, int width, int height, int scale, int fc, int bc, unsigned char *bitmap);
|
||||
void draw_buffer_spi(int x1, int y1, int x2, int y2, unsigned char *p);
|
||||
|
||||
|
||||
extern char lcd_put_char(char c, int flush);
|
||||
extern void lcd_print_string(char* s);
|
||||
|
||||
extern void lcd_spi_init();
|
||||
extern void lcd_init();
|
||||
extern void lcd_clear();
|
||||
extern void reset_controller(void);
|
||||
extern void pin_set_bit(int pin, unsigned int offset);
|
||||
|
||||
extern void lcd_set_cursor(int x, int y);
|
||||
|
||||
#endif
|
||||
332
Code/pico_multi_booter/sd_boot/main.c
Normal file
332
Code/pico_multi_booter/sd_boot/main.c
Normal file
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* PicoCalc SD Firmware Loader
|
||||
*
|
||||
* Author: Hsuan Han Lai
|
||||
* Email: hsuan.han.lai@gmail.com
|
||||
* Website: https://hsuanhanlai.com
|
||||
* Year: 2025
|
||||
*
|
||||
*
|
||||
* This project is a bootloader for the PicoCalc device, designed to load and execute
|
||||
* firmware applications from an SD card.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "debug.h"
|
||||
#include "i2ckbd.h"
|
||||
#include "lcdspi.h"
|
||||
#include <hardware/flash.h>
|
||||
#include <errno.h>
|
||||
#include <hardware/watchdog.h>
|
||||
#include "config.h"
|
||||
|
||||
#include "blockdevice/sd.h"
|
||||
#include "filesystem/fat.h"
|
||||
#include "filesystem/vfs.h"
|
||||
#include "text_directory_ui.h"
|
||||
#include "key_event.h"
|
||||
|
||||
const uint LEDPIN = 25;
|
||||
|
||||
// Vector and RAM offset
|
||||
#if PICO_RP2040
|
||||
#define VTOR_OFFSET M0PLUS_VTOR_OFFSET
|
||||
#define MAX_RAM 0x20040000
|
||||
#elif PICO_RP2350
|
||||
#define VTOR_OFFSET M33_VTOR_OFFSET
|
||||
#define MAX_RAM 0x20080000
|
||||
#endif
|
||||
|
||||
bool sd_card_inserted(void)
|
||||
{
|
||||
// Active low detection - returns true when pin is low
|
||||
return !gpio_get(SD_DET_PIN);
|
||||
}
|
||||
|
||||
bool fs_init(void)
|
||||
{
|
||||
DEBUG_PRINT("fs init SD\n");
|
||||
blockdevice_t *sd = blockdevice_sd_create(spi0,
|
||||
SD_MOSI_PIN,
|
||||
SD_MISO_PIN,
|
||||
SD_SCLK_PIN,
|
||||
SD_CS_PIN,
|
||||
125000000 / 2 / 4, // 15.6MHz
|
||||
true);
|
||||
filesystem_t *fat = filesystem_fat_create();
|
||||
int err = fs_mount("/", fat, sd);
|
||||
if (err == -1)
|
||||
{
|
||||
DEBUG_PRINT("format /\n");
|
||||
err = fs_format(fat, sd);
|
||||
if (err == -1)
|
||||
{
|
||||
DEBUG_PRINT("format err: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
err = fs_mount("/", fat, sd);
|
||||
if (err == -1)
|
||||
{
|
||||
DEBUG_PRINT("mount err: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __not_in_flash_func(is_same_existing_program)(FILE *fp)
|
||||
{
|
||||
uint8_t buffer[FLASH_SECTOR_SIZE] = {0};
|
||||
size_t program_size = 0;
|
||||
size_t len = 0;
|
||||
while ((len = fread(buffer, 1, sizeof(buffer), fp)) > 0)
|
||||
{
|
||||
uint8_t *flash = (uint8_t *)(XIP_BASE + SD_BOOT_FLASH_OFFSET + program_size);
|
||||
if (memcmp(buffer, flash, len) != 0)
|
||||
return false;
|
||||
program_size += len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function must run from RAM since it erases and programs flash memory
|
||||
static bool __not_in_flash_func(load_program)(const char *filename)
|
||||
{
|
||||
FILE *fp = fopen(filename, "r");
|
||||
if (fp == NULL)
|
||||
{
|
||||
DEBUG_PRINT("open %s fail: %s\n", filename, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (is_same_existing_program(fp))
|
||||
{
|
||||
// Program is up to date
|
||||
}
|
||||
|
||||
// Check file size to ensure it doesn't exceed the available flash space
|
||||
if (fseek(fp, 0, SEEK_END) == -1)
|
||||
{
|
||||
DEBUG_PRINT("seek err: %s\n", strerror(errno));
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
long file_size = ftell(fp);
|
||||
if (file_size <= 0)
|
||||
{
|
||||
DEBUG_PRINT("invalid size: %ld\n", file_size);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file_size > MAX_APP_SIZE)
|
||||
{
|
||||
DEBUG_PRINT("file too large: %ld > %d\n", file_size, MAX_APP_SIZE);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_PRINT("updating: %ld bytes\n", file_size);
|
||||
if (fseek(fp, 0, SEEK_SET) == -1)
|
||||
{
|
||||
DEBUG_PRINT("seek err: %s\n", strerror(errno));
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t program_size = 0;
|
||||
uint8_t buffer[FLASH_SECTOR_SIZE] = {0};
|
||||
size_t len = 0;
|
||||
|
||||
// Erase and program flash in FLASH_SECTOR_SIZE chunks
|
||||
while ((len = fread(buffer, 1, sizeof(buffer), fp)) > 0)
|
||||
{
|
||||
// Ensure we don't write beyond the application area
|
||||
if ((program_size + len) > MAX_APP_SIZE)
|
||||
{
|
||||
DEBUG_PRINT("err: write beyond app area\n");
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ints = save_and_disable_interrupts();
|
||||
flash_range_erase(SD_BOOT_FLASH_OFFSET + program_size, FLASH_SECTOR_SIZE);
|
||||
flash_range_program(SD_BOOT_FLASH_OFFSET + program_size, buffer, len);
|
||||
restore_interrupts(ints);
|
||||
|
||||
program_size += len;
|
||||
}
|
||||
DEBUG_PRINT("program loaded\n");
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function jumps to the application entry point
|
||||
// It must update the vector table and stack pointer before jumping
|
||||
void __not_in_flash_func(launch_application_from)(uint32_t *app_location)
|
||||
{
|
||||
// https://vanhunteradams.com/Pico/Bootloader/Bootloader.html
|
||||
uint32_t *new_vector_table = app_location;
|
||||
volatile uint32_t *vtor = (uint32_t *)(PPB_BASE + VTOR_OFFSET);
|
||||
*vtor = (uint32_t)new_vector_table;
|
||||
asm volatile(
|
||||
"msr msp, %0\n"
|
||||
"bx %1\n"
|
||||
:
|
||||
: "r"(new_vector_table[0]), "r"(new_vector_table[1])
|
||||
:);
|
||||
}
|
||||
|
||||
// Check if a valid application exists in flash by examining the vector table
|
||||
static bool is_valid_application(uint32_t *app_location)
|
||||
{
|
||||
// Check that the initial stack pointer is within a plausible RAM region (assumed range for Pico: 0x20000000 to 0x20040000)
|
||||
uint32_t stack_pointer = app_location[0];
|
||||
if (stack_pointer < 0x20000000 || stack_pointer > MAX_RAM)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the reset vector is within the valid flash application area
|
||||
uint32_t reset_vector = app_location[1];
|
||||
if (reset_vector < (0x10000000 + SD_BOOT_FLASH_OFFSET) || reset_vector > (0x10000000 + PICO_FLASH_SIZE_BYTES))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int load_firmware_by_path(const char *path)
|
||||
{
|
||||
text_directory_ui_set_status("STAT: loading app...");
|
||||
|
||||
// Attempt to load the application from the SD card
|
||||
// bool load_success = load_program(FIRMWARE_PATH);
|
||||
bool load_success = load_program(path);
|
||||
|
||||
// Get the pointer to the application flash area
|
||||
uint32_t *app_location = (uint32_t *)(XIP_BASE + SD_BOOT_FLASH_OFFSET);
|
||||
|
||||
// Check if there is an already valid application in flash
|
||||
bool has_valid_app = is_valid_application(app_location);
|
||||
|
||||
|
||||
|
||||
if (load_success || has_valid_app)
|
||||
{
|
||||
text_directory_ui_set_status("STAT: launching app...");
|
||||
DEBUG_PRINT("launching app\n");
|
||||
// Small delay to allow printf to complete
|
||||
sleep_ms(100);
|
||||
launch_application_from(app_location);
|
||||
}
|
||||
else
|
||||
{
|
||||
text_directory_ui_set_status("ERR: No valid app");
|
||||
DEBUG_PRINT("no valid app, halting\n");
|
||||
|
||||
sleep_ms(2000);
|
||||
|
||||
// Trigger a watchdog reboot
|
||||
watchdog_reboot(0, 0, 0);
|
||||
}
|
||||
|
||||
// We should never reach here
|
||||
while (1)
|
||||
{
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
|
||||
void final_selection_callback(const char *path)
|
||||
{
|
||||
// Trigger firmware loading with the selected path
|
||||
DEBUG_PRINT("selected: %s\n", path);
|
||||
|
||||
char status_message[128];
|
||||
const char *extension = ".bin";
|
||||
size_t path_len = strlen(path);
|
||||
size_t ext_len = strlen(extension);
|
||||
|
||||
if (path_len < ext_len || strcmp(path + path_len - ext_len, extension) != 0)
|
||||
{
|
||||
DEBUG_PRINT("not a bin: %s\n", path);
|
||||
snprintf(status_message, sizeof(status_message), "Err: FILE is not a .bin file");
|
||||
text_directory_ui_set_status(status_message);
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(status_message, sizeof(status_message), "SEL: %s", path);
|
||||
text_directory_ui_set_status(status_message);
|
||||
|
||||
sleep_ms(200);
|
||||
|
||||
load_firmware_by_path(path);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
char buf[64];
|
||||
stdio_init_all();
|
||||
|
||||
uart_init(uart0, 115200);
|
||||
uart_set_format(uart0, 8, 1, UART_PARITY_NONE); // 8-N-1
|
||||
uart_set_fifo_enabled(uart0, false);
|
||||
|
||||
// Initialize SD card detection pin
|
||||
gpio_init(SD_DET_PIN);
|
||||
gpio_set_dir(SD_DET_PIN, GPIO_IN);
|
||||
gpio_pull_up(SD_DET_PIN); // Enable pull-up resistor
|
||||
|
||||
keypad_init();
|
||||
lcd_init();
|
||||
lcd_clear();
|
||||
text_directory_ui_init();
|
||||
|
||||
// Check for SD card presence
|
||||
DEBUG_PRINT("Checking for SD card...\n");
|
||||
if (!sd_card_inserted())
|
||||
{
|
||||
DEBUG_PRINT("SD card not detected\n");
|
||||
text_directory_ui_set_status("SD card not detected. \nPlease insert SD card.");
|
||||
|
||||
// Poll until SD card is inserted
|
||||
while (!sd_card_inserted())
|
||||
{
|
||||
sleep_ms(100);
|
||||
}
|
||||
|
||||
// Card detected, wait for it to stabilize
|
||||
DEBUG_PRINT("SD card detected\n");
|
||||
text_directory_ui_set_status("SD card detected. Mounting...");
|
||||
sleep_ms(1500); // Wait for card to stabilize
|
||||
}
|
||||
else
|
||||
{
|
||||
// If SD card is detected at boot, wait for stabilization
|
||||
DEBUG_PRINT("SD card stabilization delay on boot\n");
|
||||
text_directory_ui_set_status("Stabilizing SD card...");
|
||||
sleep_ms(1500); // Delay to allow the SD card to fully power up and stabilize
|
||||
}
|
||||
|
||||
// Initialize filesystem
|
||||
if (!fs_init())
|
||||
{
|
||||
text_directory_ui_set_status("Failed to mount SD card!");
|
||||
DEBUG_PRINT("Failed to mount SD card\n");
|
||||
sleep_ms(2000);
|
||||
watchdog_reboot(0, 0, 0);
|
||||
}
|
||||
|
||||
sleep_ms(500);
|
||||
lcd_clear();
|
||||
|
||||
text_directory_ui_init();
|
||||
text_directory_ui_set_final_callback(final_selection_callback);
|
||||
text_directory_ui_run();
|
||||
}
|
||||
516
Code/pico_multi_booter/sd_boot/text_directory_ui.c
Normal file
516
Code/pico_multi_booter/sd_boot/text_directory_ui.c
Normal file
@@ -0,0 +1,516 @@
|
||||
/**
|
||||
* PicoCalc SD Firmware Loader
|
||||
*
|
||||
* Author: Hsuan Han Lai
|
||||
* Email: hsuan.han.lai@gmail.com
|
||||
* Website: https://hsuanhanlai.com
|
||||
* Year: 2025
|
||||
*
|
||||
* text_directory_ui.c
|
||||
*
|
||||
* Implementation for the Text Directory UI Navigator.
|
||||
*
|
||||
* This module provides a text-based UI for navigating directories and files on an SD card.
|
||||
* It uses lcdspi APIs for rendering, key_event APIs for input handling, and pico-vfs/standard POSIX APIs
|
||||
* for filesystem operations.
|
||||
*
|
||||
* Features:
|
||||
* - UI Initialization: Sets up the display, input handling, and mounts the SD card filesystem.
|
||||
* - Directory Navigation: Allows navigation through directories and files using arrow keys.
|
||||
* - File Selection: Invokes a callback when a file is selected.
|
||||
* - Status Messages: Displays temporary status messages at the bottom of the UI.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <hardware/watchdog.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "lcdspi/lcdspi.h"
|
||||
#include "key_event.h"
|
||||
#include "text_directory_ui.h"
|
||||
#include "debug.h"
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
// External functions for SD card handling
|
||||
extern bool sd_card_inserted(void);
|
||||
extern bool fs_init(void);
|
||||
|
||||
// UI Layout Constants
|
||||
#define UI_WIDTH 280
|
||||
#define UI_HEIGHT 280
|
||||
#define UI_X 20 // Offset from top-left
|
||||
#define UI_Y 20
|
||||
#define HEADER_TITLE_HEIGHT 20 // Height for the title header
|
||||
#define PATH_HEADER_HEIGHT 16 // Height for the current path display
|
||||
#define STATUS_BAR_HEIGHT 16 // Height for the status bar
|
||||
|
||||
// UI Colors
|
||||
#define COLOR_BG BLACK
|
||||
#define COLOR_FG WHITE
|
||||
#define COLOR_HIGHLIGHT GRAY
|
||||
|
||||
// Maximum number of directory entries
|
||||
#define MAX_ENTRIES 128
|
||||
|
||||
// Data structure for directory entries
|
||||
typedef struct
|
||||
{
|
||||
char name[256];
|
||||
int is_dir; // 1 if directory, 0 if file
|
||||
off_t file_size; // Size of the file in bytes
|
||||
} dir_entry_t;
|
||||
|
||||
// UI Layout Constants for file display
|
||||
#define FILE_NAME_X (UI_X + 4)
|
||||
#define FILE_NAME_AREA_WIDTH 200
|
||||
#define FILE_SIZE_X (UI_X + UI_WIDTH - 70)
|
||||
#define FILE_SIZE_AREA_WIDTH 60
|
||||
#define CHAR_WIDTH 8
|
||||
#define FILE_NAME_VISIBLE_CHARS (FILE_NAME_AREA_WIDTH / CHAR_WIDTH)
|
||||
#define SCROLL_DELAY_MS 300
|
||||
|
||||
// Global variables for UI state
|
||||
static char current_path[512] = "/sd"; // Current directory path
|
||||
static dir_entry_t entries[MAX_ENTRIES]; // Directory entries
|
||||
static int entry_count = 0; // Number of entries in the current directory
|
||||
static int selected_index = 0; // Currently selected entry index
|
||||
static char status_message[256] = ""; // Status message
|
||||
static uint32_t status_timestamp = 0; // Timestamp for status message
|
||||
static final_selection_callback_t final_callback = NULL; // Callback for file selection
|
||||
static uint32_t last_scrolling = 0; // for text scrolling in selected entry
|
||||
// Forward declarations
|
||||
static void ui_refresh(void);
|
||||
static void load_directory(const char *path);
|
||||
static void process_key_event(int key);
|
||||
static void ui_draw_title(void);
|
||||
static void ui_draw_path_header(void);
|
||||
static void ui_draw_directory_list(void);
|
||||
static void ui_draw_directory_entry(int entry_idx, int posY, int font_height, int is_selected);
|
||||
static void ui_update_selected_entry(void);
|
||||
static void ui_draw_status_bar(void);
|
||||
static void format_file_size(off_t size, int is_dir, char *buf, size_t buf_size);
|
||||
static void get_scrolling_text(const char *text, char *out, size_t out_size, int visible_chars);
|
||||
|
||||
// Helper: Draw a filled rectangle
|
||||
static void draw_filled_rect(int x, int y, int width, int height, int color)
|
||||
{
|
||||
draw_rect_spi(x, y, x + width - 1, y + height - 1, color);
|
||||
}
|
||||
|
||||
// Helper: Draw text at a specific position
|
||||
static void draw_text(int x, int y, const char *text, int foreground, int background)
|
||||
{
|
||||
lcd_set_cursor(x, y);
|
||||
lcd_print_string_color((char *)text, foreground, background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size into human-readable string
|
||||
* Converts raw byte count to KB or MB with appropriate suffix
|
||||
*/
|
||||
static void format_file_size(off_t size, int is_dir, char *buf, size_t buf_size)
|
||||
{
|
||||
if (is_dir)
|
||||
{
|
||||
snprintf(buf, buf_size, "DIR");
|
||||
}
|
||||
else if (size >= 1024 * 1024)
|
||||
{
|
||||
double mb = size / (1024.0 * 1024.0);
|
||||
snprintf(buf, buf_size, "%.1fMB", mb);
|
||||
}
|
||||
else
|
||||
{
|
||||
int kb = size / 1024;
|
||||
if (kb < 1)
|
||||
kb = 1;
|
||||
snprintf(buf, buf_size, "%dKB", kb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create scrolling text for long filenames
|
||||
* Creates a continuous scroll effect for text that exceeds visible area
|
||||
*/
|
||||
static void get_scrolling_text(const char *text, char *out, size_t out_size, int visible_chars)
|
||||
{
|
||||
char scroll_buffer[512];
|
||||
snprintf(scroll_buffer, sizeof(scroll_buffer), "%s %s", text, text);
|
||||
int scroll_len = strlen(scroll_buffer);
|
||||
uint32_t time_ms = (time_us_64() / 1000) - last_scrolling;
|
||||
int offset = (time_ms / SCROLL_DELAY_MS) % scroll_len;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < visible_chars && i < out_size - 1; i++)
|
||||
{
|
||||
int idx = (offset + i) % scroll_len;
|
||||
out[i] = scroll_buffer[idx];
|
||||
}
|
||||
out[i] = '\0';
|
||||
}
|
||||
|
||||
bool has_suffix(const char *filename, const char *suffix) {
|
||||
size_t len_filename = strlen(filename);
|
||||
size_t len_suffix = strlen(suffix);
|
||||
if (len_filename < len_suffix) return false;
|
||||
return strcmp(filename + len_filename - len_suffix, suffix) == 0;
|
||||
}
|
||||
|
||||
// Load directory entries into the global entries array
|
||||
static void load_directory(const char *path)
|
||||
{
|
||||
DIR *dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
entry_count = 0;
|
||||
return;
|
||||
}
|
||||
entry_count = 0;
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir)) != NULL && entry_count < MAX_ENTRIES)
|
||||
{
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
|
||||
continue;
|
||||
if( has_suffix(ent->d_name,".bin") == false)
|
||||
continue;
|
||||
strncpy(entries[entry_count].name, ent->d_name, sizeof(entries[entry_count].name) - 1);
|
||||
entries[entry_count].name[sizeof(entries[entry_count].name) - 1] = '\0';
|
||||
|
||||
// Build full path for stat
|
||||
char full_path[512];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", path, ent->d_name);
|
||||
|
||||
// Determine if the entry is a directory and get file size
|
||||
if (ent->d_type != DT_UNKNOWN)
|
||||
{
|
||||
entries[entry_count].is_dir = (ent->d_type == DT_DIR) ? 1 : 0;
|
||||
|
||||
// Get file size using stat even if we know the type from d_type
|
||||
struct stat statbuf;
|
||||
if (stat(full_path, &statbuf) == 0)
|
||||
{
|
||||
entries[entry_count].file_size = entries[entry_count].is_dir ? 0 : statbuf.st_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
entries[entry_count].file_size = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct stat statbuf;
|
||||
if (stat(full_path, &statbuf) == 0)
|
||||
{
|
||||
entries[entry_count].is_dir = S_ISDIR(statbuf.st_mode) ? 1 : 0;
|
||||
entries[entry_count].file_size = entries[entry_count].is_dir ? 0 : statbuf.st_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
entries[entry_count].is_dir = 0;
|
||||
entries[entry_count].file_size = 0;
|
||||
}
|
||||
}
|
||||
entry_count++;
|
||||
}
|
||||
closedir(dir);
|
||||
selected_index = 0;
|
||||
if(entry_count == 0) {
|
||||
text_directory_ui_set_status("No firmware found.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draw the title header
|
||||
static void ui_draw_title(void)
|
||||
{
|
||||
draw_rect_spi(UI_X, UI_Y, UI_X + UI_WIDTH - 1, UI_Y + HEADER_TITLE_HEIGHT, BLACK);
|
||||
draw_text(UI_X + 2, UI_Y + 2, "PicoCalc SD Firmware Loader", WHITE, BLACK);
|
||||
}
|
||||
|
||||
// Draw the current path header
|
||||
static void ui_draw_path_header(void)
|
||||
{
|
||||
char path_header[300];
|
||||
snprintf(path_header, sizeof(path_header), "Path: %s", current_path);
|
||||
int y = UI_Y + HEADER_TITLE_HEIGHT;
|
||||
draw_rect_spi(UI_X, y, UI_X + UI_WIDTH - 1, y + PATH_HEADER_HEIGHT - 1, COLOR_BG);
|
||||
draw_text(UI_X + 2, y + 2, path_header, COLOR_FG, COLOR_BG);
|
||||
draw_line_spi(UI_X, y + PATH_HEADER_HEIGHT - 2, UI_X + UI_WIDTH - 1, y + PATH_HEADER_HEIGHT - 2, COLOR_FG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a single directory entry
|
||||
*
|
||||
* @param entry_idx Index of the entry in the entries array
|
||||
* @param posY Vertical position to draw the entry
|
||||
* @param font_height Height of the font
|
||||
* @param is_selected Whether this entry is currently selected
|
||||
*/
|
||||
static void ui_draw_directory_entry(int entry_idx, int posY, int font_height, int is_selected)
|
||||
{
|
||||
// Highlight background for selected item
|
||||
if (is_selected)
|
||||
{
|
||||
draw_rect_spi(UI_X, posY - 1, UI_X + UI_WIDTH - 1, posY + font_height, COLOR_HIGHLIGHT);
|
||||
}
|
||||
|
||||
// Prepare filename with directory indicator
|
||||
char full_file_name[300];
|
||||
snprintf(full_file_name, sizeof(full_file_name), "%s%s",
|
||||
entries[entry_idx].name,
|
||||
entries[entry_idx].is_dir ? "/" : "");
|
||||
|
||||
// Prepare display text with scrolling for selected items
|
||||
char display_buffer[300];
|
||||
if (is_selected && strlen(full_file_name) > FILE_NAME_VISIBLE_CHARS)
|
||||
{
|
||||
// Use scrolling text for selected long filenames
|
||||
get_scrolling_text(full_file_name, display_buffer, sizeof(display_buffer), FILE_NAME_VISIBLE_CHARS);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-selected or short filenames
|
||||
if (strlen(full_file_name) > FILE_NAME_VISIBLE_CHARS)
|
||||
{
|
||||
// Truncate with ellipsis
|
||||
strncpy(display_buffer, full_file_name, FILE_NAME_VISIBLE_CHARS - 3);
|
||||
display_buffer[FILE_NAME_VISIBLE_CHARS - 3] = '\0';
|
||||
strcat(display_buffer, "...");
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(display_buffer, full_file_name, sizeof(display_buffer) - 1);
|
||||
display_buffer[sizeof(display_buffer) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// Format and display file size
|
||||
char size_buffer[20];
|
||||
format_file_size(entries[entry_idx].file_size, entries[entry_idx].is_dir,
|
||||
size_buffer, sizeof(size_buffer));
|
||||
|
||||
// Draw filename and file size
|
||||
draw_text(FILE_NAME_X, posY, display_buffer, is_selected?COLOR_BG:COLOR_FG , is_selected ? COLOR_HIGHLIGHT : COLOR_BG);
|
||||
draw_text(FILE_SIZE_X, posY, size_buffer, is_selected?COLOR_BG:COLOR_FG, is_selected ? COLOR_HIGHLIGHT : COLOR_BG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update only the selected entry row
|
||||
* This is an optimization to avoid redrawing the entire directory list
|
||||
* when only the selected entry needs to be updated (e.g., for scrolling text)
|
||||
*/
|
||||
static void ui_update_selected_entry(void)
|
||||
{
|
||||
const int font_height = 12;
|
||||
const int entry_padding = 2;
|
||||
int y_start = UI_Y + HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT;
|
||||
int available_height = UI_HEIGHT - (HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT + STATUS_BAR_HEIGHT);
|
||||
int max_visible = available_height / (font_height + entry_padding);
|
||||
int start_index = (selected_index >= max_visible) ? selected_index - max_visible + 1 : 0;
|
||||
|
||||
// Calculate the position of the selected entry
|
||||
int visible_index = selected_index - start_index;
|
||||
if (visible_index >= 0 && visible_index < max_visible) {
|
||||
int posY = y_start + visible_index * (font_height + entry_padding);
|
||||
|
||||
// Clear just the selected row
|
||||
//draw_rect_spi(UI_X, posY - 1, UI_X + UI_WIDTH - 1, posY + font_height, COLOR_BG);
|
||||
|
||||
// Redraw just the selected entry
|
||||
ui_draw_directory_entry(selected_index, posY, font_height, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the directory list
|
||||
static void ui_draw_directory_list(void)
|
||||
{
|
||||
const int font_height = 12;
|
||||
const int entry_padding = 2;
|
||||
int y_start = UI_Y + HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT;
|
||||
int available_height = UI_HEIGHT - (HEADER_TITLE_HEIGHT + PATH_HEADER_HEIGHT + STATUS_BAR_HEIGHT);
|
||||
int max_visible = available_height / (font_height + entry_padding);
|
||||
int start_index = (selected_index >= max_visible) ? selected_index - max_visible + 1 : 0;
|
||||
|
||||
draw_rect_spi(UI_X, y_start, UI_X + UI_WIDTH - 1, UI_Y + UI_HEIGHT - STATUS_BAR_HEIGHT - 1, COLOR_BG);
|
||||
last_scrolling = time_us_64() /1000;
|
||||
|
||||
for (int i = 0; i < max_visible && (i + start_index) < entry_count; i++)
|
||||
{
|
||||
int posY = y_start + i * (font_height + entry_padding);
|
||||
int entry_idx = i + start_index;
|
||||
int is_selected = (entry_idx == selected_index);
|
||||
|
||||
// Draw the entry using the helper function
|
||||
ui_draw_directory_entry(entry_idx, posY, font_height, is_selected);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the status bar
|
||||
static void ui_draw_status_bar(void)
|
||||
{
|
||||
int y = UI_Y + UI_HEIGHT - STATUS_BAR_HEIGHT;
|
||||
draw_rect_spi(UI_X, y, UI_X + UI_WIDTH - 1, UI_Y + UI_HEIGHT - 1, COLOR_BG);
|
||||
draw_line_spi(UI_X, y, UI_X + UI_WIDTH - 1, y, COLOR_FG);
|
||||
char truncated_message[UI_WIDTH / 8];
|
||||
strncpy(truncated_message, status_message, sizeof(truncated_message) - 1);
|
||||
truncated_message[sizeof(truncated_message) - 1] = '\0';
|
||||
draw_text(UI_X + 2, y + 2, truncated_message, COLOR_FG, COLOR_BG);
|
||||
}
|
||||
|
||||
// Refresh the entire UI
|
||||
static void ui_refresh(void)
|
||||
{
|
||||
ui_draw_title();
|
||||
ui_draw_path_header();
|
||||
ui_draw_directory_list();
|
||||
ui_draw_status_bar();
|
||||
|
||||
if (status_message[0] != '\0' && ((time_us_64() / 1000) - status_timestamp) > 3000)
|
||||
{
|
||||
status_message[0] = '\0';
|
||||
ui_draw_status_bar();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle key events for navigation and selection
|
||||
static void process_key_event(int key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case KEY_ARROW_UP:
|
||||
if (selected_index > 0)
|
||||
selected_index--;
|
||||
ui_draw_directory_list();
|
||||
break;
|
||||
case KEY_ARROW_DOWN:
|
||||
if (selected_index < entry_count - 1)
|
||||
selected_index++;
|
||||
ui_draw_directory_list();
|
||||
break;
|
||||
case KEY_ENTER:
|
||||
if (entry_count > 0)
|
||||
{
|
||||
char new_path[512];
|
||||
if (entries[selected_index].is_dir)
|
||||
{
|
||||
snprintf(new_path, sizeof(new_path), "%s/%s", current_path, entries[selected_index].name);
|
||||
strncpy(current_path, new_path, sizeof(current_path) - 1);
|
||||
load_directory(current_path);
|
||||
ui_draw_path_header();
|
||||
ui_draw_directory_list();
|
||||
}
|
||||
else if (final_callback)
|
||||
{
|
||||
char final_selected[512];
|
||||
snprintf(final_selected, sizeof(final_selected), "%s/%s", current_path, entries[selected_index].name);
|
||||
final_callback(final_selected);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KEY_BACKSPACE:
|
||||
if (strcmp(current_path, "/sd") != 0)
|
||||
{
|
||||
char *last_slash = strrchr(current_path, '/');
|
||||
if (last_slash)
|
||||
*last_slash = '\0';
|
||||
if (current_path[0] == '\0')
|
||||
strncpy(current_path, "/sd", sizeof(current_path) - 1);
|
||||
load_directory(current_path);
|
||||
ui_draw_path_header();
|
||||
ui_draw_directory_list();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ui_draw_status_bar();
|
||||
}
|
||||
|
||||
// Public API: Set the final selection callback
|
||||
void text_directory_ui_set_final_callback(final_selection_callback_t callback)
|
||||
{
|
||||
final_callback = callback;
|
||||
}
|
||||
|
||||
// Public API: Initialize the UI
|
||||
bool text_directory_ui_init(void)
|
||||
{
|
||||
draw_filled_rect(UI_X, UI_Y, UI_WIDTH, UI_HEIGHT, COLOR_BG);
|
||||
strncpy(current_path, "/sd", sizeof(current_path));
|
||||
load_directory(current_path);
|
||||
ui_refresh();
|
||||
last_scrolling = time_us_64()/1000;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Public API: Set a status message
|
||||
void text_directory_ui_set_status(const char *msg)
|
||||
{
|
||||
strncpy(status_message, msg, sizeof(status_message) - 1);
|
||||
status_message[sizeof(status_message) - 1] = '\0';
|
||||
status_timestamp = (time_us_64() / 1000);
|
||||
ui_draw_status_bar();
|
||||
}
|
||||
|
||||
// Public API: Main event loop for the UI
|
||||
void text_directory_ui_run(void)
|
||||
{
|
||||
uint32_t last_scroll_update = 0;
|
||||
const uint32_t SCROLL_UPDATE_MS = 500; // Update scrolling text every 100ms
|
||||
|
||||
while (true)
|
||||
{
|
||||
int key = keypad_get_key();
|
||||
if (key != 0)
|
||||
process_key_event(key);
|
||||
|
||||
uint32_t current_time = time_us_64() / 1000;
|
||||
|
||||
// Update scrolling text periodically
|
||||
if (current_time - last_scroll_update > SCROLL_UPDATE_MS)
|
||||
{
|
||||
// Only update the selected entry row if there are entries and a selected item might need scrolling
|
||||
if (entry_count > 0 && selected_index >= 0 &&
|
||||
strlen(entries[selected_index].name) + (entries[selected_index].is_dir ? 1 : 0) > FILE_NAME_VISIBLE_CHARS)
|
||||
{
|
||||
ui_update_selected_entry();
|
||||
}
|
||||
last_scroll_update = current_time;
|
||||
}
|
||||
|
||||
// Clear status message after timeout
|
||||
if (status_message[0] != '\0' && (current_time - status_timestamp) > 3000)
|
||||
{
|
||||
status_message[0] = '\0';
|
||||
ui_draw_status_bar();
|
||||
}
|
||||
|
||||
// Check for SD card removal during runtime
|
||||
if (!sd_card_inserted()) {
|
||||
text_directory_ui_set_status("SD card removed. Please reinsert card.");
|
||||
|
||||
// Wait until the SD card is reinserted
|
||||
while (!sd_card_inserted()) {
|
||||
sleep_ms(100);
|
||||
}
|
||||
|
||||
// Once reinserted, update the UI and reinitialize filesystem
|
||||
text_directory_ui_set_status("SD card detected. Remounting...");
|
||||
if (!fs_init()) {
|
||||
text_directory_ui_set_status("Failed to remount SD card!");
|
||||
sleep_ms(2000);
|
||||
watchdog_reboot(0, 0, 0);
|
||||
}
|
||||
|
||||
// Refresh the directory listing
|
||||
load_directory(current_path);
|
||||
ui_draw_path_header();
|
||||
ui_draw_directory_list();
|
||||
text_directory_ui_set_status("SD card remounted successfully.");
|
||||
}
|
||||
|
||||
sleep_ms(20); // Shorter sleep to make scrolling smoother
|
||||
}
|
||||
}
|
||||
28
Code/pico_multi_booter/sd_boot/text_directory_ui.h
Normal file
28
Code/pico_multi_booter/sd_boot/text_directory_ui.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* text_directory_ui.h
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TEXT_DIRECTORY_UI_H
|
||||
#define TEXT_DIRECTORY_UI_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Callback type: invoked when the user makes a final selection. The selected path is passed as an argument.
|
||||
typedef void (*final_selection_callback_t)(const char *selected_path);
|
||||
|
||||
// Initialize the text directory UI. This sets up the SD card filesystem and the display UI.
|
||||
// Returns true if initialization succeeded, false otherwise.
|
||||
bool text_directory_ui_init(void);
|
||||
|
||||
// Run the main event loop for the directory navigation UI. This function polls for key events,
|
||||
// updates the selection cursor, and processes directory changes.
|
||||
void text_directory_ui_run(void);
|
||||
|
||||
// Register a callback that will be called when the final selection is made.
|
||||
void text_directory_ui_set_final_callback(final_selection_callback_t callback);
|
||||
|
||||
// Public API: Set a status or error message to be displayed in the status bar (auto-clears after 3 seconds)
|
||||
void text_directory_ui_set_status(const char *msg);
|
||||
|
||||
#endif // TEXT_DIRECTORY_UI_H
|
||||
Reference in New Issue
Block a user