diff --git a/.github/workflows/firmware.yml b/.github/workflows/firmware.yml index 19fb2af..3265161 100644 --- a/.github/workflows/firmware.yml +++ b/.github/workflows/firmware.yml @@ -13,6 +13,7 @@ jobs: - 'qtpy' - 'cynthion r0.2' - 'cynthion r0.4' + - 'raspberry_pi_pico' include: - target-board: 'cynthion r0.2' board-major: 0 diff --git a/.github/workflows/host.yml b/.github/workflows/host.yml new file mode 100644 index 0000000..61f6530 --- /dev/null +++ b/.github/workflows/host.yml @@ -0,0 +1,36 @@ +name: python + +on: + push: + pull_request: + workflow_dispatch: + + # Run automatically every monday + schedule: + - cron: 1 12 * * 1 + +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 5 + matrix: + python-version: + - '3.8' + - '3.9' + - '3.10' + - '3.11' + - '3.12' + + name: test (${{ matrix.python-version }}) + steps: + - uses: actions/checkout@v1 + - name: Set up PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pdm install -G :all + - name: Run tests + run: pdm run test diff --git a/apollo_fpga/commands/cli.py b/apollo_fpga/commands/cli.py index 36ce7a7..4acda3a 100755 --- a/apollo_fpga/commands/cli.py +++ b/apollo_fpga/commands/cli.py @@ -153,6 +153,9 @@ def erase_flash(device, args): def program_flash(device, args): + if args.fast: + return program_flash_fast(device, args) + ensure_unconfigured(device) with device.jtag as jtag: @@ -173,7 +176,7 @@ def program_flash_fast(device, args): from luna.gateware.platform import get_appropriate_platform from apollo_fpga.gateware.flash_bridge import FlashBridge, FlashBridgeConnection except ImportError: - logging.error("`flash-fast` requires the `luna` package in the Python environment.\n" + logging.error("`flash --fast` requires the `luna` package in the Python environment.\n" "Install `luna` or use `flash` instead.") sys.exit(-1) @@ -209,6 +212,11 @@ def program_flash_fast(device, args): programmer.flash(bitstream) +def program_flash_fast_deprecated(device, args): + logging.warning("[WARNING] `flash-fast` is now deprecated. Please use `flash --fast` instead.") + return program_flash_fast(device, args) + + def read_back_flash(device, args): ensure_unconfigured(device) @@ -343,10 +351,10 @@ def jtag_debug_spi_register(device, args): # Flash commands Command("flash-erase", handler=erase_flash, help="Erases the contents of the FPGA's flash memory."), - Command("flash-program", alias=["flash"], args=["file", "--offset"], handler=program_flash, - help="Programs the target bitstream onto the FPGA's configuration flash."), - Command("flash-fast", args=["file", "--offset"], handler=program_flash_fast, - help="Programs a bitstream onto the FPGA's configuration flash using a SPI bridge"), + Command("flash-program", alias=["flash"], args=["file", "--offset", (("--fast",), dict(action='store_true'))], + handler=program_flash, help="Programs the target bitstream onto the FPGA's configuration flash."), + Command("flash-fast", args=["file", "--offset"], handler=program_flash_fast_deprecated, + help="Programs a bitstream onto the FPGA's configuration flash using a SPI bridge."), Command("flash-read", args=["file", "--offset", "--length"], handler=read_back_flash, help="Reads the contents of the attached FPGA's configuration flash."), @@ -394,7 +402,7 @@ def main(): cmd_parser.add_argument(*arg[0], **arg[1]) else: cmd_parser.add_argument(arg) - + args = parser.parse_args() if not args.command: parser.print_help() diff --git a/apollo_fpga/protocol/jtag_svf.py b/apollo_fpga/protocol/jtag_svf.py index c5beab8..dd1700e 100644 --- a/apollo_fpga/protocol/jtag_svf.py +++ b/apollo_fpga/protocol/jtag_svf.py @@ -127,7 +127,7 @@ def _lex(self): else: line, column = self.line_column() print_args = (line, column, self.buffer[self.position:self.position + 16]) - raise SVFParsingError("unrecognized SVF data at line {}, column {} ({}...)".format(print_args)) + raise SVFParsingError("unrecognized SVF data at line {}, column {} ({}...)".format(*print_args)) def peek(self): """Return the next token without advancing the position.""" diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt new file mode 100644 index 0000000..c5c679c --- /dev/null +++ b/firmware/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.17) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +# set(CMAKE_C_COMPILER_WORKS 1) + +#set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +include(${CMAKE_CURRENT_SOURCE_DIR}/../lib/tinyusb/hw/bsp/family_support.cmake) + +# gets PROJECT name for the example (e.g. -) +family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR}) + +project(${PROJECT} C CXX ASM) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}) + +# Espressif has its own cmake build system +if(FAMILY STREQUAL "espressif") + return() +endif() + +add_executable(${PROJECT}) + +# Example source +target_sources(${PROJECT} PUBLIC + # ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD}/dfu.c + # ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD}/usb_descriptors.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD}/fpga.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD}/jtag.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD}/led.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD}/spi.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD}/uart.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/mcu/rp2040/dfu.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/mcu/rp2040/usb_descriptors.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/board_rev.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/button.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/console.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/debug_spi.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/fpga_adv.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/fpga.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/jtag_tap.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/jtag.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_switch.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/vendor.c + ) + +# Example include +target_include_directories(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/${BOARD} + ) + +target_compile_definitions(${PROJECT} PUBLIC +# CFG_TUSB_OS=OPT_OS_PICO + _BOARD_REVISION_MAJOR_=${BOARD_REVISION_MAJOR} + _BOARD_REVISION_MINOR_=${BOARD_REVISION_MINOR} + VERSION_STRING="${VERSION_STRING}" + ) + +# Configure compilation flags and libraries for the example... see the corresponding function +# in hw/bsp/FAMILY/family.cmake for details. +# family_configure_device_example(${PROJECT} noos) +family_configure_target(${PROJECT} noos) +target_link_libraries(${PROJECT} PUBLIC pico_stdlib tinyusb_device pico_unique_id pico_fix_rp2040_usb_device_enumeration hardware_spi) + diff --git a/firmware/src/boards/cynthion_d11/uart.c b/firmware/src/boards/cynthion_d11/uart.c index 9b1198c..7dead05 100644 --- a/firmware/src/boards/cynthion_d11/uart.c +++ b/firmware/src/boards/cynthion_d11/uart.c @@ -68,7 +68,7 @@ void uart_release_pinmux(void) * Configures the UART we'll use for our system console. * TODO: support more configuration (parity, stop, etc.) */ -void uart_init(bool configure_pinmux, unsigned long baudrate) +void uart_initialize(bool configure_pinmux, unsigned long baudrate) { // Disable the SERCOM before configuring it, to 1) ensure we're not transacting // during configuration; and 2) as many of the registers are R/O when the SERCOM is enabled. diff --git a/firmware/src/boards/cynthion_d21/uart.c b/firmware/src/boards/cynthion_d21/uart.c index 915dca2..ea4a7d7 100644 --- a/firmware/src/boards/cynthion_d21/uart.c +++ b/firmware/src/boards/cynthion_d21/uart.c @@ -68,7 +68,7 @@ void uart_release_pinmux(void) * Configures the UART we'll use for our system console. * TODO: support more configuration (parity, stop, etc.) */ -void uart_init(bool configure_pinmux, unsigned long baudrate) +void uart_initialize(bool configure_pinmux, unsigned long baudrate) { // Disable the SERCOM before configuring it, to 1) ensure we're not transacting // during configuration; and 2) as many of the registers are R/O when the SERCOM is enabled. diff --git a/firmware/src/boards/daisho/uart.c b/firmware/src/boards/daisho/uart.c index 36dbc28..1ae9b88 100644 --- a/firmware/src/boards/daisho/uart.c +++ b/firmware/src/boards/daisho/uart.c @@ -18,7 +18,7 @@ * @param configure_pinmux If true, the pinmux will be configured for UART use during init. * @param baudrate The baud rate to apply, in symbols/second. */ -void uart_init(bool configure_pinmux, unsigned long baudrate) +void uart_initialize(bool configure_pinmux, unsigned long baudrate) { } diff --git a/firmware/src/boards/qtpy/uart.c b/firmware/src/boards/qtpy/uart.c index cb2b029..00ff118 100644 --- a/firmware/src/boards/qtpy/uart.c +++ b/firmware/src/boards/qtpy/uart.c @@ -68,7 +68,7 @@ void uart_release_pinmux(void) * Configures the UART we'll use for our system console. * TODO: support more configuration (parity, stop, etc.) */ -void uart_init(bool configure_pinmux, unsigned long baudrate) +void uart_initialize(bool configure_pinmux, unsigned long baudrate) { // Disable the SERCOM before configuring it, to 1) ensure we're not transacting // during configuration; and 2) as many of the registers are R/O when the SERCOM is enabled. diff --git a/firmware/src/boards/raspberry_pi_pico/apollo_board.h b/firmware/src/boards/raspberry_pi_pico/apollo_board.h new file mode 100644 index 0000000..c4f6491 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/apollo_board.h @@ -0,0 +1,110 @@ +/** + * Apollo board definitions for Raspberry Pi Pico + * + * Copyright (c) 2020-2024 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __APOLLO_BOARD_H__ +#define __APOLLO_BOARD_H__ + +#include + +#include "boards/pico.h" +#include "bsp/rp2040/board.h" +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/sync.h" + + +#define __NOP() {asm volatile("nop");} + + +typedef unsigned int gpio_t; + + +typedef enum gpio_direction{ + GPIO_DIRECTION_IN = GPIO_IN, + GPIO_DIRECTION_OUT = GPIO_OUT, +} gpio_direction_t; + + +typedef enum gpio_pull_mode { + GPIO_PULL_OFF, + GPIO_PULL_UP, + GPIO_PULL_DOWN, +} gpio_pull_mode_t; + + +/** + * GPIO pins for each of the microcontroller LEDs. + */ +typedef enum { + LED_A = LED_PIN, // Green + + LED_COUNT = 1, +} led_t; + +/** + * GPIO pin numbers. + */ + + + +enum { + // // Each of the JTAG pins. SPI0 + TMS_GPIO = 5, + TDI_GPIO = 3, // MOSI + TDO_GPIO = 4, // MISO + TCK_GPIO = 6, // SCK + + // // Connected to orangecrab pins 0 and 1. SERCOM0 + UART_RX = UART_RX_PIN, + UART_TX = UART_TX_PIN, + + // Connected to orangecrab RSTFPGA_RESET, ecp5 PROGRAMN + PIN_PROG = 7, +}; + + +static inline void gpio_set_pin_level(const gpio_t gpio_pin, bool level) { + gpio_put(gpio_pin, level); +} + + +static inline bool gpio_get_pin_level(const gpio_t gpio_pin) { + return gpio_get(gpio_pin); +} + + +static inline void gpio_toggle_pin_level(const gpio_t gpio_pin) { + gpio_set_pin_level(gpio_pin, !gpio_get_pin_level(gpio_pin)); +} + + +static inline void gpio_set_pin_direction(const gpio_t gpio_pin, const enum gpio_direction direction) { + gpio_init(gpio_pin); + gpio_set_dir(gpio_pin, direction); +} + + +static inline void gpio_set_pin_pull_mode(const gpio_t gpio_pin, const gpio_pull_mode_t pull_mode) { + switch(pull_mode) { + case GPIO_PULL_OFF: { + gpio_disable_pulls(gpio_pin); + } break; + case GPIO_PULL_UP: { + gpio_pull_up(gpio_pin); + } break; + case GPIO_PULL_DOWN: { + gpio_pull_down(gpio_pin); + } break; + default: { + + } break; + } +} + + +#endif diff --git a/firmware/src/boards/raspberry_pi_pico/board.mk b/firmware/src/boards/raspberry_pi_pico/board.mk new file mode 100644 index 0000000..fcfb87d --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/board.mk @@ -0,0 +1,17 @@ + +# This is an external board, so its identity is determined by its revision number. +# MAJOR = external board +# MINOR = generic Apollo board +BOARD_REVISION_MAJOR := 255 +BOARD_REVISION_MINOR := 3 + +CMAKE_DEFSYM += \ + -DBOARD_REVISION_MAJOR=$(BOARD_REVISION_MAJOR) \ + -DBOARD_REVISION_MINOR=$(BOARD_REVISION_MINOR) \ + -DVERSION_STRING="$(VERSION_STRING)" + +ifeq "$(PICO_SDK_PATH)" "" +CMAKE_DEFSYM += -DPICO_SDK_FETCH_FROM_GIT=1 +else +CMAKE_DEFSYM += -DPICO_SDK_PATH=$(PICO_SDK_PATH) +endif diff --git a/firmware/src/boards/raspberry_pi_pico/fpga.c b/firmware/src/boards/raspberry_pi_pico/fpga.c new file mode 100644 index 0000000..1e362a1 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/fpga.c @@ -0,0 +1,60 @@ +/** + * Code for basic FPGA interfacing. + * + * Copyright (c) 2020-2023 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "apollo_board.h" +#include "jtag.h" +#include "fpga.h" + + +/* + * Allows or disallows the FPGA from configuring. When disallowed, + * initialization (erasing of configuration memory) takes place, but the FPGA + * does not proceed to the configuration phase. + */ +void permit_fpga_configuration(bool enable) +{ +} + +/** + * Sets up the I/O pins needed to configure the FPGA. + */ +void fpga_io_init(void) +{ +} + + +/** + * Requests that the FPGA clear its configuration and try to reconfigure. + */ +void trigger_fpga_reconfiguration(void) +{ + /* + * If the JTAG TAP was left in certain states, pulsing PROGRAMN has no + * effect, so we reset the state first. + */ + jtag_init(); + jtag_go_to_state(STATE_TEST_LOGIC_RESET); + jtag_wait_time(2); + jtag_deinit(); + /* + * Now pulse PROGRAMN to instruct the FPGA to configure itself. + */ + gpio_set_pin_direction(PIN_PROG, GPIO_DIRECTION_OUT); + gpio_set_pin_level(PIN_PROG, false); + + board_delay(1); + + gpio_set_pin_level(PIN_PROG, true); + gpio_set_pin_direction(PIN_PROG, GPIO_DIRECTION_IN); + + // Update internal state. + fpga_set_online(true); +} diff --git a/firmware/src/boards/raspberry_pi_pico/jtag.c b/firmware/src/boards/raspberry_pi_pico/jtag.c new file mode 100644 index 0000000..817a6a8 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/jtag.c @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2020-2023 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#include +#include +#include "spi.h" + +#include + +extern uint8_t jtag_in_buffer[256]; +extern uint8_t jtag_out_buffer[256]; + + +/** + * Hook that performs hardware-specific initialization. + */ +void jtag_platform_init(void) +{ + // Set up our SPI port for SPI-accelerated JTAG. + spi_initialize(SPI_FPGA_JTAG, true, false, 1, 1, 1); +} + + +/** + * Hook that performs hardware-specific deinitialization. + */ +void jtag_platform_deinit(void) +{ +} diff --git a/firmware/src/boards/raspberry_pi_pico/led.c b/firmware/src/boards/raspberry_pi_pico/led.c new file mode 100644 index 0000000..b384e02 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/led.c @@ -0,0 +1,128 @@ +/* + * LED control abstraciton code. + * + * This file is part of LUNA. + * + * Copyright (c) 2020-2024 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#include +#include +#include + +#include +#include + + +#include "led.h" + + +/** Store the current LED blink pattern. */ +static led_pattern_t led_pattern = LED_IDLE; + + +/** + * Sets the active LED pattern. + */ +void led_set_pattern(led_pattern_t pattern) +{ + led_pattern = pattern; + leds_off(); +} + + +/** + * Sets up each of the LEDs for use. + */ +void led_init(void) +{ + uint8_t pins[] = { LED_A, }; + + // Default each LED to an output and _off_. + for (unsigned i = 0; i < LED_COUNT; ++i) { + gpio_set_pin_direction(pins[i], GPIO_DIRECTION_OUT); + gpio_set_pin_level(pins[i], true); + } +} + + +/** + * Turns the provided LED on. + */ +void led_on(led_t led) +{ + gpio_set_pin_level(led, false); +} + + +/** + * Turns the provided LED off. + */ +void led_off(led_t led) +{ + gpio_set_pin_level(led, true); +} + + +/** + * Toggles the provided LED. + */ +void led_toggle(led_t led) +{ + gpio_toggle_pin_level(led); +} + + +/** + * Sets whether a given led is on. + */ +void led_set(led_t led, bool on) +{ + gpio_set_pin_level(led, !on); +} + + +/** + * Turns off all of the device's LEDs. + */ +void leds_off(void) +{ + led_t leds[] = {LED_A}; + + for (unsigned i = 0; i < LED_COUNT; ++i) { + led_off(leds[i]); + } +} + + +/** + * Turns on the given LED. + */ +static void display_led_number(uint8_t number) +{ + led_t leds[] = {LED_A}; + + if (number < LED_COUNT) { + led_on(leds[number]); + } +} + + +/** + * Task that handles LED updates. + */ +void led_task(void) +{ + static uint32_t start_ms = 0; + + // Blink every interval ms + if ( board_millis() - start_ms < led_pattern) { + return; // not enough time + } + + start_ms += led_pattern; + led_toggle(LED_A); +} diff --git a/firmware/src/boards/raspberry_pi_pico/platform_jtag.h b/firmware/src/boards/raspberry_pi_pico/platform_jtag.h new file mode 100644 index 0000000..68eaeb6 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/platform_jtag.h @@ -0,0 +1,45 @@ +/** + * Platform-specific JTAG I/O helpers. + * Using these rather than the raw GPIO functions allows read optimizations. + * + * Copyright (c) 2020 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#ifndef __PLATFORM_JTAG_H__ +#define __PLATFORM_JTAG_H__ + +#include + +static inline void jtag_set_tms(void) +{ + gpio_set_pin_level(TMS_GPIO, true); +} + + +static inline void jtag_clear_tms(void) +{ + gpio_set_pin_level(TMS_GPIO, false); +} + + +static inline void jtag_set_tdi(void) +{ + gpio_set_pin_level(TDI_GPIO, true); +} + + +static inline void jtag_clear_tdi(void) +{ + gpio_set_pin_level(TDI_GPIO, false); +} + + +static inline bool jtag_read_tdo(void) +{ + return gpio_get_pin_level(TDO_GPIO); +} + +#endif diff --git a/firmware/src/boards/raspberry_pi_pico/spi.c b/firmware/src/boards/raspberry_pi_pico/spi.c new file mode 100644 index 0000000..1d0d497 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/spi.c @@ -0,0 +1,172 @@ +/* + * SPI driver code. + * + * This file is part of LUNA. + * + * Copyright (c) 2020 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "hardware/spi.h" +#include "hardware/resets.h" +#include "hardware/clocks.h" + +#include "apollo_board.h" +#include "spi.h" +#include "led.h" + + +static const char reverse_char[0x100] = {0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF}; + +static volatile bool _lsb_first = false; + + +/** + * Returns the SPI object associated with the given target. + */ +static spi_inst_t *spi_inst_for_target(spi_target_t target) +{ + switch (target) { + case SPI_FPGA_JTAG: return spi0; + case SPI_FPGA_DEBUG: return NULL; + } + + return NULL; +} + + +/** + * Pinmux the relevent pins so the can be used for SPI. + */ +static void _spi_configure_pinmux(spi_target_t target, bool use_for_spi) +{ + switch (target) { + + // FPGA JTAG connection -- configure TDI, TCK, TDO + case SPI_FPGA_JTAG: + if (use_for_spi) { + gpio_set_function(TDI_GPIO, GPIO_FUNC_SPI); + gpio_set_function(TCK_GPIO, GPIO_FUNC_SPI); + gpio_set_function(TDO_GPIO, GPIO_FUNC_SPI); + } else { + gpio_set_function(TDI_GPIO, GPIO_FUNC_SIO); + gpio_set_function(TCK_GPIO, GPIO_FUNC_SIO); + gpio_set_function(TDO_GPIO, GPIO_FUNC_SIO); + } + break; + case SPI_FPGA_DEBUG: + // not implemented + break; + } +} + + +/** + * Configures the relevant SPI target's pins to be used for SPI. + */ +void spi_configure_pinmux(spi_target_t target) +{ + _spi_configure_pinmux(target, true); +} + + +/** + * Returns the relevant SPI target's pins to being used for GPIO. + */ +void spi_release_pinmux(spi_target_t target) +{ + _spi_configure_pinmux(target, false); +} + + +/** + * Configures the provided target to be used as an SPI port. + */ +void spi_initialize(spi_target_t target, bool lsb_first, bool configure_pinmux, uint8_t baud_divider, + uint8_t clock_polarity, uint8_t clock_phase) +{ + spi_inst_t *spi = spi_inst_for_target(target); + _lsb_first = lsb_first; + + // Disable the SPI before configuring it. + spi_deinit(spi); + + // Set up clocking for the SPI peripheral. + spi_init(spi, 8 * 1000 * 1000 / (2*(baud_divider+1))); + + // Configure the SPI for master mode. + spi_set_slave(spi, false); + + // Set SPI format + spi_set_format( spi, // SPI instance + 8, // Number of bits per transfer + clock_polarity, // Polarity (CPOL) + clock_phase, // Phase (CPHA) + SPI_MSB_FIRST); + + // Pinmux the relevant pins to be used for the SPI. + if (configure_pinmux) { + spi_configure_pinmux(target); + } +} + + +/** + * Synchronously send a single byte on the given SPI bus. + * Does not manage the SSEL line. + */ +uint8_t spi_send_byte(spi_target_t target, uint8_t data) +{ + uint8_t dst; + spi_inst_t *spi = spi_inst_for_target(target); + + if (_lsb_first) { + data = reverse_char[data]; + } + + spi_write_read_blocking(spi, &data, &dst, 1); + + if (_lsb_first) { + dst = reverse_char[dst]; + } + + return dst; +} + + +/** + * Sends a block of data over the SPI bus. + * + * @param port The port on which to perform the SPI transaction. + * @param data_to_send The data to be transferred over the SPI bus. + * @param data_received Any data received during the SPI transaction. + * @param length The total length of the data to be exchanged, in bytes. + */ +void spi_send(spi_target_t port, void *data_to_send, void *data_received, size_t length) +{ + uint8_t *to_send = data_to_send; + uint8_t *received = data_received; + + // TODO: use the FIFO to bulk send data + for (unsigned i = 0; i < length; ++i) { + received[i] = spi_send_byte(port, to_send[i]); + } +} diff --git a/firmware/src/boards/raspberry_pi_pico/spi.h b/firmware/src/boards/raspberry_pi_pico/spi.h new file mode 100644 index 0000000..5f29896 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/spi.h @@ -0,0 +1,60 @@ +/* + * SPI driver code. + * + * This file is part of LUNA. + * + * Copyright (c) 2020 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __SPI_H__ +#define __SPI_H__ + +#include +#include +#include + +typedef enum { + SPI_FPGA_JTAG, + SPI_FPGA_DEBUG + } spi_target_t; + + +/** + * Configures the relevant SPI target's pins to be used for SPI. + */ +void spi_configure_pinmux(spi_target_t target); + + +/** + * Returns the relevant SPI target's pins to being used for GPIO. + */ +void spi_release_pinmux(spi_target_t target); + + +/** + * Configures the provided target to be used as an SPI port via the SERCOM. + */ +void spi_initialize(spi_target_t target, bool lsb_first, bool configure_pinmux, uint8_t baud_divider, + uint8_t clock_polarity, uint8_t clock_phase); + + +/** + * Synchronously send a single byte on the given SPI bus. + * Does not manage the SSEL line. + */ +uint8_t spi_send_byte(spi_target_t target, uint8_t data); + + +/** + * Sends a block of data over the SPI bus. + * + * @param target The port on which to perform the SPI transaction. + * @param data_to_send The data to be transferred over the SPI bus. + * @param data_received Any data received during the SPI transaction. + * @param length The total length of the data to be exchanged, in bytes. + */ +void spi_send(spi_target_t target, void *data_to_send, void *data_received, size_t length); + +#endif diff --git a/firmware/src/boards/raspberry_pi_pico/tusb_config.h b/firmware/src/boards/raspberry_pi_pico/tusb_config.h new file mode 100644 index 0000000..e922c59 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/tusb_config.h @@ -0,0 +1,120 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2024 Markus Blechschmidt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +// This example doesn't use an RTOS +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + + +//------------- CLASS -------------// +#define CFG_TUD_CDC 1 +#define CFG_TUD_DFU_RUNTIME 1 +#define CFG_TUD_VENDOR 1 + +// VENDOR FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE 16 +#define CFG_TUD_VENDOR_TX_BUFSIZE 512 + + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + + + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/firmware/src/boards/raspberry_pi_pico/uart.c b/firmware/src/boards/raspberry_pi_pico/uart.c new file mode 100644 index 0000000..585a4e4 --- /dev/null +++ b/firmware/src/boards/raspberry_pi_pico/uart.c @@ -0,0 +1,148 @@ +/** + * UART driver code. + * + * This file is part of LUNA. + * + * Copyright (c) 2020 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#include "hardware/uart.h" +#include "hardware/irq.h" + +#include "apollo_board.h" + + +// Create a quick reference to our uart object. +static uart_inst_t *uart_inst = PICO_DEFAULT_UART; + +// Keep track of whether our UART has been configured and is active. +bool uart_active = false; + +// Declare interrupt handler +static void on_uart_rx(); + + +/** + * Pinmux the relevent pins so the can be used for SERCOM UART. + */ +static void _uart_configure_pinmux(bool use_for_uart) +{ + if (use_for_uart) { + gpio_set_function(UART_TX, GPIO_FUNC_UART); + gpio_set_function(UART_RX, GPIO_FUNC_UART); + } else { + gpio_set_function(UART_TX, GPIO_FUNC_NULL); + gpio_set_function(UART_RX, GPIO_FUNC_NULL); + } +} + + +/** + * Configures the relevant UART's target's pins to be used for UART. + */ +void uart_configure_pinmux(void) +{ + _uart_configure_pinmux(true); + uart_active = true; +} + + +/** + * Releases the relevant pins from being used for UART, returning them + * to use as GPIO. + */ +void uart_release_pinmux(void) +{ + _uart_configure_pinmux(false); + uart_active = false; +} + + +/** + * Configures the UART we'll use for our system console. + * TODO: support more configuration (parity, stop, etc.) + */ +void uart_initialize(bool configure_pinmux, unsigned long baudrate) +{ + uart_deinit(uart_inst); + + if (configure_pinmux) { + uart_configure_pinmux(); + } + + uart_init(uart_inst, baudrate); + + while(!uart_is_enabled(uart_inst)); + + // Turn off FIFO's - we want to do this character by character + uart_set_fifo_enabled(uart_inst, false); + + // Set up a RX interrupt + // We need to set up the handler first + // Select correct interrupt for the UART we are using + int UART_IRQ = (uart_inst == uart0) ? UART0_IRQ : UART1_IRQ; + + // And set up and enable the interrupt handlers + irq_set_exclusive_handler(UART_IRQ, on_uart_rx); + irq_set_enabled(UART_IRQ, true); + + // Now enable the UART to send interrupts - RX only + uart_set_irq_enables(uart_inst, true, false); +} + + +/** + * Callback issued when the UART recieves a new byte. + */ +__attribute__((weak)) void uart_byte_received_cb(uint8_t byte) { + (void) byte; // unused +} + + +/** + * UART interrupt handler. + */ +void on_uart_rx() { + while (uart_is_readable(uart_inst)) { + uint8_t ch = uart_getc(uart_inst); + uart_byte_received_cb(ch); + } +} + + +/** + * @return True iff the UART can accept data. + */ +bool uart_ready_for_write(void) +{ + return uart_is_writable(uart_inst); +} + + +/** + * Starts a write over the Apollo console UART. + + * Does not check for readiness; it is assumed the caller knows that the + * UART is avaiable (e.g. by calling uart_ready_for_write). + */ +void uart_nonblocking_write(uint8_t byte) +{ + if(uart_ready_for_write()) { + uart_putc_raw(uart_inst, byte); + } +} + + +/** + * Writes a byte over the Apollo console UART. + * + * @param byte The byte to be written. + */ +void uart_blocking_write(uint8_t byte) +{ + while(!uart_ready_for_write()) {} + uart_putc_raw(uart_inst, byte); +} diff --git a/firmware/src/boards/samd11_xplained/uart.c b/firmware/src/boards/samd11_xplained/uart.c index 35aab82..23ef1f9 100644 --- a/firmware/src/boards/samd11_xplained/uart.c +++ b/firmware/src/boards/samd11_xplained/uart.c @@ -68,7 +68,7 @@ void uart_release_pinmux(void) * Configures the UART we'll use for our system console. * TODO: support more configuration (parity, stop, etc.) */ -void uart_init(bool configure_pinmux, unsigned long baudrate) +void uart_initialize(bool configure_pinmux, unsigned long baudrate) { // Disable the SERCOM before configuring it, to 1) ensure we're not transacting // during configuration; and 2) as many of the registers are R/O when the SERCOM is enabled. diff --git a/firmware/src/button.c b/firmware/src/button.c index 9496383..444657c 100644 --- a/firmware/src/button.c +++ b/firmware/src/button.c @@ -9,7 +9,6 @@ #include "usb_switch.h" #include "fpga.h" #include "apollo_board.h" -#include static inline void delay(int cycles) { diff --git a/firmware/src/console.c b/firmware/src/console.c index 1c62faa..a9e89a8 100644 --- a/firmware/src/console.c +++ b/firmware/src/console.c @@ -61,7 +61,7 @@ void console_task(void) */ void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* coding) { - uart_init(true, coding->bit_rate); + uart_initialize(true, coding->bit_rate); } @@ -72,13 +72,13 @@ void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* coding) void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) { if (!uart_active) { - uart_init(true, 115200); + uart_initialize(true, 115200); } } void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { if (!uart_active) { - uart_init(true, 115200); + uart_initialize(true, 115200); } } diff --git a/firmware/src/mcu/rp2040/dfu.c b/firmware/src/mcu/rp2040/dfu.c new file mode 100644 index 0000000..f9ef02c --- /dev/null +++ b/firmware/src/mcu/rp2040/dfu.c @@ -0,0 +1,21 @@ +/** + * DFU Runtime Support + * + * This file provides support for automatically rebooting into the DFU bootloader. + * + * Copyright (c) 2023-2024 Great Scott Gadgets + * Copyright (c) 2024 Markus Blechschmidt + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/bootrom.h" +#include "tusb.h" + +/** + * Handler for DFU_DETACH events, which should cause us to reboot into the bootloader. + */ +void tud_dfu_runtime_reboot_to_dfu_cb(void) +{ + reset_usb_boot(0, 0); + while(1); +} diff --git a/firmware/src/mcu/rp2040/usb_descriptors.c b/firmware/src/mcu/rp2040/usb_descriptors.c new file mode 100644 index 0000000..9b66011 --- /dev/null +++ b/firmware/src/mcu/rp2040/usb_descriptors.c @@ -0,0 +1,216 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Katherine J. Temkin + * Copyright (c) 2019 Great Scott Gadgets + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2024 Markus Blechschmidt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb.h" + +#include "pico.h" +#include "pico/unique_id.h" + +#define SERIAL_NUMBER_STRING_INDEX 3 + +// /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. +// * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. +// * +// * Auto ProductID layout's Bitmap: +// * [MSB] MIDI | HID | MSC | CDC [LSB] +// */ +// #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +// #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ +// _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + // We use bDeviceClass = 0 to indicate that we're a composite device. + // Another option is to use the Interface Association Descriptor (IAD) method, + // but this requires extra descriptors. + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + // These are a unique VID/PID for development LUNA boards. + .idVendor = 0x1d50, + .idProduct = 0x615c, + .bcdDevice = (_BOARD_REVISION_MAJOR_ << 8) | _BOARD_REVISION_MINOR_, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = SERIAL_NUMBER_STRING_INDEX, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +enum +{ + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, + ITF_NUM_DFU_RT, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_DFU_RT_DESC_LEN) + + +uint8_t const desc_configuration[] = +{ + // Interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 0, 0x81, 8, 0x02, 0x83, 64), + + // Interface descriptor for the DFU runtime interface. + TUD_DFU_RT_DESCRIPTOR(ITF_NUM_DFU_RT, 0, 0x0d, 500, 4096), +}; + + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "Great Scott Gadgets", // 1: Manufacturer + "Apollo Debugger", // 2: Product + NULL, // 3: Serials, should use chip ID +}; + +static uint16_t _desc_str[34]; + + +/** + * Returns a USB string descriptor that describes this device's unique ID. + */ +static uint16_t *get_serial_number_string_descriptor(void) +{ + static pico_unique_board_id_t uID; + static bool got_unique_board_id = false; + + const unsigned serial_number_chars = PICO_UNIQUE_BOARD_ID_SIZE_BYTES*2; + + int count = 0; + + // + // Read and save the device serial number as hexadecimal. + // + + if (!got_unique_board_id) { + pico_get_unique_board_id(&uID); + got_unique_board_id = true; + } + + // Populate the length and string type, as these are the first two bytes + // of our descriptor... + _desc_str[count++] = (TUSB_DESC_STRING << 8 ) | ((serial_number_chars * 2) + 2);; + + // ... and convert our serial number into hex. + for (unsigned i = 0; i < serial_number_chars; ++i) { + uint8_t hexit = (uID.id[i / 2] >> (4 * (1 - (i % 2)))); + _desc_str[count++] = "0123456789abcdef"[hexit & 0xF]; + } + + return _desc_str; +} + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + uint8_t chr_count; + + // If we're looking for the "supported languages" descriptor, return it directly. + if (index == 0) { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + } + // If this is a request for the serial number, return the device's unique ID> + else if (index == SERIAL_NUMBER_STRING_INDEX) { + return get_serial_number_string_descriptor(); + } + + // Otherwise, take the ASCII string provided and encode it as UTF-16. + else { + + const char* str; + if (index == 0xee) { + // Microsoft OS 1.0 String Descriptor + str = "MSFT100\xee"; + } else { + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) { + return NULL; + } + + str = string_desc_arr[index]; + } + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + for(uint8_t i=0; i #include #include diff --git a/pyproject.toml b/pyproject.toml index 606a271..bf148bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,12 @@ dependencies = [ "pyxdg>=0.27", ] +[project.optional-dependencies] +py_ci = [ + "amaranth==0.4.1", + "luna-usb @ git+https://github.com/greatscottgadgets/luna@main" +] + [project.urls] repository = "https://github.com/greatscottgadgets/apollo" @@ -41,3 +47,6 @@ include = ["apollo_fpga*"] [project.scripts] apollo = "apollo_fpga.commands.cli:main" + +[tool.pdm.scripts] +test.cmd = "python -m unittest discover -p jtag_svf.py -v"