diff --git a/.gitignore b/.gitignore index 46f42f8..18a21b2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ install_manifest.txt compile_commands.json CTestTestfile.cmake _deps +[Bb]uild/ +.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ebf7859 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +set(PICO_SDK_PATH "../../pico-sdk") + +include(pico_sdk_import.cmake) + +project(RGB.NET C CXX ASM) + +pico_sdk_init() + +add_executable(RGB.NET) + +pico_generate_pio_header(RGB.NET ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio) + +target_include_directories(RGB.NET PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_sources(RGB.NET PRIVATE RGB.NET.c usb_descriptors.c) + +pico_set_program_name(RGB.NET "RGB.NET") +pico_set_program_version(RGB.NET "1.0") + +pico_enable_stdio_uart(RGB.NET 0) +pico_enable_stdio_usb(RGB.NET 0) + +target_link_libraries(RGB.NET PRIVATE pico_stdlib hardware_pio pico_multicore tinyusb_device tinyusb_board hardware_flash) + +pico_add_extra_outputs(RGB.NET) diff --git a/RGB.NET.c b/RGB.NET.c new file mode 100644 index 0000000..6c3bd11 --- /dev/null +++ b/RGB.NET.c @@ -0,0 +1,459 @@ +#include +#include +#include +#include "pico/stdlib.h" +#include "pico/multicore.h" +#include "hardware/flash.h" +#include "hardware/pio.h" +#include "hardware/clocks.h" +#include "hardware/watchdog.h" +#include "ws2812.pio.h" +#include "bsp/board.h" +#include "tusb.h" + +// This code allows to use the Rasperry Pi PICO as PicoPiDevice. + +#define VERSION 1 + +//#### CONFIGURATION #### + +// The default amount of LED of each channel. This can be configured afterwards through USB. +// Setting a channel to 0 LEDs will disable it. +#define DEFAULT_LED_COUNT_CHANNEL_1 255 +#define DEFAULT_LED_COUNT_CHANNEL_2 255 +#define DEFAULT_LED_COUNT_CHANNEL_3 255 +#define DEFAULT_LED_COUNT_CHANNEL_4 255 +#define DEFAULT_LED_COUNT_CHANNEL_5 255 +#define DEFAULT_LED_COUNT_CHANNEL_6 255 +#define DEFAULT_LED_COUNT_CHANNEL_7 255 +#define DEFAULT_LED_COUNT_CHANNEL_8 255 + +#define PIN_CHANNEL_1 8 +#define PIN_CHANNEL_2 9 +#define PIN_CHANNEL_3 10 +#define PIN_CHANNEL_4 11 +#define PIN_CHANNEL_5 12 +#define PIN_CHANNEL_6 13 +#define PIN_CHANNEL_7 14 +#define PIN_CHANNEL_8 15 + +//####################### + +#define CHANNELS 8 // Change this only if you add or remove channels in the implementation-part. To disable channels set them to 0 LED. +#define MAX_CHANNEL_SIZE 255 + +#define BUFFER_SIZE (MAX_CHANNEL_SIZE * 3) +#define TRANSFER_BUFFER_SIZE ((BUFFER_SIZE + 3) * CHANNELS) + +#define OFFSET_MULTIPLIER 60 + +#define FLASH_CONFIG_OFFSET (256 * 1024) +#define CONFIG_MAGIC_NUMBER_LENGTH 8 + +const PIO CHANNEL_PIO[CHANNELS] = {pio0, pio0, pio0, pio0, pio1, pio1, pio1, pio1}; +const uint CHANNEL_SM[CHANNELS] = {0, 1, 2, 3, 0, 1, 2, 3}; + +uint8_t pins[CHANNELS] = {PIN_CHANNEL_1, PIN_CHANNEL_2, PIN_CHANNEL_3, PIN_CHANNEL_4, PIN_CHANNEL_5, PIN_CHANNEL_6, PIN_CHANNEL_7, PIN_CHANNEL_8}; +uint8_t led_counts[CHANNELS] = {DEFAULT_LED_COUNT_CHANNEL_1, DEFAULT_LED_COUNT_CHANNEL_2, DEFAULT_LED_COUNT_CHANNEL_3, DEFAULT_LED_COUNT_CHANNEL_4, DEFAULT_LED_COUNT_CHANNEL_5, DEFAULT_LED_COUNT_CHANNEL_6, DEFAULT_LED_COUNT_CHANNEL_7, DEFAULT_LED_COUNT_CHANNEL_8}; + +uint8_t buffer_channel_1[BUFFER_SIZE]; +uint8_t buffer_channel_2[BUFFER_SIZE]; +uint8_t buffer_channel_3[BUFFER_SIZE]; +uint8_t buffer_channel_4[BUFFER_SIZE]; +uint8_t buffer_channel_5[BUFFER_SIZE]; +uint8_t buffer_channel_6[BUFFER_SIZE]; +uint8_t buffer_channel_7[BUFFER_SIZE]; +uint8_t buffer_channel_8[BUFFER_SIZE]; + +uint8_t *buffers[] = {(uint8_t *)buffer_channel_1, (uint8_t *)buffer_channel_2, (uint8_t *)buffer_channel_3, (uint8_t *)buffer_channel_4, (uint8_t *)buffer_channel_5, (uint8_t *)buffer_channel_6, (uint8_t *)buffer_channel_7, (uint8_t *)buffer_channel_8}; +uint8_t send_buffer[CFG_TUD_ENDPOINT0_SIZE]; +uint8_t read_buffer[CFG_TUD_ENDPOINT0_SIZE]; +uint8_t transfer_buffer[TRANSFER_BUFFER_SIZE]; +uint32_t transfer_buffer_count = 0; +uint32_t transfer_length = 0; + +const uint8_t config_magic_number[CONFIG_MAGIC_NUMBER_LENGTH] = {0x52, 0x47, 0x42, 0x2E, 0x4E, 0x45, 0x54, VERSION}; +const uint8_t *config = (const uint8_t *)(XIP_BASE + FLASH_CONFIG_OFFSET); + +static inline void put_pixel(PIO pio, int sm, uint32_t pixel_grb) +{ + pio_sm_put_blocking(pio, sm, pixel_grb << 8u); +} + +static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) +{ + return ((uint32_t)(r) << 8) | ((uint32_t)(g) << 16) | ((uint32_t)(b)); +} + +void init_channel(int channel) +{ + PIO pio = CHANNEL_PIO[channel]; + uint sm = CHANNEL_SM[channel]; + uint8_t pin = pins[channel]; + + uint offset = pio_add_program(pio, &ws2812_program); + ws2812_program_init(pio, sm, offset, pin, 800000, false); +} + +void update_channel(int channel) +{ + PIO pio = CHANNEL_PIO[channel]; + uint sm = CHANNEL_SM[channel]; + uint8_t *data = buffers[channel]; + int count = led_counts[channel]; + + for (int i = 0; i < count; i++) + { + int offset = i * 3; + uint8_t r = data[offset]; + uint8_t g = data[offset + 1]; + uint8_t b = data[offset + 2]; + uint32_t pixel = urgb_u32(r, g, b); + put_pixel(pio, sm, pixel); + } +} + +void stage_channel_update(uint8_t channel, uint8_t const *buffer, uint16_t length) +{ + uint8_t *data = buffers[channel]; + bool update = buffer[0] > 0; + int offset = buffer[1] * OFFSET_MULTIPLIER; + int dataLength = length - 2; + int bufferSize = led_counts[channel] * 3; + if ((offset + dataLength) > bufferSize) + { + dataLength = bufferSize - offset; + if (dataLength <= 0) + { + return; + } + } + multicore_fifo_pop_blocking(); + __builtin_memcpy(data + offset, buffer + 2, dataLength); + if (update) + { + multicore_fifo_push_blocking(channel); + } + else + { + multicore_fifo_push_blocking(0xFF); + } +} + +void stage_channel_update_bulk(uint8_t channel, uint8_t const *buffer, uint16_t length) +{ + uint8_t *data = buffers[channel]; + + multicore_fifo_pop_blocking(); + __builtin_memcpy(data, buffer, length); + multicore_fifo_push_blocking(channel); +} + +void send_info(uint8_t info) +{ + __builtin_memset(send_buffer, 0, sizeof(send_buffer)); + send_buffer[0] = info; + tud_hid_report(0, send_buffer, 64); +} + +void send_info_n(uint8_t const *data, uint16_t length) +{ + __builtin_memset(send_buffer, 0, sizeof(send_buffer)); + for (uint16_t i = 0; i < length; i++) + { + send_buffer[i] = data[i]; + } + tud_hid_report(0, send_buffer, 64); +} + +void write_configuration(uint8_t const *ledCounts, uint8_t const *pins, uint16_t length) +{ + if (length < CHANNELS) + { + return; + } + + uint8_t config_buffer[FLASH_PAGE_SIZE]; + __builtin_memset(config_buffer, 0, FLASH_PAGE_SIZE); + __builtin_memcpy(config_buffer, config_magic_number, CONFIG_MAGIC_NUMBER_LENGTH); + + for (int i = 0; i < CHANNELS; i++) + { + uint8_t ledCount = ledCounts[i]; + if (ledCount > MAX_CHANNEL_SIZE) + { + ledCount = MAX_CHANNEL_SIZE; + } + config_buffer[CONFIG_MAGIC_NUMBER_LENGTH + i] = ledCount; + config_buffer[CONFIG_MAGIC_NUMBER_LENGTH + CHANNELS + i] = pins[i]; + } + + flash_range_erase(FLASH_CONFIG_OFFSET, FLASH_SECTOR_SIZE); + flash_range_program(FLASH_CONFIG_OFFSET, config_buffer, FLASH_PAGE_SIZE); + + watchdog_reboot(0, SRAM_END, 100); +} + +void load_configuration() +{ + for (int i = 0; i < CONFIG_MAGIC_NUMBER_LENGTH; i++) + { + if (config[i] != config_magic_number[i]) + { + write_configuration(led_counts, pins, CHANNELS); + break; + } + } + + for (int i = 0; i < CHANNELS; i++) + { + led_counts[i] = config[CONFIG_MAGIC_NUMBER_LENGTH + i]; + pins[i] = config[CONFIG_MAGIC_NUMBER_LENGTH + CHANNELS + i]; + } +} + +void process_command(uint8_t command, uint8_t const *data, uint16_t length) +{ + int request = command & 0x0F; + int channel = (command >> 4) & 0x0F; + + if (channel == 0) // Device-commands + { + if (request == 0x01) + { + send_info(CHANNELS); + } + else if (request == 0x0A) + { + write_configuration(data, pins, length); + } + else if (request == 0x0B) + { + write_configuration(led_counts, data, length); + } + else if (request == 0x0E) + { + uint8_t id[8]; + flash_get_unique_id(id); + send_info_n(id, sizeof(id)); + } + else if (request == 0x0F) + { + send_info(VERSION); + } + } + else if (channel <= CHANNELS) + { + channel--; + if (request == 0x01) + { + stage_channel_update(channel, data, length); + } + else if (request == 0x02) + { + stage_channel_update_bulk(channel, data, length); + } + else if (request == 0x0A) + { + send_info(led_counts[channel]); + } + else if (request == 0x0B) + { + send_info(pins[channel]); + } + } +} + +void process_transfer_buffer() +{ + uint32_t offset = 0; + while (offset < transfer_buffer_count) + { + uint8_t command = transfer_buffer[offset++]; + uint8_t payload_length = ((uint16_t)transfer_buffer[offset++] << 8) | transfer_buffer[offset++]; + process_command(command, transfer_buffer + offset, payload_length); + offset += payload_length; + } + + transfer_buffer_count = 0; + transfer_length = 0; +} + +void reset() +{ + for (int i = 0; i < CHANNELS; i++) + { + if (led_counts[i] > 0) + { + uint8_t *data = buffers[i]; + + multicore_fifo_pop_blocking(); + __builtin_memset(data, 0, BUFFER_SIZE); + multicore_fifo_push_blocking(i); + } + } +} + +uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) +{ + (void)report_id; + (void)report_type; + (void)buffer; + (void)reqlen; + + return 0; +} + +void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) +{ + (void)report_type; + + process_command(buffer[0], buffer + 1, bufsize - 1); +} + +void tud_umount_cb(void) +{ + reset(); +} + +void tud_vendor_task(void) +{ +#if CFG_TUD_VENDOR > 0 + if (tud_vendor_available()) + { + uint8_t *readBuffer = read_buffer; + uint32_t count = tud_vendor_read(read_buffer, sizeof(read_buffer)); + while (count > 0) + { + if (transfer_length == 0) + { + if (count < 2) + { + count = 0; + } + else + { + transfer_length = ((uint16_t)readBuffer[0] << 8) | readBuffer[1]; + readBuffer += 2; + count -= 2; + } + } + else + { + uint32_t missingData = transfer_length - transfer_buffer_count; + uint32_t copyAmount = missingData > count ? count : missingData; + + __builtin_memcpy(transfer_buffer + transfer_buffer_count, readBuffer, copyAmount); + + transfer_buffer_count += copyAmount; + missingData -= copyAmount; + + if (missingData == 0) + { + process_transfer_buffer(); + } + + readBuffer += copyAmount; + count -= copyAmount; + } + } + } +#endif +} + +void loop_core1() +{ + multicore_fifo_push_blocking(0); + while (1) + { + uint32_t channel = multicore_fifo_pop_blocking(); + if (channel < CHANNELS) + { + update_channel(channel); + } + multicore_fifo_push_blocking(channel); + } +} + +void loop() +{ + watchdog_update(); + tud_task(); + tud_vendor_task(); +} + +void setup() +{ + load_configuration(); + + watchdog_enable(500, 1); + + for (int i = 0; i < CHANNELS; i++) + { + if (led_counts[i] > 0) + { + init_channel(i); + } + } + + tusb_init(); +} + +int main(void) +{ + setup(); + + multicore_launch_core1(loop_core1); + + while (1) + { + loop(); + } + + return 0; +} + +// Configuration Validation +#if DEFAULT_LED_COUNT_CHANNEL_1 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 1 +#endif + +#if DEFAULT_LED_COUNT_CHANNEL_2 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 2 +#endif + +#if DEFAULT_LED_COUNT_CHANNEL_3 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 3 +#endif + +#if DEFAULT_LED_COUNT_CHANNEL_4 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 4 +#endif + +#if DEFAULT_LED_COUNT_CHANNEL_5 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 5 +#endif + +#if DEFAULT_LED_COUNT_CHANNEL_6 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 6 +#endif + +#if DEFAULT_LED_COUNT_CHANNEL_7 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 7 +#endif + +#if DEFAULT_LED_COUNT_CHANNEL_8 > MAX_CHANNEL_SIZE +#error There are more than MAX_CHANNEL_SIZE LEDs in channel 8 +#endif + +#if CHANNELS != 8 +#error Channel-count can not be changed without adapting the code for it! +#endif + +#if MAX_CHANNEL_SIZE != 255 +#error Max channel size can not be changed without adapting the code for it! +#endif + +#if CFG_TUD_HID < 1 +#error At least one HID-endpoint is required! +#endif diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# 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 () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + 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/tusb_config.h b/tusb_config.h new file mode 100644 index 0000000..fb7b96c --- /dev/null +++ b/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) + #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_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 1 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 1 + +#define CFG_TUD_HID_EP_BUFSIZE 64 + +#define CFG_TUD_VENDOR_RX_BUFSIZE 1024 +#define CFG_TUD_VENDOR_TX_BUFSIZE 1024 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ \ No newline at end of file diff --git a/usb_descriptors.c b/usb_descriptors.c new file mode 100644 index 0000000..50ba8ad --- /dev/null +++ b/usb_descriptors.c @@ -0,0 +1,175 @@ +/* + * 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" +#include "hardware/flash.h" + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = + { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0x1209, + .idProduct = 0x2812, + .bcdDevice = 0x0100, + + .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; +} + +//--------------------------------------------------------------------+ +// HID Report Descriptor +//--------------------------------------------------------------------+ + +uint8_t const desc_hid_report[] = {TUD_HID_REPORT_DESC_GENERIC_INOUT(CFG_TUD_HID_EP_BUFSIZE)}; + +// Invoked when received GET HID REPORT DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) +{ + (void)itf; + return desc_hid_report; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +enum +{ + ITF_NUM_HID, +#if CFG_TUD_VENDOR > 0 + ITF_NUM_DATA, +#endif + ITF_NUM_TOTAL +}; + +#if CFG_TUD_VENDOR > 0 +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN + TUD_VENDOR_DESC_LEN) +#else +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN) +#endif + +#define EPNUM_HID 0x01 +#define USBD_DATA_EP_OUT 0x02 +#define USBD_DATA_EP_IN 0x82 + +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, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 250), + TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_HID, 0, HID_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, 0x80 | EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 1), +#if CFG_TUD_VENDOR > 0 + TUD_VENDOR_DESCRIPTOR(ITF_NUM_DATA, 1, USBD_DATA_EP_OUT, USBD_DATA_EP_IN, 64), +#endif +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) +{ + (void)index; // for multiple configurations + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const *string_desc_arr[] = + { + (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) + "RGB.NET", // 1: Manufacturer + "RGB.NET WS2812B controller", // 2: Product + "0000000000000000", // 3: Serials, replaced with chip ID +}; + +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; + + char serial[16]; + uint8_t id[8]; + flash_get_unique_id(id); + sprintf(serial, "%X", id); + string_desc_arr[3] = serial; + + if (index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + } + else + { + // 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 + + 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; +} \ No newline at end of file diff --git a/ws2812.pio b/ws2812.pio new file mode 100644 index 0000000..d4b1d3b --- /dev/null +++ b/ws2812.pio @@ -0,0 +1,38 @@ +.program ws2812 +.side_set 1 + +.define public T1 2 +.define public T2 5 +.define public T3 3 + +.wrap_target +bitloop: + out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls + jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse +do_one: + jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse +do_zero: + nop side 0 [T2 - 1] ; Or drive low, for a short pulse +.wrap + +% c-sdk { +#include "hardware/clocks.h" + +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) { + + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + + pio_sm_config c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; + float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +%}