Pull in programmer board firmware

This commit is contained in:
Matt Mets 2023-03-08 13:46:59 +01:00 committed by Michael Cardell Widerkrantz
parent 2cd7c9f8e3
commit 9c7022edd0
No known key found for this signature in database
GPG Key ID: D3DB3DDF57E704E5
14 changed files with 1344 additions and 351 deletions

5
hw/boards/tp1/firmware/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
build/
.vscode/
__pycache__/
*.swp

View File

@ -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")

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,73 @@
# This is a copy of <PICO_SDK_PATH>/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})

View File

@ -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.

View File

@ -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

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <hardware/gpio.h>
#include <hardware/adc.h>
#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<<PIN_COUNT) - 1)
// #define PIN_MASK (0xFFFFFFFF)
void pins_init()
{
gpio_init_mask(PIN_MASK);
}
//! @brief Set the pull-up state for the gpios specified in the pinmask
void set_pulls_masked(
const uint32_t mask,
const uint32_t ups,
const uint32_t downs)
{
for (int gpio = 0; gpio < PIN_COUNT; gpio++)
{
if (!(PIN_MASK & (1 << gpio)))
continue;
if (mask & (1 << gpio))
{
gpio_set_pulls(gpio, ups & (1 << gpio), downs & (1 << gpio));
}
}
}
uint32_t get_directions_all() {
uint32_t pin_directions = 0;
for (int gpio = 0; gpio < PIN_COUNT; gpio++)
{
pin_directions |= (gpio_get_dir(gpio)) << gpio;
}
return pin_directions;
}
void spi_xfer(bool handle_cs,
const uint32_t byte_count,
uint8_t const *buf_out,
uint8_t *buf_in)
{
if(byte_count > 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
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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);
}
%}

View File

@ -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_ */

View File

@ -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<chr_count; i++)
{
_desc_str[1+i] = str[i];
}
}
// first byte is length (including header), second byte is string type
_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
return _desc_str;
}

View File

@ -1,351 +0,0 @@
#!/usr/bin/env python
"""IceFlasher, an iCE40 programming tool based on an RPi Pico"""
import struct
from typing import List, Any
import usb1 # type: ignore
# def processReceivedData(transfer):
# # print('got rx data',
# transfer.getStatus(),
# transfer.getActualLength())
#
# if transfer.getStatus() != usb1.TRANSFER_COMPLETED:
# # Transfer did not complete successfully, there is no
# # data to read. This example does not resubmit transfers
# # on errors. You may want to resubmit in some cases (timeout,
# # ...).
# return
# data = transfer.getBuffer()[:transfer.getActualLength()]
# # Process data...
# # Resubmit transfer once data is processed.
# transfer.submit()
class IceFlasher:
""" iCE40 programming tool based on an RPi Pico """
COMMAND_PIN_DIRECTION = 0x30
COMMAND_PULLUPS = 0x31
COMMAND_PIN_VALUES = 0x32
COMMAND_SPI_CONFIGURE = 0x40
COMMAND_SPI_XFER = 0x41
COMMAND_SPI_CLKOUT = 0x42
COMMAND_ADC_READ = 0x50
COMMAND_BOOTLOADER = 0xE0
SPI_MAX_TRANSFER_SIZE = 2048 - 8
def __init__(self) -> 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