diff --git a/hw/boards/tp1/firmware/.gitignore b/hw/boards/tp1/firmware/.gitignore new file mode 100644 index 0000000..e0cc62f --- /dev/null +++ b/hw/boards/tp1/firmware/.gitignore @@ -0,0 +1,5 @@ +build/ +.vscode/ +__pycache__/ + +*.swp diff --git a/hw/boards/tp1/firmware/CMakeLists.txt b/hw/boards/tp1/firmware/CMakeLists.txt new file mode 100644 index 0000000..c3ad00a --- /dev/null +++ b/hw/boards/tp1/firmware/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.12) + +# Pull in SDK (must be before project) +#include(pico_sdk_import.cmake) +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) + + +project(pico_examples C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + +set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR}) + +# Initialize the SDK +pico_sdk_init() + +add_compile_options(-Wall + -Wno-format # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int + -Wno-unused-function # we have some for the docs that aren't called + -Wno-maybe-uninitialized + ) + +add_executable(main) + +pico_generate_pio_header(main ${CMAKE_CURRENT_LIST_DIR}/src/spi.pio) + +target_sources(main PUBLIC + src/main.c + src/usb_descriptors.c + src/pio_spi.c + ) + +# Make sure TinyUSB can find tusb_config.h +target_include_directories(main PUBLIC + src + ) + +pico_add_extra_outputs(main) + +# In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_device +# for TinyUSB device support and tinyusb_board for the additional board support library used by the example +target_link_libraries(main PUBLIC + pico_stdlib + hardware_adc + hardware_pio + tinyusb_device + tinyusb_board + ) + +# add url via pico_set_program_url +pico_set_program_name(main "ice40 flasher") +pico_set_program_url(main "https://github.com/blinkinlabs/ice40_flasher") diff --git a/hw/boards/tp1/firmware/LICENSE b/hw/boards/tp1/firmware/LICENSE new file mode 100644 index 0000000..46b33a6 --- /dev/null +++ b/hw/boards/tp1/firmware/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Tillitis AB + +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. diff --git a/hw/boards/tp1/firmware/README.md b/hw/boards/tp1/firmware/README.md new file mode 100644 index 0000000..9c8e64b --- /dev/null +++ b/hw/boards/tp1/firmware/README.md @@ -0,0 +1,190 @@ +# Ice40 programmer + +This firmware allows a Raspberry Pi Pico (or any RP2040) to work as a programmer for the lattice ice40 parts. + +It has been integrated into icestorm: +https://github.com/Blinkinlabs/icestorm/commits/interfaces + +Advantages: +* Cheap: RPi Pico boards are currently EUR4, FT232H boards are closer to EUR15 +* Available: As of summer '22, FT232H boards and chips are in short supply; Pico boards are still readily available +* Flexible: Any GPIO-capable pins on the pico can be used for programming. This allow for example multiple ice40 parts to be programmed from a single Pico. + +## Usage + +First, program your RPi Pico using the included binary 'main.u2f'. To do so, disconnect the pico from your computer, press down the bootloader button, then plug the pico back in. The computer should detect it as a memory device. Copy the main.u2f file into the root directory of this memory device. This will program the Pico. Once completed, the pico should restart and present itself as a USB HID device. + +To use it with icestorm, the 'iceprog' utility will need to be built from the above fork. Known issues: + +* This version of iceprog is hard-coded to use the pico as a programmer; it should have a command-line switch to choose the correct programmer +* SRAM programming mode (-S) is untested + +TODO: Suggested wiring diagram + +TODO: Permissions + +## Version checking + +The firmware version can be determined from the bcdDevice field in the USB device descriptor. Known versions are: + +| bcdDevice | Version | Description | +| --- | --- | --- | +| 0x0100 | 1.0 | Raw USB test version | +| 0x0200 | 2.0 | Release version | + +The command set described in this document describes the version 2.0 format. + +## Command set + +Commands are sent to the device as [control transfers](https://www-user.tu-chemnitz.de/~heha/hsn/chm/usb.chm/usb4.htm#Control). The bRequest field is used to select the commmand, and any configuration data associated with the command + +| Command | Direction | bRequest | wValue | wIndex | wLength | +| --- | --- | --- | --- | --- | --- | +| Set pin directions | OUT | 0x30 | 0 | 0 | 8 | +| Set pullups/pulldowns | OUT | 0x31 | 0 | 0 | 12 | +| Set pin values | OUT | 0x32 | 0 | 0 | 8 | +| Get pin values | IN | 0x32 | 0 | 0 | 4 | +| Configure SPI pins and clock speed | OUT | 0x40 | 0 | 0 | 5 | +| Perform a SPI transfer | OUT | 0x41 | 0 | 0 | 5+n | +| Read data from previous SPI transfer | IN | 0x41 | 0 | 0 | n | +| Send SPI clocks | OUT | 0x42 | 0 | 0 | 0 | +| Read ADC inputs | IN | 0x50 | 0 | 0 | 12 | +| Enter bootloader mode | OUT | 0xE0 | 0 | 0 | 0 | + +Additionally, the device supports an additional control transfer to support driver assignment on Windows: + +| Command | Direction | bRequest | wValue | wIndex | wLength | Description | +| --- | --- | --- |--- | --- |--- |--- | +| MS_DESCRIPTOR | IN | 0xF8 | 0 | 0 | x | Get a Microsoft OS compatible descriptor | + +### Set pin directions + +This command is used to set pin directions. The first field is a mask of pins to update, and the second is a bitmap of the resulting states. Any pin that has a bit set in the mask will be updated. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x00 | 4 | uint32: Pin mask (1=set direction) | +| 0x04 | 4 | uint32: Pin direction (1=output, 0=input) | + +### Set pin pullup/pulldown resistors + +This command is used to set pin pullup/pulldown resistors. The first field is a mask of pins to update, the second is the pull-up states, and the third is the pull-down states. Any pin that has a bit set in the mask will be updated. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x00 | 4 | uint32: Pin mask (1=set direction) | +| 0x04 | 4 | uint32: Pin pullups (1=enable, 0=disable) | +| 0x08 | 4 | uint32: Pin pulldowns (1=enable, 0=disable) | + +### Set pin values + +This command is used to set the value of output pins. The first field is a mask of pins to update, and the second is a bitmap of new output values to apply. Any pin that has a bit set in the mask will be updated. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x00 | 4 | uint32: Pin mask (1=set direction) | +| 0x04 | 4 | uint32: Pin value (1=high, 0=low) | + +### Read pin values + +This command is used to read the value of all pins. Note that pins which are confgured as outputs will report their current output setting. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x05 | 4 | uint32: Pin values (1=high, 0=low) | + +### Set SPI configuration + +This command is used to configure the SPI engine. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x00 | 1 | GPIO pin number for SCK | +| 0x01 | 1 | GPIO pin number for CS | +| 0x02 | 1 | GPIO pin number for MOSI | +| 0x03 | 1 | GPIO pin number for MISO | +| 0x04 | 1 | SPI clock frequency, in MHz | + +### Transfer SPI data + +This command is used to send data over the pins currently configured as the SPI interface. This command performs a full-duplex read/write operation, and stores the read data in a buffer. To retrieve the data read during this operation, issue a read command. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x00 | 1 | 0: don't toggle CS; any other value: toggle CS | +| 0x01 | 4 | Bytes to transfer | +| 0x05 | 1-2040 | SPI data to transfer | + +### Get data read during previous SPI transaction + +This command is used to retrieve any data transferred during the previous SPI transaction. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x00 | 1-2040 | SPI data to transfer | + +### SPI clock out + +This command is used to toggle the SPI clock pin, but doesn't transfer any data. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x00 | 4 | Number of SPI bytes to clock | + + + + +### Read ADCs xxx + +This command is used to read the analog value of analog input pins 0-2. Each value is returned as a uint32_t value representing the reading in microvolts. + +Data packet format: + +| Offset | Length | Description | +| --- | --- | --- | +| 0x01 | 4 | ADC channel 0 value, in microvolts | +| 0x05 | 4 | ADC channel 1 value, in microvolts | +| 0x09 | 4 | ADC channel 2 value, in microvolts | + +### Bootloader + +This command is used to put the device in bootloader mode. No data is sent during this + +## Building the firmware + +First, install the [Rasberry Pi Pico SDK](https://github.com/raspberrypi/pico-sdk.git): + + sudo apt install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib + cd ~ + git clone https://github.com/raspberrypi/pico-sdk.git + cd pico-sdk + git submodule update --init + +Then, clone and build this repository: + + cd ~ + git clone https://github.com/Blinkinlabs/ice40_flasher + cd ice40_flasher + export PICO_SDK_PATH=~/pico-sdk + mkdir build + cd build + cmake .. + make + +Finally, load the firmware onto the Pico using the instructions in the [usage](https://github.com/Blinkinlabs/ice40_flasher#usage) section. diff --git a/hw/boards/tp1/firmware/pico_sdk_import.cmake b/hw/boards/tp1/firmware/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/hw/boards/tp1/firmware/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# 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() + +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 () + +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") + +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 () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + 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/hw/boards/tp1/firmware/src/LICENSE.TXT b/hw/boards/tp1/firmware/src/LICENSE.TXT new file mode 100644 index 0000000..ddd4ab4 --- /dev/null +++ b/hw/boards/tp1/firmware/src/LICENSE.TXT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018, hathach (tinyusb.org) + +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. diff --git a/hw/boards/tp1/firmware/src/README.md b/hw/boards/tp1/firmware/src/README.md new file mode 100644 index 0000000..3491d44 --- /dev/null +++ b/hw/boards/tp1/firmware/src/README.md @@ -0,0 +1,2 @@ +This is a copy of the hid_composite example from TinyUSB (https://github.com/hathach/tinyusb/tree/master/examples/device/hid_composite) +showing how to build with TinyUSB when using the Raspberry Pi Pico SDK \ No newline at end of file diff --git a/hw/boards/tp1/firmware/src/main.c b/hw/boards/tp1/firmware/src/main.c new file mode 100644 index 0000000..d6c2d43 --- /dev/null +++ b/hw/boards/tp1/firmware/src/main.c @@ -0,0 +1,431 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 +#include +#include +#include +#include +#include "pico/bootrom.h" +#include "bsp/board.h" +#include "tusb.h" +#include "pio_spi.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum +{ + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + + +static bool do_reset = false; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +#define BULK_TRANSFER_MAX_SIZE 2048 // Maximum control transfer data length +#define SPI_MAX_TRANSFER_SIZE (BULK_TRANSFER_MAX_SIZE-8) + + +pio_spi_inst_t spi = { + .pio = pio0, + .sm = 0, + .cs_pin = 0 +}; + +uint pio_offset; + +void led_blinking_task(void); + +void pins_init(); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + tusb_init(); + + pio_offset = pio_add_program(spi.pio, &spi_cpha0_program); + + pins_init(); + + adc_init(); + + while (1) + { + tud_task(); // tinyusb device task + led_blinking_task(); + + if(do_reset) { + reset_usb_boot(0,0); + } + } + + return 0; +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void)remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +#define PIN_COUNT 32 // PIN_MASK is 32 bits, so that's the max number of pins available +#define PIN_MASK (0b00011100011111111111111111111111) +// #define PIN_MASK ((1< SPI_MAX_TRANSFER_SIZE) + return; + + if((buf_in == NULL) && (buf_out == NULL)) + return; + + if(buf_in != NULL) + memset(buf_in, 0, SPI_MAX_TRANSFER_SIZE); + + if(handle_cs) + gpio_put(spi.cs_pin, false); + + if(buf_in == NULL) { // Write transaction + pio_spi_write8_blocking(&spi, buf_out, byte_count); + } + else if(buf_out == NULL) { // Read transaction + pio_spi_read8_blocking(&spi, buf_in, byte_count); + } + else { // Full-duplex transaction + pio_spi_write8_read8_blocking(&spi, buf_out, buf_in, byte_count); + } + + if(handle_cs) + gpio_put(spi.cs_pin, true); +} + +uint32_t adc_sample_input(uint8_t input) +{ + if (input > 3) + { + return 0; + } + + // Configure the GPIO + adc_gpio_init(input + 26); + + adc_select_input(input); + + // 12-bit conversion, assume max value == ADC_VREF == 3.3 V + const uint32_t conversion_factor = 3300000 / (1 << 12); // microvolts per sample + uint16_t result = adc_read(); + + return result * conversion_factor; +} + +uint32_t read_uint32(uint8_t const *buffer) +{ + const uint32_t val = + (*(buffer + 0) << 24) + (*(buffer + 1) << 16) + (*(buffer + 2) << 8) + (*(buffer + 3) << 0); + return val; +} + +void write_uint32(uint32_t val, uint8_t *buffer) +{ + buffer[0] = ((val >> 24) & 0xFF); + buffer[1] = ((val >> 16) & 0xFF); + buffer[2] = ((val >> 8) & 0xFF); + buffer[3] = ((val >> 0) & 0xFF); +} + + +typedef enum +{ + COMMAND_PIN_DIRECTION = 0x30, // Configurure GPIO pin directions + COMMAND_PULLUPS = 0x31, // Configure GPIO pullups + COMMAND_PIN_VALUES = 0x32, // Set GPIO output values + COMMAND_SPI_CONFIGURE = 0x40, // Configure SPI pins + COMMAND_SPI_XFER = 0x41, // SPI transaction with CS pin + COMMAND_SPI_CLKOUT = 0x42, // Just toggle the clock + COMMAND_ADC_READ = 0x50, // Read ADC inputs + COMMAND_BOOTLOADER = 0xE0, // Jump to bootloader mode + + COMMAND_MS_OS_DESCRIPTOR = 0xF8 +} command_t; + +static const uint8_t microsoft_os_compatible_id_desc[] = { + 40, 0, 0, 0, // total length, 16 header + 24 function * 1 + 0, 1, // Version 1 + 4, 0, // Extended compatibility ID descriptor index + 1, // Number of function sections + 0, 0, 0, 0, 0, 0, 0, // Reserved + 0, // Interface number + 1, // Reserved + 'W','I','N','U','S','B',0,0, // compatibleID + 0,0,0,0,0,0,0,0, // subCompatibleID + 0,0,0,0,0,0 // Reserved +}; + +uint8_t out_buffer[BULK_TRANSFER_MAX_SIZE]; // Host->Device data +uint8_t in_buffer[BULK_TRANSFER_MAX_SIZE]; // Holds Device->Host data + +uint8_t spi_in_buffer[SPI_MAX_TRANSFER_SIZE]; // Holds read values from last SPI transfer + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +{ + // Only handle vendor requests + if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR) + return false; + + if ((stage == CONTROL_STAGE_SETUP) && (request->bmRequestType_bit.direction == 1)) + { // Device to host (IN) + memset(in_buffer, 0, sizeof(in_buffer)); + + switch (request->bRequest) + { + case COMMAND_MS_OS_DESCRIPTOR: + // TODO: Check index + memcpy(in_buffer, + microsoft_os_compatible_id_desc, + sizeof(microsoft_os_compatible_id_desc) + ); + return tud_control_xfer(rhport, + request, + (void *)(uintptr_t)in_buffer, + sizeof(microsoft_os_compatible_id_desc) + ); + break; + + case COMMAND_PIN_VALUES: + write_uint32(gpio_get_all() & PIN_MASK, &in_buffer[0]); + return tud_control_xfer(rhport, request, (void *)(uintptr_t)in_buffer, 4); + break; + + case COMMAND_ADC_READ: + write_uint32(adc_sample_input(0), &in_buffer[0]); + write_uint32(adc_sample_input(1), &in_buffer[4]); + write_uint32(adc_sample_input(2), &in_buffer[8]); + return tud_control_xfer(rhport, request, (void *)(uintptr_t)in_buffer, (3*4)); + break; + + case COMMAND_SPI_XFER: + return tud_control_xfer(rhport, request, (void *)(uintptr_t)spi_in_buffer, sizeof(spi_in_buffer)); + break; + + default: + break; + } + } + else if ((stage == CONTROL_STAGE_SETUP) && (request->bmRequestType_bit.direction == 0)) + { // Host to device (OUT): Set up request to handle DATA stage + switch (request->bRequest) + { + case COMMAND_PIN_DIRECTION: + case COMMAND_PULLUPS: + case COMMAND_PIN_VALUES: + case COMMAND_SPI_CONFIGURE: + case COMMAND_SPI_XFER: + case COMMAND_SPI_CLKOUT: + return tud_control_xfer(rhport, request, (void *)(uintptr_t)out_buffer, sizeof(out_buffer)); + break; + + case COMMAND_BOOTLOADER: + do_reset = true; + return true; + break; + + default: + break; + } + } + else if ((stage == CONTROL_STAGE_ACK) && (request->bmRequestType_bit.direction == 0)) + { // Host to device (OUT): Handle data + switch (request->bRequest) + { + case COMMAND_PIN_DIRECTION: + gpio_set_dir_masked( + read_uint32(&out_buffer[0]) & PIN_MASK, + read_uint32(&out_buffer[4]) & PIN_MASK); + return true; + break; + + case COMMAND_PULLUPS: // set pullups/pulldowns + set_pulls_masked( + read_uint32(&out_buffer[0]) & PIN_MASK, + read_uint32(&out_buffer[4]) & PIN_MASK, + read_uint32(&out_buffer[8]) & PIN_MASK); + return true; + break; + + case COMMAND_PIN_VALUES: // set pin values + gpio_put_masked( + read_uint32(&out_buffer[0]) & PIN_MASK, + read_uint32(&out_buffer[4]) & PIN_MASK); + return true; + break; + + case COMMAND_SPI_CONFIGURE: + if(out_buffer[4] == 0) + return false; + + pio_spi_init(spi.pio, spi.sm, pio_offset, + 8, // 8 bits per SPI frame + (31.25f/out_buffer[4]), // 1 MHz @ 125 clk_sys + false, // CPHA = 0 + false, // CPOL = 0 + out_buffer[0], //sck + out_buffer[2], // mosi + out_buffer[3] // miso + ); + spi.cs_pin = out_buffer[1]; // cs + + return true; + break; + + case COMMAND_SPI_XFER: + spi_xfer( + out_buffer[0], // CS + read_uint32(&out_buffer[1]), // byte length + &out_buffer[5], // data + spi_in_buffer + ); + + return true; + break; + + case COMMAND_SPI_CLKOUT: + spi_xfer( + false, + read_uint32(&out_buffer[0]), + NULL, + spi_in_buffer + ); + + return true; + break; + + + + default: + break; + } + } + else { + return true; + } + + // stall unknown request + return false; +} + +//--------------------------------------------------------------------+ +// BLINKING TASK +//--------------------------------------------------------------------+ +void led_blinking_task(void) +{ + static uint32_t start_ms = 0; + static bool led_state = false; + + // Blink every interval ms + if (board_millis() - start_ms < blink_interval_ms) + return; // not enough time + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/hw/boards/tp1/firmware/src/pio_spi.c b/hw/boards/tp1/firmware/src/pio_spi.c new file mode 100644 index 0000000..dcd47b9 --- /dev/null +++ b/hw/boards/tp1/firmware/src/pio_spi.c @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pio_spi.h" + +// Just 8 bit functions provided here. The PIO program supports any frame size +// 1...32, but the software to do the necessary FIFO shuffling is left as an +// exercise for the reader :) +// +// Likewise we only provide MSB-first here. To do LSB-first, you need to +// - Do shifts when reading from the FIFO, for general case n != 8, 16, 32 +// - Do a narrow read at a one halfword or 3 byte offset for n == 16, 8 +// in order to get the read data correctly justified. + +void __time_critical_func(pio_spi_write8_blocking)(const pio_spi_inst_t *spi, const uint8_t *src, size_t len) { + size_t tx_remain = len, rx_remain = len; + // Do 8 bit accesses on FIFO, so that write data is byte-replicated. This + // gets us the left-justification for free (for MSB-first shift-out) + io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; + io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; + while (tx_remain || rx_remain) { + if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { + *txfifo = *src++; + --tx_remain; + } + if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { + (void) *rxfifo; + --rx_remain; + } + } +} + +void __time_critical_func(pio_spi_read8_blocking)(const pio_spi_inst_t *spi, uint8_t *dst, size_t len) { + size_t tx_remain = len, rx_remain = len; + io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; + io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; + while (tx_remain || rx_remain) { + if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { + *txfifo = 0; + --tx_remain; + } + if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { + *dst++ = *rxfifo; + --rx_remain; + } + } +} + +void __time_critical_func(pio_spi_write8_read8_blocking)(const pio_spi_inst_t *spi, const uint8_t *src, uint8_t *dst, + size_t len) { + size_t tx_remain = len, rx_remain = len; + io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; + io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; + while (tx_remain || rx_remain) { + if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { + *txfifo = *src++; + --tx_remain; + } + if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { + *dst++ = *rxfifo; + --rx_remain; + } + } +} + diff --git a/hw/boards/tp1/firmware/src/pio_spi.h b/hw/boards/tp1/firmware/src/pio_spi.h new file mode 100644 index 0000000..a96903e --- /dev/null +++ b/hw/boards/tp1/firmware/src/pio_spi.h @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef _PIO_SPI_H +#define _PIO_SPI_H + +#include "hardware/pio.h" +#include "spi.pio.h" + +typedef struct pio_spi_inst { + PIO pio; + uint sm; + uint cs_pin; +} pio_spi_inst_t; + +void pio_spi_write8_blocking(const pio_spi_inst_t *spi, const uint8_t *src, size_t len); + +void pio_spi_read8_blocking(const pio_spi_inst_t *spi, uint8_t *dst, size_t len); + +void pio_spi_write8_read8_blocking(const pio_spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len); + +#endif diff --git a/hw/boards/tp1/firmware/src/spi.pio b/hw/boards/tp1/firmware/src/spi.pio new file mode 100644 index 0000000..967cb57 --- /dev/null +++ b/hw/boards/tp1/firmware/src/spi.pio @@ -0,0 +1,169 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; These programs implement full-duplex SPI, with a SCK period of 4 clock +; cycles. A different program is provided for each value of CPHA, and CPOL is +; achieved using the hardware GPIO inversion available in the IO controls. +; +; Transmit-only SPI can go twice as fast -- see the ST7789 example! + + +.program spi_cpha0 +.side_set 1 + +; Pin assignments: +; - SCK is side-set pin 0 +; - MOSI is OUT pin 0 +; - MISO is IN pin 0 +; +; Autopush and autopull must be enabled, and the serial frame size is set by +; configuring the push/pull threshold. Shift left/right is fine, but you must +; justify the data yourself. This is done most conveniently for frame sizes of +; 8 or 16 bits by using the narrow store replication and narrow load byte +; picking behaviour of RP2040's IO fabric. + +; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and +; transitions on the trailing edge, or some time before the first leading edge. + + out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if + in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low) + +.program spi_cpha1 +.side_set 1 + +; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and +; is captured on the trailing edge. + + out x, 1 side 0 ; Stall here on empty (keep SCK deasserted) + mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) + in pins, 1 side 0 ; Input data, deassert SCK + +% c-sdk { +#include "hardware/gpio.h" +static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits, + float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) { + pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) : spi_cpha0_program_get_default_config(prog_offs); + sm_config_set_out_pins(&c, pin_mosi, 1); + sm_config_set_in_pins(&c, pin_miso); + sm_config_set_sideset_pins(&c, pin_sck); + // Only support MSB-first in this example code (shift to left, auto push/pull, threshold=nbits) + sm_config_set_out_shift(&c, false, true, n_bits); + sm_config_set_in_shift(&c, false, true, n_bits); + sm_config_set_clkdiv(&c, clkdiv); + + // MOSI, SCK output are low, MISO is input + pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi)); + pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); + pio_gpio_init(pio, pin_mosi); + pio_gpio_init(pio, pin_miso); + pio_gpio_init(pio, pin_sck); + + // The pin muxes can be configured to invert the output (among other things + // and this is a cheesy way to get CPOL=1 + gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); + // SPI is synchronous, so bypass input synchroniser to reduce input delay. + hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); + + pio_sm_init(pio, sm, prog_offs, &c); + pio_sm_set_enabled(pio, sm, true); +} +%} + +; SPI with Chip Select +; ----------------------------------------------------------------------------- +; +; For your amusement, here are some SPI programs with an automatic chip select +; (asserted once data appears in TX FIFO, deasserts when FIFO bottoms out, has +; a nice front/back porch). +; +; The number of bits per FIFO entry is configured via the Y register +; and the autopush/pull threshold. From 2 to 32 bits. +; +; Pin assignments: +; - SCK is side-set bit 0 +; - CSn is side-set bit 1 +; - MOSI is OUT bit 0 (host-to-device) +; - MISO is IN bit 0 (device-to-host) +; +; This program only supports one chip select -- use GPIO if more are needed +; +; Provide a variation for each possibility of CPHA; for CPOL we can just +; invert SCK in the IO muxing controls (downstream from PIO) + + +; CPHA=0: data is captured on the leading edge of each SCK pulse (including +; the first pulse), and transitions on the trailing edge + +.program spi_cpha0_cs +.side_set 2 + +.wrap_target +bitloop: + out pins, 1 side 0x0 [1] + in pins, 1 side 0x1 + jmp x-- bitloop side 0x1 + + out pins, 1 side 0x0 + mov x, y side 0x0 ; Reload bit counter from Y + in pins, 1 side 0x1 + jmp !osre bitloop side 0x1 ; Fall-through if TXF empties + + nop side 0x0 [1] ; CSn back porch +public entry_point: ; Must set X,Y to n-2 before starting! + pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles) +.wrap ; Note ifempty to avoid time-of-check race + +; CPHA=1: data transitions on the leading edge of each SCK pulse, and is +; captured on the trailing edge + +.program spi_cpha1_cs +.side_set 2 + +.wrap_target +bitloop: + out pins, 1 side 0x1 [1] + in pins, 1 side 0x0 + jmp x-- bitloop side 0x0 + + out pins, 1 side 0x1 + mov x, y side 0x1 + in pins, 1 side 0x0 + jmp !osre bitloop side 0x0 + +public entry_point: ; Must set X,Y to n-2 before starting! + pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles) + nop side 0x0 [1]; CSn front porch +.wrap + +% c-sdk { +#include "hardware/gpio.h" +static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool cpha, bool cpol, + uint pin_sck, uint pin_mosi, uint pin_miso) { + pio_sm_config c = cpha ? spi_cpha1_cs_program_get_default_config(prog_offs) : spi_cpha0_cs_program_get_default_config(prog_offs); + sm_config_set_out_pins(&c, pin_mosi, 1); + sm_config_set_in_pins(&c, pin_miso); + sm_config_set_sideset_pins(&c, pin_sck); + sm_config_set_out_shift(&c, false, true, n_bits); + sm_config_set_in_shift(&c, false, true, n_bits); + sm_config_set_clkdiv(&c, clkdiv); + + pio_sm_set_pins_with_mask(pio, sm, (2u << pin_sck), (3u << pin_sck) | (1u << pin_mosi)); + pio_sm_set_pindirs_with_mask(pio, sm, (3u << pin_sck) | (1u << pin_mosi), (3u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); + pio_gpio_init(pio, pin_mosi); + pio_gpio_init(pio, pin_miso); + pio_gpio_init(pio, pin_sck); + pio_gpio_init(pio, pin_sck + 1); + gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); + hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); + + uint entry_point = prog_offs + (cpha ? spi_cpha1_cs_offset_entry_point : spi_cpha0_cs_offset_entry_point); + pio_sm_init(pio, sm, entry_point, &c); + pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2)); + pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2)); + pio_sm_set_enabled(pio, sm, true); +} +%} + diff --git a/hw/boards/tp1/firmware/src/tusb_config.h b/hw/boards/tp1/firmware/src/tusb_config.h new file mode 100644 index 0000000..526aa35 --- /dev/null +++ b/hw/boards/tp1/firmware/src/tusb_config.h @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 board.mk +#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 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) + #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 + +#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_HID 0 +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 1 + + + +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/hw/boards/tp1/firmware/src/usb_descriptors.c b/hw/boards/tp1/firmware/src/usb_descriptors.c new file mode 100644 index 0000000..9333962 --- /dev/null +++ b/hw/boards/tp1/firmware/src/usb_descriptors.c @@ -0,0 +1,171 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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" + +/* 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] 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) ) + +#define USB_VID 0xCafe +#define USB_BCD 0x0200 +#define PROTOCOL_VERSION 0x0200 + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, + .bDeviceClass = 0x00, // Specified in interface descriptor + .bDeviceSubClass = 0x00, // No subclass + .bDeviceProtocol = 0x00, // No protocol + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, // Max packet size for ep0 + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = PROTOCOL_VERSION, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .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_VENDOR, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_VENDOR_DESC_LEN) + +#define EPNUM_VENDOR_IN 0x01 +#define EPNUM_VENDOR_OUT 0x01 + +uint8_t const desc_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + // Interface number, string index, EP Out & IN address, EP size + TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 0, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, 64) +}; + +// 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 + + // This example use the same configuration for both high and full speed mode + 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) + "Blinkinlabs", // 1: Manufacturer + "ICE40 programmer", // 2: Product + "123456", // 3: Serials, should use chip ID +}; + +static const uint8_t microsoft_os_string_desc[18] = { + 18, // Descriptor length + 3, // Descriptor type + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, // Signature field 'MSFT100' + 0xF8, // GET_MS_DESCRIPTOR will use bRequest=0xF8 + 0 // Pad field +}; + + +static uint16_t _desc_str[32]; + +// 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) +{ + (void) langid; + + uint8_t chr_count; + + if ( index == 0xEE) { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + memcpy(_desc_str, microsoft_os_string_desc, sizeof(microsoft_os_string_desc)); + return _desc_str; + } + else if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + } + else { + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; i None: - self.transfer_list: List[Any] = [] - - # See: https://github.com/vpelletier/python-libusb1#usage - self.context = usb1.USBContext() - self.handle = self.context.openByVendorIDAndProductID( - 0xcafe, - 0x4010, - skip_on_error=True, - ) - - if self.handle is None: - # Device not present, or user is not allowed to access - # device. - raise ValueError('Device not found') - - # Check the device firmware version - bcd_device = self.handle.getDevice().getbcdDevice() - if bcd_device != 0x0200: - raise ValueError( - 'Pico firmware version out of date- please upgrade') - - self.handle.claimInterface(0) - - self.cs_pin = -1 - - def __del__(self) -> None: - self.close() - - def close(self) -> None: - """ Release the USB device handle """ - self._wait_async() - - if self.handle is not None: - self.handle.close() - self.handle = None - self.context.close() - self.context = None - - def _wait_async(self) -> None: - # Wait until all submitted transfers can be cleared - while any(transfer.isSubmitted() - for transfer in self.transfer_list): - try: - self.context.handleEvents() - except usb1.USBErrorInterrupted: - pass - - for transfer in reversed(self.transfer_list): - if transfer.getStatus() == \ - usb1.TRANSFER_COMPLETED: - self.transfer_list.remove(transfer) - else: - print( - transfer.getStatus(), - usb1.TRANSFER_COMPLETED) - - def _write( - self, - request_id: int, - data: bytes, - nonblocking: bool = False) -> None: - - if nonblocking: - transfer = self.handle.getTransfer() - transfer.setControl( - # usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | - # usb1.RECIPIENT_DEVICE, #request type - 0x40, - request_id, # request - 0, # index - 0, - data, # data - callback=None, # callback functiopn - user_data=None, # userdata - timeout=1000 - ) - transfer.submit() - self.transfer_list.append(transfer) - else: - self.handle.controlWrite( - 0x40, request_id, 0, 0, data, timeout=100) - - def _read(self, request_id: int, length: int) -> bytes: - # self._wait_async() - return self.handle.controlRead( - 0xC0, request_id, 0, 0, length, timeout=100) - - def gpio_set_direction(self, pin: int, direction: bool) -> None: - """Set the direction of a single GPIO pin - - Keyword arguments: - pin -- GPIO pin number - value -- True: Set pin as output, False: set pin as input - """ - msg = struct.pack('>II', - (1 << pin), - ((1 if direction else 0) << pin), - ) - - self._write(self.COMMAND_PIN_DIRECTION, msg) - - def gpio_set_pulls( - self, - pin: int, - pullup: bool, - pulldown: bool) -> None: - """Configure the pullup/down resistors for a single GPIO pin - - Keyword arguments: - pin -- GPIO pin number - pullup -- True: Enable pullup, False: Disable pullup - pulldown -- True: Enable pulldown, False: Disable pulldown - """ - msg = struct.pack('>III', - (1 << pin), - ((1 if pullup else 0) << pin), - ((1 if pulldown else 0) << pin), - ) - - self._write(self.COMMAND_PULLUPS, msg) - - def gpio_put(self, pin: int, val: bool) -> None: - """Set the output level of a single GPIO pin - - Keyword arguments: - pin -- GPIO pin number - val -- True: High, False: Low - """ - msg = struct.pack('>II', - 1 << pin, - (1 if val else 0) << pin, - ) - - self._write(self.COMMAND_PIN_VALUES, msg) - - def gpio_get_all(self) -> int: - """Read the input levels of all GPIO pins""" - msg_in = self._read(self.COMMAND_PIN_VALUES, 4) - [gpio_states] = struct.unpack('>I', msg_in) - - return gpio_states - - def gpio_get(self, pin: int) -> bool: - """Read the input level of a single GPIO pin - - Keyword arguments: - pin -- GPIO pin number - """ - gpio_states = self.gpio_get_all() - - return ((gpio_states >> pin) & 0x01) == 0x01 - - def spi_configure( - self, - sck_pin: int, - cs_pin: int, - mosi_pin: int, - miso_pin: int, - clock_speed: int) -> None: - """Set the pins to use for SPI transfers - - Keyword arguments: - sck_pin -- GPIO pin number to use as the SCK signal - cs_pin -- GPIO pin number to use as the CS signal - mosi_pin -- GPIO pin number to use as the MOSI signal - miso_pin -- GPIO pin number to use as the MISO signal - clock_speed -- SPI clock speed, in MHz - """ - header = struct.pack('>BBBBB', - sck_pin, - cs_pin, - mosi_pin, - miso_pin, - clock_speed) - msg = bytearray() - msg.extend(header) - - self._write(self.COMMAND_SPI_CONFIGURE, msg) - - self.cs_pin = cs_pin - - def spi_write( - self, - buf: bytes, - toggle_cs: bool = True) -> None: - """Write data to the SPI port - - Keyword arguments: - buf -- Byte buffer to send. - toggle_cs -- (Optional) If true, toggle the CS line - """ - self._spi_xfer(buf, toggle_cs, False) - - def spi_rxtx( - self, - buf: bytes, - toggle_cs: bool = True) -> bytes: - """Bitbang a SPI transfer - - Keyword arguments: - buf -- Byte buffer to send. - toggle_cs -- (Optional) If true, toggle the CS line - """ - - return self._spi_xfer(buf, toggle_cs, True) - - def _spi_xfer( - self, - buf: bytes, - toggle_cs: bool, - read_after_write: bool) -> bytes: - - ret = bytearray() - - if len(buf) <= self.SPI_MAX_TRANSFER_SIZE: - return self._spi_xfer_inner( - buf, - toggle_cs, - read_after_write) - - if toggle_cs: - self.gpio_put(self.cs_pin, False) - - for i in range(0, len(buf), self.SPI_MAX_TRANSFER_SIZE): - chunk = buf[i:i + self.SPI_MAX_TRANSFER_SIZE] - ret.extend( - self._spi_xfer_inner( - chunk, - False, - read_after_write)) - - if toggle_cs: - self.gpio_put(self.cs_pin, True) - - return bytes(ret) - - def _spi_xfer_inner( - self, - buf: bytes, - toggle_cs: bool, - read_after_write: bool) -> bytes: - """Bitbang a SPI transfer using the specificed GPIO pins - - Keyword arguments: - buf -- Byte buffer to send. - toggle_cs -- (Optional) If true, toggle the CS line - """ - - if len(buf) > self.SPI_MAX_TRANSFER_SIZE: - raise ValueError( - 'Message too large, ' - + f'size:{len(buf)} max:{self.SPI_MAX_TRANSFER_SIZE}') - - header = struct.pack('>BI', toggle_cs, len(buf)) - msg = bytearray() - msg.extend(header) - msg.extend(buf) - - self._write(self.COMMAND_SPI_XFER, msg) - - if not read_after_write: - return bytes() - - msg_in = self._read( - self.COMMAND_SPI_XFER, - len(buf)) - - return msg_in - - def spi_clk_out(self, byte_count: int) -> None: - """Run the SPI clock without transferring data - - This function is useful for SPI devices that need a clock to - advance their state machines. - - Keyword arguments: - byte_count -- Number of bytes worth of clocks to send - """ - - header = struct.pack('>I', - byte_count) - msg = bytearray() - msg.extend(header) - self._write( - self.COMMAND_SPI_CLKOUT, - msg) - - def adc_read_all(self) -> tuple[float, float, float]: - """Read the voltage values of ADC 0, 1, and 2 - - The firmware will read the values for each input multiple - times, and return averaged values for each input. - """ - msg_in = self._read(self.COMMAND_ADC_READ, 3 * 4) - [ch0, ch1, ch2] = struct.unpack('>III', msg_in) - - return ch0 / 1000000, ch1 / 1000000, ch2 / 1000000 - - def bootloader(self) -> None: - """Reset the programmer to bootloader mode - - After the device is reset, it can be programmed using - picotool, or by copying a file to the uf2 drive. - """ - try: - self._write(self.COMMAND_BOOTLOADER, bytes()) - except usb1.USBErrorIO: - pass