From c3166e54b2ff63dc90758c5002ca1e6d48d0e6a2 Mon Sep 17 00:00:00 2001 From: shylie Date: Sun, 4 Jan 2026 13:48:17 -0500 Subject: [PATCH] Initial commit --- .clang-format | 12 ++++ .gitignore | 2 + CMakeLists.txt | 28 ++++++++ include/display.h | 39 +++++++++++ include/pixelstream.h | 30 +++++++++ include/tusb_config.h | 23 +++++++ pico_sdk_import.cmake | 121 +++++++++++++++++++++++++++++++++ src/display.cpp | 152 ++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 60 +++++++++++++++++ src/pixelstream.cpp | 20 ++++++ src/usb_descriptors.c | 93 ++++++++++++++++++++++++++ 11 files changed, 580 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 include/display.h create mode 100644 include/pixelstream.h create mode 100644 include/tusb_config.h create mode 100644 pico_sdk_import.cmake create mode 100644 src/display.cpp create mode 100644 src/main.cpp create mode 100644 src/pixelstream.cpp 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..a4fb4fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2e58d61 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.13) + +include(pico_sdk_import.cmake) + +project(mtgcard) + +add_executable(mtgcard + src/main.cpp + src/display.cpp + src/pixelstream.cpp + + src/usb_descriptors.c +) + +pico_sdk_init() + +target_include_directories(mtgcard PUBLIC include) + +target_link_libraries(mtgcard PUBLIC + pico_stdlib + hardware_spi + hardware_dma + tinyusb_device + tinyusb_board + pico_unique_id +) + +pico_add_extra_outputs(mtgcard) diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..abecdd7 --- /dev/null +++ b/include/display.h @@ -0,0 +1,39 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include + +namespace display +{ + +class PixelStream; + +class Display +{ + friend PixelStream; + +public: + Display(uint8_t sck, uint8_t tx, uint8_t cs, uint8_t dc, int baudrate); + + void set_update_area(uint16_t left, uint16_t top, uint16_t width, + uint16_t height); + + PixelStream pixels(); + +private: + uint8_t sck, tx, cs, dc; + spi_inst_t* spi; + + void send_command(uint8_t command); + void send_command(uint8_t command, uint8_t param); + void send_command(uint8_t command, const uint8_t* params, + size_t param_count); + + void send_command_and_begin_data_stream(uint8_t command); + void send_data(uint8_t data); + void end_data_stream(); +}; + +} + +#endif // DISPLAY_H diff --git a/include/pixelstream.h b/include/pixelstream.h new file mode 100644 index 0000000..e185446 --- /dev/null +++ b/include/pixelstream.h @@ -0,0 +1,30 @@ +#ifndef PIXELSTREAM_H +#define PIXELSTREAM_H + +#include + +namespace display +{ + +class Display; + +class PixelStream +{ + friend Display; + +public: + PixelStream(const PixelStream&) = delete; + PixelStream& operator=(const PixelStream&) = delete; + ~PixelStream(); + + void write(uint8_t red, uint8_t green, uint8_t blue); + +private: + PixelStream(Display&); + + Display& display; +}; + +} + +#endif // PIXELSTREAM_H diff --git a/include/tusb_config.h b/include/tusb_config.h new file mode 100644 index 0000000..7626957 --- /dev/null +++ b/include/tusb_config.h @@ -0,0 +1,23 @@ +#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 + +#ifdef __cplusplus +} +#endif + +#endif // TUSB_CONFIG_H 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/display.cpp b/src/display.cpp new file mode 100644 index 0000000..7d00f75 --- /dev/null +++ b/src/display.cpp @@ -0,0 +1,152 @@ +#include "display.h" + +#include "pixelstream.h" + +#include +#include + +namespace +{ + +spi_inst_t* get_spi_instance(uint8_t gpio) +{ + return gpio % 16 < 8 ? spi0 : spi1; +} + +} + +using namespace display; + +Display::Display(uint8_t sck, uint8_t tx, uint8_t cs, uint8_t dc, + int baudrate) : + sck(sck), + tx(tx), + cs(cs), + dc(dc) +{ + spi_inst_t* sck_spi = get_spi_instance(sck); + spi_inst_t* tx_spi = get_spi_instance(tx); + + hard_assert(sck_spi == tx_spi, "Invalid configuration"); + spi = sck_spi; + + gpio_set_function(sck, GPIO_FUNC_SPI); + gpio_set_function(tx, GPIO_FUNC_SPI); + + // chip select and data/command controlled manually + gpio_set_function(cs, GPIO_FUNC_SIO); + gpio_set_dir(cs, GPIO_OUT); + gpio_set_function(dc, GPIO_FUNC_SIO); + gpio_set_dir(dc, GPIO_OUT); + + spi_init(spi, baudrate); + + // initialize display command sequence + // ----------------------------------- + // reset device + send_command(0x01); + sleep_ms(150); + + // take device out of sleep mode + send_command(0x11); + sleep_ms(10); + + // set display format to 18-bit color + send_command(0x3A, 0x66); + sleep_ms(10); + + set_update_area(0, 0, 240, 320); + + // set memory addressing mode + send_command(0x36, 0b11000000); + + // turn on display inversion + send_command(0x21); + + // set display to normal mode + send_command(0x13); + sleep_ms(10); + + // set display brightness + send_command(0x51, 0xFF); + + // clear display to black + { + auto p = pixels(); + for (int i = 0; i < 320 * 240; i++) + { + p.write(0, 0, 0); + } + } + + // turn on 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; + const uint16_t bottom = top + height; + { + uint8_t params[4] + = { static_cast(left >> 8), static_cast(left & 0xFF), + static_cast(right >> 8), + static_cast(right & 0xFF) }; + + send_command(0x2A, params, 4); + } + { + uint8_t params[4] + = { static_cast(top >> 8), static_cast(top & 0xFF), + static_cast(bottom >> 8), + static_cast(bottom & 0xFF) }; + + send_command(0x2B, params, 4); + } +} + +PixelStream Display::pixels() { return PixelStream(*this); } + +void Display::send_command(uint8_t command) +{ + send_command(command, nullptr, 0); +} + +void Display::send_command(uint8_t command, uint8_t param) +{ + send_command(command, ¶m, 1); +} + +void Display::send_command(uint8_t command, const uint8_t* params, + size_t param_count) +{ + gpio_put(cs, false); + + gpio_put(dc, false); + spi_write_blocking(spi, &command, 1); + + if (param_count > 0) + { + // parameters are sent via data mode + gpio_put(dc, true); + spi_write_blocking(spi, params, param_count); + } + + gpio_put(cs, true); +} + +void Display::send_command_and_begin_data_stream(uint8_t command) +{ + gpio_put(cs, false); + + gpio_put(dc, false); + spi_write_blocking(spi, &command, 1); + + gpio_put(dc, true); +} + +void Display::send_data(uint8_t data) { spi_write_blocking(spi, &data, 1); } + +void Display::end_data_stream() { gpio_put(cs, true); } diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1371728 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,60 @@ +#include "display.h" +#include "pixelstream.h" +#include "tusb.h" + +#include + +namespace +{ + +display::Display disp(2, 3, 5, 6, 5000000); +uint16_t row = 0; +uint16_t column = 0; +uint8_t row_data[240 * 3]; + +} + +int main() +{ + tusb_rhport_init_t dev_init + = { .role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_AUTO }; + tud_init(0); + + while (true) + { + tud_task(); + } +} + +void tud_vendor_rx_cb(uint8_t itf, const uint8_t* buffer, uint16_t bufsize) +{ + size_t written = 0; + + while (written < bufsize) + { + size_t copy_size = bufsize - written < (240 * 3) - column + ? bufsize - written + : (240 * 3) - column; + memcpy(row_data + column, buffer + written, copy_size); + column += copy_size; + written += copy_size; + + if (column >= 240 * 3) + { + disp.set_update_area(0, row, 240, 320); + auto pixels = disp.pixels(); + for (int i = 0; i < 240; i++) + { + pixels.write(row_data[i * 3], row_data[i * 3 + 1], + row_data[i * 3 + 2]); + } + + column -= 240 * 3; + row += 1; + if (row == 320) + { + row = 0; + } + } + } +} diff --git a/src/pixelstream.cpp b/src/pixelstream.cpp new file mode 100644 index 0000000..57a85d8 --- /dev/null +++ b/src/pixelstream.cpp @@ -0,0 +1,20 @@ +#include "pixelstream.h" + +#include "display.h" + +using namespace display; + +PixelStream::PixelStream(Display& display) : + display(display) +{ + display.send_command_and_begin_data_stream(0x2C); +} + +void PixelStream::write(uint8_t red, uint8_t green, uint8_t blue) +{ + display.send_data(red); + display.send_data(green); + display.send_data(blue); +} + +PixelStream::~PixelStream() { display.end_data_stream(); } diff --git a/src/usb_descriptors.c b/src/usb_descriptors.c new file mode 100644 index 0000000..a6da856 --- /dev/null +++ b/src/usb_descriptors.c @@ -0,0 +1,93 @@ +#include "bsp/board_api.h" +#include "tusb.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[] + = { (const char[]){ 0x09, 0x04 }, "shylie.info", "MtG Token", 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; +}