From f28aba370b3c7ca6a9cabd5536509ee12fbe748d Mon Sep 17 00:00:00 2001 From: shylie Date: Mon, 25 May 2026 16:16:54 -0400 Subject: [PATCH] Initial commit --- .clang-format | 12 ++ .gitignore | 1 + CMakeLists.txt | 25 ++++ include/card-os.h | 13 ++ include/display.h | 21 +++ include/tusb_config.h | 24 ++++ include/user/card-os.h | 14 ++ pico_sdk_import.cmake | 121 +++++++++++++++++ src/card-os-link.ld | 298 +++++++++++++++++++++++++++++++++++++++++ src/card-os-user.c | 7 + src/card-os.c | 64 +++++++++ src/display.c | 112 ++++++++++++++++ src/usb_descriptors.c | 95 +++++++++++++ 13 files changed, 807 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 include/card-os.h create mode 100644 include/display.h create mode 100644 include/tusb_config.h create mode 100644 include/user/card-os.h create mode 100644 pico_sdk_import.cmake create mode 100644 src/card-os-link.ld create mode 100644 src/card-os-user.c create mode 100644 src/card-os.c create mode 100644 src/display.c create mode 100644 src/usb_descriptors.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f1b5bbc --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +BasedOnStyle: GNU +SpaceBeforeParens: ControlStatementsExceptControlMacros +PointerAlignment: Left +UseTab: Never +IndentWidth: 2 +ContinuationIndentWidth: 2 +ConstructorInitializerIndentWidth: 2 +BreakAfterReturnType: Automatic +BreakConstructorInitializers: AfterColon +PackConstructorInitializers: Never +IncludeBlocks: Regroup +BreakBeforeBraces: Allman diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d907035 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 4.0) + +include(pico_sdk_import.cmake) + +project(card-os) + +set(PICO_USE_FASTEST_SUPPORTED_CLOCK 1) +set(PICO_FLASH_SIZE_BYTES "8 * 1024 * 1024") + +pico_sdk_init() + +add_executable(card-os + src/card-os.c + src/usb_descriptors.c + src/display.c +) +target_include_directories(card-os PRIVATE include) +target_link_libraries(card-os PRIVATE pico_stdlib tinyusb_device tinyusb_board pico_unique_id hardware_clocks hardware_spi) +pico_set_linker_script(card-os ${CMAKE_CURRENT_SOURCE_DIR}/src/card-os-link.ld) +pico_add_extra_outputs(card-os) + +add_library(card-os-user + src/card-os-user.c +) +target_include_directories(card-os-user PUBLIC include/user) diff --git a/include/card-os.h b/include/card-os.h new file mode 100644 index 0000000..5f76e8b --- /dev/null +++ b/include/card-os.h @@ -0,0 +1,13 @@ +#ifndef CARD_OS +#define CARD_OS + +#include +#include + +extern const uint8_t DISPLAY_SCK; +extern const uint8_t DISPLAY_TX; +extern const uint8_t DISPLAY_CS; +extern const uint8_t DISPLAY_DC; +extern spi_inst_t* const DISPLAY_SPI; + +#endif // CARD_OS diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..00bc243 --- /dev/null +++ b/include/display.h @@ -0,0 +1,21 @@ +#ifndef CARD_OS_DISPLAY_H +#define CARD_OS_DISPLAY_H + +#include + +void display_send_command(uint8_t command); +void display_send_command_with_parameters(uint8_t command, + const uint8_t* params, + uint8_t param_count); + +void display_send_command_and_start_data_stream(uint8_t command); +void display_send_data(const uint8_t* data, uint8_t data_count); +void display_end_data_stream(void); + +void display_init(void); +void display_set_update_area(uint16_t left, uint16_t top, uint16_t width, + uint16_t height); +void display_put_rect(uint8_t r, uint8_t g, uint8_t b, uint16_t x, uint16_t y, + uint16_t w, uint16_t h); + +#endif // CARD_OS_DISPLAY_H diff --git a/include/tusb_config.h b/include/tusb_config.h new file mode 100644 index 0000000..e3c9b36 --- /dev/null +++ b/include/tusb_config.h @@ -0,0 +1,24 @@ +#ifndef TUSB_CONFIG_H +#define TUSB_CONFIG_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define CFG_TUD_ENABLED 1 + +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HDI 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 1 + +#define CFG_TUD_VENDOR_RX_BUFSIZE 0 +#define CFG_TUD_VENDOR_TX_BUFSIZE 256 + +#ifdef __cplusplus +} +#endif + +#endif // TUSB_CONFIG_H diff --git a/include/user/card-os.h b/include/user/card-os.h new file mode 100644 index 0000000..88d187c --- /dev/null +++ b/include/user/card-os.h @@ -0,0 +1,14 @@ +#ifndef CARD_OS_USER +#define CARD_OS_USER + +#include + +typedef struct +{ + uint8_t r, g, b; + uint16_t x, y, w, h; +} card_os_rect; + +void card_os_put_rect(const card_os_rect* rect); + +#endif // CARD_OS_USER diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/src/card-os-link.ld b/src/card-os-link.ld new file mode 100644 index 0000000..322d0f4 --- /dev/null +++ b/src/card-os-link.ld @@ -0,0 +1,298 @@ +/* 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) +*/ + +FLASH_SIZE = 8m; +OSCALL_SIZE = 4k; + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = FLASH_SIZE - OSCALL_SIZE + OSCALL(rx) : ORIGIN = 0x10000000 + FLASH_SIZE - OSCALL_SIZE, LENGTH = OSCALL_SIZE + 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 (*(.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) + *(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 + + .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); + + . = ALIGN(4); + __oscall_start = .; + .oscall : + { + KEEP(*(.oscall)) + } > OSCALL + __oscall_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*) + + . = 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); + *(.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) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (NOLOAD): + { + __end__ = .; + end = __end__; + KEEP(*(.heap*)) + } > RAM + /* 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 */ + __HeapLimit = ORIGIN(RAM) + LENGTH(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 + + /* 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 <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ +} diff --git a/src/card-os-user.c b/src/card-os-user.c new file mode 100644 index 0000000..02745bd --- /dev/null +++ b/src/card-os-user.c @@ -0,0 +1,7 @@ +#include +#include + +typedef void (*os_call_fn)(int call, const void* inptr, void* outptr); + +// address guaranteed by linker script for os +extern os_call_fn __oscall_start; diff --git a/src/card-os.c b/src/card-os.c new file mode 100644 index 0000000..930040d --- /dev/null +++ b/src/card-os.c @@ -0,0 +1,64 @@ +#include "user/card-os.h" + +#include "card-os.h" +#include "display.h" + +#include +#include +#include +#include + +const uint8_t DISPLAY_SCK = 2; +const uint8_t DISPLAY_TX = 3; +const uint8_t DISPLAY_CS = 5; +const uint8_t DISPLAY_DC = 4; +spi_inst_t* const DISPLAY_SPI = spi0; + +int main(void) +{ + gpio_set_function(DISPLAY_SCK, GPIO_FUNC_SPI); + gpio_set_function(DISPLAY_TX, GPIO_FUNC_SPI); + + gpio_set_function(DISPLAY_CS, GPIO_FUNC_SIO); + gpio_set_dir(DISPLAY_CS, GPIO_OUT); + + gpio_set_function(DISPLAY_DC, GPIO_FUNC_SIO); + gpio_set_dir(DISPLAY_DC, GPIO_OUT); + + spi_init(DISPLAY_SPI, 100 * 1000 * 1000); + spi_set_format(DISPLAY_SPI, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST); + + display_init(); + display_put_rect(0xFF, 0xFF, 0xFF, 0, 0, 240, 320); + + tud_init(0); + + while (true) + { + tud_task(); + } +} + +void tud_vendor_rx_cb(uint8_t itf, const uint8_t* buffer, uint16_t bufsize) +{ + if (buffer[0] == 0x99) + { + rom_reset_usb_boot(0, 0); + } +} + +void os_put_rect(const card_os_rect* rect) +{ + display_put_rect(rect->r, rect->g, rect->b, rect->x, rect->y, rect->w, + rect->h); +} + +static __attribute__((section(".oscall"))) void +oscall(int call, const void* inptr, void* outptr) +{ + switch (call) + { + case 0: + break; + } +} diff --git a/src/display.c b/src/display.c new file mode 100644 index 0000000..592ee70 --- /dev/null +++ b/src/display.c @@ -0,0 +1,112 @@ +#include "display.h" + +#include "card-os.h" + +#include +#include +#include + +void display_send_command(uint8_t command) +{ + display_send_command_with_parameters(command, NULL, 0); +} + +void display_send_command_with_parameters(uint8_t command, + const uint8_t* params, + uint8_t param_count) +{ + gpio_put(DISPLAY_CS, false); + + gpio_put(DISPLAY_DC, false); + spi_write_blocking(DISPLAY_SPI, &command, 1); + + if (param_count > 0) + { + gpio_put(DISPLAY_DC, true); + spi_write_blocking(DISPLAY_SPI, params, param_count); + } + + gpio_put(DISPLAY_CS, true); +} + +void display_send_command_and_start_data_stream(uint8_t command) +{ + gpio_put(DISPLAY_CS, false); + + gpio_put(DISPLAY_DC, false); + spi_write_blocking(DISPLAY_SPI, &command, 1); + + gpio_put(DISPLAY_DC, true); +} + +void display_send_data(const uint8_t* data, uint8_t data_count) +{ + spi_write_blocking(DISPLAY_SPI, data, data_count); +} + +void display_end_data_stream(void) { gpio_put(DISPLAY_CS, true); } + +void display_init(void) +{ + // reset device + display_send_command(0x01); + sleep_ms(150); + + // take device out of sleep mode + display_send_command(0x11); + sleep_ms(10); + + // set display format to 18-bit color + display_send_command_with_parameters(0x3A, (uint8_t[]){ 0x66 }, 1); + sleep_ms(10); + + // set memory addressing mode + display_send_command_with_parameters(0x36, (uint8_t[]){ 0b11000000 }, 1); + + // turn on display inversion + display_send_command(0x21); + + // set display to normal mode + display_send_command(0x13); + sleep_ms(10); + + // set display brightness + display_send_command_with_parameters(0x51, (uint8_t[]){ 0x00 }, 1); + + // set framerate + display_send_command_with_parameters(0xC6, (uint8_t[]){ 0x00 }, 1); + display_send_command_with_parameters( + 0xB2, (uint8_t[]){ 0x01, 0x01, 0x01, 0x11, 0x11 }, 5); + + // clear to black + display_put_rect(0, 0, 0, 0, 0, 240, 320); + + // turn on display + display_send_command(0x29); + sleep_ms(10); +} + +void display_set_update_area(uint16_t left, uint16_t top, uint16_t width, + uint16_t height) +{ + const uint16_t right = left + width - 1; + const uint16_t bottom = top + height - 1; + + display_send_command_with_parameters( + 0x2A, (uint8_t[]){ left >> 8, left & 0xFF, right >> 8, right & 0xFF }, 4); + display_send_command_with_parameters( + 0x2B, (uint8_t[]){ top >> 8, top & 0xFF, bottom >> 8, bottom & 0xFF }, 4); +} + +void display_put_rect(uint8_t r, uint8_t g, uint8_t b, uint16_t x, uint16_t y, + uint16_t w, uint16_t h) +{ + display_set_update_area(x, y, w, h); + + display_send_command_and_start_data_stream(0x2C); + for (int i = 0; i < w * h; i++) + { + display_send_data((uint8_t[]){ r, g, b }, 3); + } + display_end_data_stream(); +} diff --git a/src/usb_descriptors.c b/src/usb_descriptors.c new file mode 100644 index 0000000..607d7ee --- /dev/null +++ b/src/usb_descriptors.c @@ -0,0 +1,95 @@ +#include "bsp/board_api.h" + +#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) +#define USB_PID (0x4000 | _PID_MAP(VENDOR, 4)) + +const tusb_desc_device_t desc_device + = { .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_VENDOR_SPECIFIC, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0xCAFE, + .idProduct = 0xCA6D, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 }; + +const uint8_t* tud_descriptor_device_cb(void) +{ + return (const uint8_t*)&desc_device; +} + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_VENDOR_DESC_LEN) + +const uint8_t desc_configuration[] + = { TUD_CONFIG_DESCRIPTOR(1, 1, 0, CONFIG_TOTAL_LEN, 0x00, 100), + TUD_VENDOR_DESCRIPTOR(1, 0, 0x01, 0x81, 64) }; + +const uint8_t* tud_descriptor_configuration_cb(uint8_t index) +{ + (void)index; + return desc_configuration; +} + +enum +{ + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL +}; + +const char* string_desc_arr[] + = { [STRID_LANGID] = (const char[]){ 0x09, 0x04 }, + [STRID_MANUFACTURER] = "shylie.info", + [STRID_PRODUCT] = "card device", + [STRID_SERIAL] = NULL }; + +const uint16_t* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + static uint16_t _desc_str[32 + 1]; + + (void)langid; + size_t chr_count; + + switch (index) + { + case STRID_LANGID: + memcpy(_desc_str + 1, string_desc_arr[0], 2); + chr_count = 1; + break; + + case STRID_SERIAL: + chr_count = board_usb_get_serial(_desc_str + 1, 32); + break; + + default: + if (!(index < sizeof(string_desc_arr) / sizeof(*string_desc_arr))) + { + return NULL; + } + const char* str = string_desc_arr[index]; + chr_count = strlen(str); + const size_t max_count = sizeof(_desc_str) / sizeof(*_desc_str) - 1; + if (chr_count > max_count) + { + chr_count = max_count; + } + + for (size_t i = 0; i < chr_count; i++) + { + _desc_str[i + 1] = str[i]; + } + + break; + } + + _desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); + + return _desc_str; +}