diff --git a/.gitmodules b/.gitmodules index 4693242c2b..540e4d29c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "ext/microchip/sam"] path = ext/microchip/sam url = https://github.com/modm-io/cmsis-header-sam.git +[submodule "ext/hathach/tinyusb"] + path = ext/hathach/tinyusb + url = https://github.com/modm-ext/tinyusb-partial.git diff --git a/examples/generic/usb/main.cpp b/examples/generic/usb/main.cpp new file mode 100644 index 0000000000..b938f8233c --- /dev/null +++ b/examples/generic/usb/main.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include + +using namespace Board; + +#if CFG_TUD_CDC +modm::IODeviceWrapper usb_io_device0; +modm::IOStream usb_stream0(usb_io_device0); +#endif + +modm::PeriodicTimer tmr{2.5s}; + +// Invoked when device is mounted +void tud_mount_cb() { tmr.restart(1s); } +// Invoked when device is unmounted +void tud_umount_cb() { tmr.restart(250ms); } +// 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) { tmr.restart(2.5s); } +// Invoked when usb bus is resumed +void tud_resume_cb() { tmr.restart(1s); } +void midi_task(); + +int main() +{ + Board::initialize(); + Board::initializeUsbFs(); + tusb_init(); + + while (true) + { + tud_task(); + midi_task(); + + if (tmr.execute()) + { + Leds::toggle(); + static uint8_t counter{0}; +#ifdef MODM_BOARD_HAS_LOGGER + MODM_LOG_INFO << "Loop counter: " << (counter++) << modm::endl; +#endif +#if CFG_TUD_CDC + usb_stream0 << "Hello World from USB: " << (counter++) << modm::endl; +#endif + } + } + + return 0; +} + + + +void midi_task() +{ +#if CFG_TUD_MIDI + static modm::PeriodicTimer tmr{286ms}; + + if (tmr.execute()) + { + // The MIT License (MIT) + // Copyright (c) 2019 Ha Thach (tinyusb.org) + + // Store example melody as an array of note values + static uint8_t note_sequence[] = + { + 74,78,81,86,90,93,98,102,57,61,66,69,73,78,81,85,88,92,97,100,97,92,88,85,81,78, + 74,69,66,62,57,62,66,69,74,78,81,86,90,93,97,102,97,93,90,85,81,78,73,68,64,61, + 56,61,64,68,74,78,81,86,90,93,98,102 + }; + // Variable that holds the current position in the sequence. + static uint32_t note_pos = 0; + + // Previous positions in the note sequence. + int previous = note_pos - 1; + + // If we currently are at position 0, set the + // previous position to the last note in the sequence. + if (previous < 0) previous = sizeof(note_sequence) - 1; + + // Send Note On for current position at full velocity (127) on channel 1. + tudi_midi_write24(0, 0x90, note_sequence[note_pos], 127); + + // Send Note Off for previous note. + tudi_midi_write24(0, 0x80, note_sequence[previous], 0); + + // Increment position + note_pos++; + + // If we are at the end of the sequence, start over. + if (note_pos >= sizeof(note_sequence)) note_pos = 0; + } +#endif +} diff --git a/examples/generic/usb/msc_disk.c b/examples/generic/usb/msc_disk.c new file mode 100644 index 0000000000..830bb6e726 --- /dev/null +++ b/examples/generic/usb/msc_disk.c @@ -0,0 +1,248 @@ +/* + * 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" + +#if CFG_TUD_MSC + +// Some MCU doesn't have enough 8KB SRAM to store the whole disk +// We will use Flash as read-only disk with board that has +// CFG_EXAMPLE_MSC_READONLY defined + +#define README_CONTENTS \ +"This is tinyusb's MassStorage Class demo.\r\n\r\n\ +If you find any bugs or get any questions, feel free to file an\r\n\ +issue at github.com/hathach/tinyusb" + +enum +{ + DISK_BLOCK_NUM = 16, // 8KB is the smallest size that windows allow to mount + DISK_BLOCK_SIZE = 512 +}; + +#ifdef CFG_EXAMPLE_MSC_READONLY +const +#endif +uint8_t msc_disk[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = +{ + //------------- Block0: Boot Sector -------------// + // byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = DISK_BLOCK_NUM; + // sector_per_cluster = 1; reserved_sectors = 1; + // fat_num = 1; fat12_root_entry_num = 16; + // sector_per_fat = 1; sector_per_track = 1; head_num = 1; hidden_sectors = 0; + // drive_number = 0x80; media_type = 0xf8; extended_boot_signature = 0x29; + // filesystem_type = "FAT12 "; volume_serial_number = 0x1234; volume_label = "TinyUSB MSC"; + // FAT magic code at offset 510-511 + { + 0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, 0x02, 0x01, 0x01, 0x00, + 0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x29, 0x34, 0x12, 0x00, 0x00, 'T' , 'i' , 'n' , 'y' , 'U' , + 'S' , 'B' , ' ' , 'M' , 'S' , 'C' , 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00, + + // Zero up to 2 last bytes of FAT magic code + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA + }, + + //------------- Block1: FAT12 Table -------------// + { + 0xF8, 0xFF, 0xFF, 0xFF, 0x0F // // first 2 entries must be F8FF, third entry is cluster end of readme file + }, + + //------------- Block2: Root Directory -------------// + { + // first entry is volume label + 'T' , 'i' , 'n' , 'y' , 'U' , 'S' , 'B' , ' ' , 'M' , 'S' , 'C' , 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, 0x65, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry is readme file + 'R' , 'E' , 'A' , 'D' , 'M' , 'E' , ' ' , ' ' , 'T' , 'X' , 'T' , 0x20, 0x00, 0xC6, 0x52, 0x6D, + 0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, 0x65, 0x43, 0x02, 0x00, + sizeof(README_CONTENTS)-1, 0x00, 0x00, 0x00 // readme's files size (4 Bytes) + }, + + //------------- Block3: Readme Content -------------// + README_CONTENTS +}; + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) +{ + (void) lun; + + const char vid[] = "TinyUSB"; + const char pid[] = "Mass Storage"; + const char rev[] = "1.0"; + + memcpy(vendor_id , vid, strlen(vid)); + memcpy(product_id , pid, strlen(pid)); + memcpy(product_rev, rev, strlen(rev)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) +{ + (void) lun; + + return true; // RAM disk is always ready +} + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) +{ + (void) lun; + + *block_count = DISK_BLOCK_NUM; + *block_size = DISK_BLOCK_SIZE; +} + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) +{ + (void) lun; + (void) power_condition; + + if ( load_eject ) + { + if (start) + { + // load disk storage + }else + { + // unload disk storage + } + } + + return true; +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) +{ + (void) lun; + + uint8_t const* addr = msc_disk[lba] + offset; + memcpy(buffer, addr, bufsize); + + return bufsize; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) +{ + (void) lun; + +#ifndef CFG_EXAMPLE_MSC_READONLY + uint8_t* addr = msc_disk[lba] + offset; + memcpy(addr, buffer, bufsize); +#else + (void) lba; (void) offset; (void) buffer; +#endif + + return bufsize; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 has their own callbacks +int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) +{ + // read10 & write10 has their own callback and MUST not be handled here + + void const* response = NULL; + uint16_t resplen = 0; + + // most scsi handled is input + bool in_xfer = true; + + switch (scsi_cmd[0]) + { + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + // Host is about to read/write etc ... better not to disconnect disk + resplen = 0; + break; + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + + // return resplen must not larger than bufsize + if ( resplen > bufsize ) resplen = bufsize; + + if ( response && (resplen > 0) ) + { + if(in_xfer) + { + memcpy(buffer, response, resplen); + }else + { + // SCSI output + } + } + + return resplen; +} + +#endif diff --git a/examples/generic/usb/openocd.cfg b/examples/generic/usb/openocd.cfg new file mode 100644 index 0000000000..b157e7e432 --- /dev/null +++ b/examples/generic/usb/openocd.cfg @@ -0,0 +1,2 @@ +# Replace this with your custom programmer +source [find interface/stlink-v2.cfg] diff --git a/examples/generic/usb/project.xml b/examples/generic/usb/project.xml new file mode 100644 index 0000000000..e1a5c4cd22 --- /dev/null +++ b/examples/generic/usb/project.xml @@ -0,0 +1,23 @@ + + modm:blue-pill + + + + + + + + + + + + + + + + modm:build:scons + modm:tinyusb + modm:processing:timer + modm:io + + diff --git a/examples/samd/usbserial/main.cpp b/examples/samd/usbserial/main.cpp new file mode 100644 index 0000000000..f70cce4e08 --- /dev/null +++ b/examples/samd/usbserial/main.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include + +using namespace Board; + +modm::IODeviceWrapper usb_io_device; +modm::IOStream usb_stream(usb_io_device); + +// Arduino style hack that resets to DFU on disconnect at 1200bps +modm_extern_c void +tud_cdc_line_state_cb(uint8_t, bool dtr, bool) +{ + if (!dtr) // disconnected + { + cdc_line_coding_t coding; + tud_cdc_get_line_coding(&coding); + if (coding.bit_rate == 1200) + { + static volatile uint32_t *DBL_TAP_PTR = (volatile uint32_t *)(HMCRAMC0_ADDR + HMCRAMC0_SIZE - 4); + constexpr uint32_t DBL_TAP_MAGIC = 0xf01669ef; // For UF2 bootloader + // constexpr uint32_t DBL_TAP_MAGIC = 0x07738135; // For Arduino Zero bootloader + *DBL_TAP_PTR = DBL_TAP_MAGIC; + NVIC_SystemReset(); + } + } +} + +modm::PeriodicTimer tmr{2.5s}; + +// Invoked when device is mounted +void tud_mount_cb() { tmr.restart(1s); } +// Invoked when device is unmounted +void tud_umount_cb() { tmr.restart(250ms); } +// 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) { tmr.restart(2.5s); } +// Invoked when usb bus is resumed +void tud_resume_cb() { tmr.restart(1s); } + +int main() +{ + Board::initialize(); + Board::initializeUsbFs(); + + // Output 500hz PWM on D12 so we can validate the GCLK0 clock speed. + PM->APBCMASK.reg |= PM_APBCMASK_TCC0; + TCC0->PER.bit.PER = SystemClock::Frequency / 1000; + TCC0->CC[3].bit.CC = SystemClock::Frequency / 2000; + TCC0->CTRLA.bit.ENABLE = true; + D12::Wo3::connect(); + GenericClockController::connect(ClockGenerator::System); + + tusb_init(); + + while (true) + { + tud_task(); + + if (tmr.execute()) + { + Leds::toggle(); + static uint8_t counter{0}; + usb_stream << "Hello World from USB: " << (counter++) << modm::endl; + } + } + + return 0; +} diff --git a/examples/samd/usbserial/project.xml b/examples/samd/usbserial/project.xml new file mode 100644 index 0000000000..ca321b438c --- /dev/null +++ b/examples/samd/usbserial/project.xml @@ -0,0 +1,14 @@ + + modm:feather-m0 + + + + + + + modm:build:scons + modm:tinyusb + modm:processing:timer + modm:io + + diff --git a/examples/stm32f3_discovery/usb_dfu/main.cpp b/examples/stm32f3_discovery/usb_dfu/main.cpp new file mode 100644 index 0000000000..eb6a1921c0 --- /dev/null +++ b/examples/stm32f3_discovery/usb_dfu/main.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include + +using namespace Board; + +modm::PeriodicTimer tmr{1s}; + +// Invoked when device is mounted +void tud_mount_cb() { tmr.restart(1s); } +// Invoked when device is unmounted +void tud_umount_cb() { tmr.restart(250ms); } +// 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) { tmr.restart(2.5s); } +// Invoked when usb bus is resumed +void tud_resume_cb() { tmr.restart(1s); } + +modm_section(".noinit") uint64_t dfu_mode; +constexpr uint64_t DFU_MAGIC{0xf318ea89313f2be8}; + +/* 1. Do scons program once + * 2. Power-cycle by disconnecting USB cable from ST-Link + * 3. Force USB re-enumeration by connecting USB cable to UsbFs + * 3. Do scons program-dfu as many times as you want + */ + +int main() +{ + if (dfu_mode == DFU_MAGIC) + { + dfu_mode = 0; __DSB(); + // Jump to SystemFlash *before* initializing any peripherals! + asm volatile + ( + // Address of SystemFlash differs between devices!!! + "ldr r0, =0x1FFFD800" "\n\t" + "ldr sp,[r0, #0]" "\n\t" + "ldr r0,[r0, #4]" "\n\t" + "bx r0" + ); + } + Board::initialize(); + Board::initializeUsbFs(); + tusb_init(); + + while (true) + { + tud_task(); + + if (tmr.execute()) + { + if (dfu_mode == DFU_MAGIC) NVIC_SystemReset(); + LedNorth::toggle(); + } + } + + return 0; +} + +// Invoked on DFU_DETACH request to reboot to the bootloader +void tud_dfu_rt_reboot_to_dfu(void) +{ + dfu_mode = DFU_MAGIC; + // You must delay SystemReset so that TinyUSB can finish!!! + tmr.restart(100ms); +} diff --git a/examples/stm32f3_discovery/usb_dfu/project.xml b/examples/stm32f3_discovery/usb_dfu/project.xml new file mode 100644 index 0000000000..5889b438c6 --- /dev/null +++ b/examples/stm32f3_discovery/usb_dfu/project.xml @@ -0,0 +1,13 @@ + + modm:disco-f303vc + + + + + + modm:build:scons + modm:tinyusb + modm:processing:timer + modm:io + + diff --git a/ext/hathach/module.lb b/ext/hathach/module.lb new file mode 100644 index 0000000000..33af35ec17 --- /dev/null +++ b/ext/hathach/module.lb @@ -0,0 +1,310 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020, Niklas Hauser +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +from pathlib import Path +from collections import defaultdict +tusb_config = {} + +# ----------------------------------------------------------------------------- +def init(module): + module.name = ":tinyusb" + module.description = FileReader("module.md") + +def prepare(module, options): + if not (options[":target"].has_driver("usb") or + options[":target"].has_driver("usb_otg_fs") or + options[":target"].has_driver("usb_otg_hs")): + return False + + configs = {"device.cdc", "device.msc", "device.vendor", "device.midi", "device.dfu"} + # TODO: Allow all device classes + # configs = {"device.{}".format(p.parent.name) + # for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.h")} + # TODO: Allow all host classes + # configs |= {"host.{}".format(p.parent.name) + # for p in Path(localpath("tinyusb/src/class/")).glob("*/*_host.h")} + + module.add_list_option( + EnumerationOption(name="config", + description="Endpoint Configuration", + enumeration=sorted(configs), + dependencies=lambda vs: + [":tinyusb:{}".format(v.replace(".", ":")) for v in vs])) + + if options[":target"].has_driver("usb_otg_hs"): + module.add_option( + EnumerationOption(name="speed", + description="USB Port Speed", + enumeration={"full": "fs", "high": "hs"}, + default="full", + dependencies=lambda s: ":platform:usb:{}s".format(s[0]))) + + module.add_submodule(TinyUsbDeviceModule()) + module.add_submodule(TinyUsbHostModule()) + module.depends(":cmsis:device", ":architecture:interrupt", ":platform:usb") + return True + + +def validate(env): + if env.has_module(":tinyusb:device") and env.has_module(":tinyusb:host"): + raise ValidateException("TinyUSB won't let you use both Device *and* Host at the same time!") + + +def build(env): + env.collect(":build:path.include", "modm/ext/tinyusb/") + env.outbasepath = "modm/ext/tinyusb" + + env.copy("tinyusb/src/tusb.h", "tusb.h") + env.copy("tinyusb/src/tusb_option.h", "tusb_option.h") + env.copy("tinyusb/src/osal/osal.h", "osal/osal.h") + env.copy("tinyusb/src/common", "common/") + env.copy("tinyusb/src/tusb.c", "tusb.c") + + """ Generic config defaults: + - CFG_TUSB_CONFIG_FILE = tusb_config.h + - CFG_TUSB_DEBUG = 0 + - CFG_TUSB_DEBUG_PRINTF = [undef] + - CFG_TUSB_MCU = OPT_MCU_NONE + - CFG_TUSB_MEM_ALIGN = __attribute__((aligned(4))) + - CFG_TUSB_MEM_SECTION = [empty] # FIXME + - CFG_TUSB_OS = OPT_OS_NONE + - CFG_TUSB_RHPORT0_MODE = OPT_MODE_NONE + - CFG_TUSB_RHPORT1_MODE = OPT_MODE_NONE + """ + global tusb_config + + target = env[":target"].identifier + if target.platform == "stm32": + tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_STM32{}".format(target.family.upper()) + # TODO: use modm-devices driver type for this + fs_dev = (target.family in ["f0", "f3", "l0"] or + (target.family == "f1" and target.name <= "03")) + if fs_dev: + # PMA buffer size: 512B or 1024B + env.copy("tinyusb/src/portable/st/stm32_fsdev/", "portable/st/stm32_fsdev/") + else: + env.copy("tinyusb/src/portable/st/synopsys", "portable/st/synopsys/") + + elif target.platform == "sam": + if target.family == "g": + tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_SAMG" + env.copy("tinyusb/src/portable/microchip/samg/", "portable/microchip/samg/") + else: + tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_SAM{}{}".format(target.family.upper(), target.series.upper()) + env.copy("tinyusb/src/portable/microchip/samd/", "portable/microchip/samd/") + + if env.has_module(":freertos"): + tusb_config["CFG_TUSB_OS"] = "OPT_OS_FREERTOS" + env.copy("tinyusb/src/osal/osal_freertos.h", "osal/osal_freertos.h") + else: + env.copy("tinyusb/src/osal/osal_none.h", "osal/osal_none.h") + + if env.has_module(":debug"): + tusb_config["CFG_TUSB_DEBUG_PRINTF"] = "tinyusb_debug_printf" + # env.collect(":build:cppdefines.debug", "CFG_TUSB_DEBUG=2") + + has_device = env.has_module(":tinyusb:device") + has_host = env.has_module(":tinyusb:host") + speed = env.get("speed", "fs") + port = 0 if speed == "fs" else 1 + + mode = None + if has_device: mode = "OPT_MODE_DEVICE"; + if has_host: mode = "OPT_MODE_HOST"; + if mode is not None: + if "hs" in speed: mode = "({} | OPT_MODE_HIGH_SPEED)".format(mode); + tusb_config["CFG_TUSB_RHPORT{}_MODE".format(port)] = mode + + itf_config = env["config"] + # Enumerate the configurations + config_enum = defaultdict(list) + config_enum_counter = 0 + for conf in itf_config: + config_enum[conf.split(".")[1]].append(config_enum_counter) + config_enum_counter += 1 + + # Generate the ITF and Endpoint counters + itfs = [] + itf_enum = [] + endpoints = {} + endpoint_counter = 0 + for devclass, devitfs in config_enum.items(): + prefix = "{}".format(devclass.upper()) + for itf in devitfs: + endpoint_counter += 1 + itf_prefix = prefix + str(itf) + itfs.append( (prefix, itf_prefix) ) + if devclass in ["cdc"]: + itf_enum.extend([itf_prefix, itf_prefix + "_DATA"]) + endpoints[itf_prefix + "_NOTIF"] = hex(0x80 | endpoint_counter) + endpoint_counter += 1 + elif devclass in ["msc", "midi", "vendor", "dfu"]: + itf_enum.append(itf_prefix) + else: + raise ValidateException("Unknown ITF device class '{}'!".format(devclass)) + + endpoints[itf_prefix + "_OUT"] = hex(endpoint_counter) + endpoints[itf_prefix + "_IN"] = hex(0x80 | endpoint_counter) + + if target.platform == "stm32": + irq_data = env.query(":platform:usb:irqs") + irqs = irq_data["port_irqs"][speed] + if irq_data["is_remap"]: + tusb_config["STM32F3_IRQ_REMAP"] = 1 + else: + irqs = ["USB"] + + env.substitutions = { + "target": target, + "config": tusb_config, + "irqs": irqs, + "port": port, + "with_debug": env.has_module(":debug"), + "with_cdc": env.has_module(":tinyusb:device:cdc"), + "itfs": itfs, + "itfs_enum": itf_enum, + "endpoints": endpoints, + } + env.template("tusb_config.h.in") + if len(itf_config): env.template("tusb_descriptors.c.in"); + env.template("tusb_port.cpp.in") + + +# ----------------------------------------------------------------------------- +class TinyUsbDeviceModule(Module): + def init(self, module): + module.name = ":tinyusb:device" + module.description = "TinyUSB in Device Mode" + + def prepare(self, module, options): + paths = {p.parent for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.h")} + for path in paths: + module.add_submodule(TinyUsbClassModule(path, "device")) + return True + + def build(self, env): + env.outbasepath = "modm/ext/tinyusb" + env.copy("tinyusb/src/device/", "device/") + + """ Device config defaults: + - CFG_TUD_ENDPOINT0_SIZE = 64 + - CFG_TUD_TASK_QUEUE_SZ = 16 + """ + + +# ----------------------------------------------------------------------------- +class TinyUsbHostModule(Module): + def __init__(self): + self.config = {} + + def init(self, module): + module.name = ":tinyusb:host" + module.description = "TinyUSB in Host Mode" + + def prepare(self, module, options): + # Only OTG has Host Mode + if not (options[":target"].has_driver("usb_otg_fs") or + options[":target"].has_driver("usb_otg_hs")): + return False + + paths = {p.parent for p in Path(localpath("tinyusb/src/class/")).glob("*/*_host.h")} + for path in paths: + module.add_submodule(TinyUsbClassModule(path, "host")) + return True + + def build(self, env): + env.outbasepath = "modm/ext/tinyusb" + env.copy("tinyusb/src/host/", "host/") + + """ Host config defaults: + - CFG_TUH_TASK_QUEUE_SZ = 16 + """ + + +# ----------------------------------------------------------------------------- +class TinyUsbClassModule(Module): + def __init__(self, path, mode): + self.mode = mode + self.name = str(path.name) + + def init(self, module): + module.name = ":tinyusb:{}:{}".format(self.mode, self.name) + module.description = "{} class {}".format(self.mode.capitalize(), self.name.upper()) + + def prepare(self, module, options): + if self.mode == "device": + # FIXME: Changing these defaults seems to crash TinyUSB in weird ways. + # Since all of the TinyUSB examples leave them at these default values + # I think they are not supposed to be changed? -Niklas + # if self.name in ["cdc", "midi", "vendor"]: + # module.add_option(NumericOption("buffer.rx", description="", minimum=64, default=64)) + # module.add_option(NumericOption("buffer.tx", description="", minimum=64, default=64)) + # if self.name in ["msc"]: + # module.add_option(NumericOption("buffer.ep", description="", minimum=512, default=512)) + if self.name == "cdc": + module.depends(":architecture:uart") + module.depends(":tinyusb") + return True + + def build(self, env): + env.outbasepath = "modm/ext/tinyusb" + env.copy("tinyusb/src/class/{}".format(self.name), "class/{}".format(self.name), + ignore=env.ignore_files("*_host.*" if "device" in self.mode else "*_device.*")) + + global tusb_config + cfg_name = "CFG_TU{}_{}".format(self.mode[0].upper(), self.name.upper()) + if "DFU" in cfg_name: cfg_name += "_RT"; + tusb_config[cfg_name] = env[":tinyusb:config"].count("{}.{}".format(self.mode, self.name)) + speed = env.get(":tinyusb:speed", 0) + + if self.mode == "device": + # These are the defaults that don't crash TinyUSB. + if self.name in ["cdc", "midi", "vendor"]: + tusb_config[cfg_name+"_RX_BUFSIZE"] = 512 if speed else 64 #env["buffer.rx"] + tusb_config[cfg_name+"_TX_BUFSIZE"] = 512 if speed else 64 #env["buffer.tx"] + if self.name in ["msc"]: + tusb_config[cfg_name+"_EP_BUFSIZE"] = 512 #env["buffer.ep"] + if self.name == "midi": + env.copy("tinyusb/src/class/audio", "class/audio") + + + """ Device class defaults: + - CFG_TUD_BTH_DATA_EPSIZE = 64 + - CFG_TUD_BTH_EVENT_EPSIZE = 16 + - CFG_TUD_BTH_ISO_ALT_COUNT = 0 + + - CFG_TUD_CDC_EP_BUFSIZE = 64 or 512 (hs) + - CFG_TUD_CDC_RX_BUFSIZE = [undef] + - CFG_TUD_CDC_TX_BUFSIZE = [undef] + + - CFG_TUD_HID_EP_BUFSIZE = 16 + + - CFG_TUD_MIDI_EP_BUFSIZE = 64 or 512 (hs) + - CFG_TUD_MIDI_RX_BUFSIZE = [undef] + - CFG_TUD_MIDI_TX_BUFSIZE = [undef] + + - CFG_TUD_MSC_EP_BUFSIZE = [undef] + + - CFG_TUD_NET_ENDPOINT_SIZE = 64 or 512 (hs) + + - CFG_TUD_USBTMC_ENABLE_488 = 1 + + - CFG_TUD_VENDOR_EPSIZE = 64 + - CFG_TUD_VENDOR_RX_BUFSIZE = [undef] + - CFG_TUD_VENDOR_TX_BUFSIZE = [undef] + """ + + """ Host config defaults: + - CFG_TUH_CDC_RNDIS + - CFG_TUH_HID_KEYBOARD + - CFG_TUH_HID_MOUSE + """ diff --git a/ext/hathach/module.md b/ext/hathach/module.md new file mode 100644 index 0000000000..38f1df368d --- /dev/null +++ b/ext/hathach/module.md @@ -0,0 +1,107 @@ +# TinyUSB + +TinyUSB is an open-source cross-platform USB Host/Device stack for embedded +system, designed to be memory-safe with no dynamic allocation and thread-safe +with all interrupt events are deferred then handled in the non-ISR task +function. + +This module provides a autogenerated port for TinyUSB, which includes the +correct interrupt mapping, a serial number based on the UID of the device, +as well as remapping the assertions of TinyUSB. + + +## Autogeneration of USB Descriptors + +You can select the device classes you want to use via the `modm:tinyusb:config` +list option: + +- `device.cdc`: Serial connection (uses **two** endpoints!) +- `device.msc`: Mass Storage class. +- `device.midi`: MIDI device. +- `device.vendor`: WebUSB device. +- `device.dfu`: DFU (runtime only). + +Note that you can add multiple devices at the same time, as long as there are +enough endpoints and USB RAM available: + +```xml + + + + +``` + +modm will generate the USB descriptors automatically for the set of device +classes you've chosen. You can then implement your app via TinyUSB callbacks. + + +## Manual USB Descriptors + +If you leave the `modm:tinyusb:config` option empty, no descriptors are +generated, so you can implement them yourself. Note that you must also +manually depend on the device classes you want to implement: + +```xml +modm:tinyusb:device:net +modm:tinyusb:device:hid +modm:tinyusb:device:usbtmc +``` + + +## Initializing USB + +The `modm:platform:usb` module provides the correct way of initializing the USB +peripheral, however, you must connect the right signals too: + +```cpp +// USB is timing-sensitive, so prioritize the IRQs accordingly +Usb::initialize(/*priority=*/3); + +// For Device-Only USB implementations, this is enough +Usb::connect(); + +// But for On-The-Go (OTG) USB implementations, you need more: +Usb::connect(); +// Enable hardware Vbus sensing on GpioA9 (this can be tricky to get right!) +USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBDEN; +``` + +Note that depending on your specific hardware setup, you may need to fiddle +around to find the right VBus sensing mechanism. + +!!! warning "USB shares resources with CAN" + Note that on STM32F1 and STM32F3 the USB interrupts and RAM are shared with CAN, + thus there are conflicts in IRQ definitions as well as resource limitions in + hardware. On some STM32F3, the USB IRQs can be remapped, this is done + automatically by our port. + + +## Debugging TinyUSB + +Since we've made it so easy to add multiple device classes, it's also easy to +run out of endpoints or RAM. Therefore we reroute TinyUSBs assertions to +`modm_assert`, so make sure you have implemented the `modm_abandon` handler! +See the `modm:architecture:assert` module for details. + +A TinyUSB assertion failure in release mode is fairly cryptic: + +``` +Assertion 'tu' failed! +Abandoning... +``` + +If you run this again in debug mode, you'll note a much more detailed assertion +description. In this example you've exhaused the number of endpoints: + +``` +Assertion 'tu' failed! + modm/ext/tinyusb/portable/st/synopsys/dcd_synopsys.c:524 -> "epnum < 4U" +Abandoning... +``` + +To trace the TinyUSB core, you can add `CFG_TUSB_DEBUG=2` to your CPP flags and +the output will be forwarded to `MODM_LOG_DEBUG`. + +``` +CFG_TUSB_DEBUG=2 +``` diff --git a/ext/hathach/tinyusb b/ext/hathach/tinyusb new file mode 160000 index 0000000000..1b2eae6387 --- /dev/null +++ b/ext/hathach/tinyusb @@ -0,0 +1 @@ +Subproject commit 1b2eae638788813a96771fb0fd25a91029f0b37c diff --git a/ext/hathach/tusb_config.h.in b/ext/hathach/tusb_config.h.in new file mode 100644 index 0000000000..8f07eb8ac6 --- /dev/null +++ b/ext/hathach/tusb_config.h.in @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +%% for name, value in config.items() | sort +#define {{name}} {{value}} + +%% endfor + +#include + +// Redirect TinyUSB asserts to use modm_assert +#define MODM_ASSERT_1ARGS(_cond) \ + TU_VERIFY_DEFINE(_cond, modm_assert(0, "tu", \ + __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #_cond "\""), false) + +#define MODM_ASSERT_2ARGS(_cond, _ret) \ + TU_VERIFY_DEFINE(_cond, modm_assert_continue_fail(0, "tu", \ + __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #_cond "\""), _ret) + +#define TU_ASSERT(...) GET_3RD_ARG(__VA_ARGS__, MODM_ASSERT_2ARGS, MODM_ASSERT_1ARGS,UNUSED)(__VA_ARGS__) diff --git a/ext/hathach/tusb_descriptors.c.in b/ext/hathach/tusb_descriptors.c.in new file mode 100644 index 0000000000..213d5b089e --- /dev/null +++ b/ext/hathach/tusb_descriptors.c.in @@ -0,0 +1,201 @@ +/* + * 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] MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) +#define USB_PID \ + (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 2) | _PID_MAP(HID, 3) | \ + _PID_MAP(MIDI, 4) | _PID_MAP(VENDOR, 5)) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +const tusb_desc_device_t desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0210, + +%% if with_cdc + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, +%% else + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, +%% endif + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .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; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +enum +{ +%% for itf in itfs_enum + ITF_NUM_{{itf}}{% if loop.first %} = 0{% endif %}, +%% endfor + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + \ + CFG_TUD_CDC * TUD_CDC_DESC_LEN + \ + CFG_TUD_MSC * TUD_MSC_DESC_LEN + \ + CFG_TUD_HID * TUD_HID_DESC_LEN + \ + CFG_TUD_MIDI * TUD_MIDI_DESC_LEN + \ + CFG_TUD_DFU_RT * TUD_DFU_RT_DESC_LEN + \ + CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN) + +%% for endpoint, value in endpoints.items() +#define EPNUM_{{ endpoint }} {{ value }} +%% endfor + +%% macro itf_descr(hs=False) +%% set ep_size = 512 if hs else 64 +const uint8_t desc_{{"hs" if hs else "fs"}}_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, 100), + +%% for (itf_class, itf) in itfs + %% if itf_class in ["CDC"] + // {{loop.index}}st CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_{{itf}}, {{loop.index0+4}}, EPNUM_{{itf}}_NOTIF, 8, EPNUM_{{itf}}_OUT, EPNUM_{{itf}}_IN, {{ep_size}}), + %% elif itf_class in ["MSC", "MIDI", "VENDOR"] + // {{loop.index}}st {{itf_class}}: Interface number, string index, EP Out & EP In address, EP size + TUD_{{itf_class}}_DESCRIPTOR(ITF_NUM_{{itf}}, {{loop.index0+4}}, EPNUM_{{itf}}_OUT, EPNUM_{{itf}}_IN, {{ep_size}}), + %% elif itf_class in ["DFU"] + // Interface number, string index, attributes, detach timeout, transfer size */ + TUD_DFU_RT_DESCRIPTOR(ITF_NUM_{{itf}}, {{loop.index0+4}}, 0x0d, 1000, 4096), + %% endif +%% endfor +}; +%% endmacro + +{{ itf_descr(hs=False) }} + +%% if port +{{ itf_descr(hs=True) }} +%% endif + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +const uint8_t* tud_descriptor_configuration_cb(uint8_t index) +{ + (void)index; // for multiple configurations +%% if port + // Although we are highspeed, host may be fullspeed. + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; +%% else + return desc_fs_configuration; +%% endif +} +// +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +const char* string_desc_arr[] = +{ + NULL, // 0: Language + "TinyUSB", // 1: Manufacturer + "TinyUSB Device", // 2: Product + NULL, // 3: Serials, should use chip ID +%% for (_, itf) in itfs + "TinyUSB {{itf}}", // {{loop.index0 + 4}}: {{itf}} Interface +%% endfor +}; + +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 +const uint16_t* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void)langid; + uint8_t chr_count = 1; + + if (index == 0) + { // supported language is English (0x0409) + _desc_str[1] = 0x09; _desc_str[2] = 0x04; + } + else if (index == 3) + { + extern uint8_t tusb_get_device_serial(uint16_t* serial_str); + chr_count = tusb_get_device_serial(_desc_str + 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; +} diff --git a/ext/hathach/tusb_port.cpp.in b/ext/hathach/tusb_port.cpp.in new file mode 100644 index 0000000000..01693d699a --- /dev/null +++ b/ext/hathach/tusb_port.cpp.in @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include "tusb.h" + +%% for irq in irqs | sort +MODM_ISR({{irq}}) +{ + tud_int_handler({{port}}); +} +%% endfor + + +extern "C" uint8_t +tusb_get_device_serial(uint16_t *serial_str) +{ + constexpr uint8_t SERIAL_BYTE_LEN = 16; + + uint32_t *id_addresses[4] = + { +%% if target.platform in ["stm32"] + ((uint32_t *) UID_BASE), ((uint32_t *) UID_BASE) + 1, + ((uint32_t *) UID_BASE) + 2, ((uint32_t *) UID_BASE) + 3 +%% elif target.platform in ["sam"] + %% if "samd51" in target.string + (uint32_t *)0x008061FC, (uint32_t *)0x00806010, + (uint32_t *)0x00806014, (uint32_t *)0x00806018 + %% else + (uint32_t *)0x0080A00C, (uint32_t *)0x0080A040, + (uint32_t *)0x0080A044, (uint32_t *)0x0080A048 + %% endif +%% endif + }; + + uint8_t raw_id[SERIAL_BYTE_LEN]; + + for (int i = 0; i < 4; i++) + for (int k = 0; k < 4; k++) + raw_id[4 * i + (3 - k)] = (*(id_addresses[i]) >> k * 8) & 0xff; + + const auto fn_nibble = [](uint8_t nibble) + { + return nibble + (nibble > 9 ? 'A' - 10 : '0'); + }; + + for (unsigned int i = 0; i < sizeof(raw_id); i++) + { + serial_str[i * 2 + 1] = fn_nibble(raw_id[i] & 0xf); + serial_str[i * 2] = fn_nibble(raw_id[i] >> 4 & 0xf); + } + + return sizeof(raw_id) * 2; +} + +%% if with_debug +#ifdef CFG_TUSB_DEBUG +#include + +extern "C" int +tinyusb_debug_printf(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + modm::log::debug.vprintf(fmt, va); + va_end(va); + return 0; +} +#endif +%% endif diff --git a/ext/hathach/uart.hpp b/ext/hathach/uart.hpp new file mode 100644 index 0000000000..3c9335a16e --- /dev/null +++ b/ext/hathach/uart.hpp @@ -0,0 +1,84 @@ +/* +* Copyright (c) 2020, Erik Henriksson +* Copyright (c) 2020, Niklas Hauser +* +* This file is part of the modm project. +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include "tusb.h" + +namespace modm::platform +{ + +template< uint8_t ITF=0 > +class UsbUart : public modm::Uart +{ + static_assert(ITF < CFG_TUD_CDC, "TinyUSB does not have this CDC port!"); +public: + static inline bool + connected() + { + return tud_cdc_n_connected(ITF); + } + + static inline bool + write(uint8_t c) + { + bool rc = tud_cdc_n_write_char(ITF, c); + tud_cdc_n_write_flush(ITF); + return rc; + } + + static inline std::size_t + write(const uint8_t *data, std::size_t length) + { + std::size_t rc = tud_cdc_n_write(ITF, data, length); + tud_cdc_n_write_flush(ITF); + return rc; + } + + static inline void + flushWriteBuffer() + { + tud_cdc_n_write_flush(ITF); + } + + static inline bool + read(uint8_t& c) + { + return tud_cdc_n_read(ITF, &c, sizeof(uint8_t)); + } + + static inline std::size_t + read(uint8_t *data, std::size_t length) + { + return tud_cdc_n_read(ITF, data, length); + } + + static inline std::size_t + receiveBufferSize() + { + return tud_cdc_n_available(ITF); + } + + static inline std::size_t + transmitBufferSize() + { + return tud_cdc_n_write_available(ITF); + } +}; + +using UsbUart0 = UsbUart<0>; +using UsbUart1 = UsbUart<1>; +using UsbUart2 = UsbUart<2>; +using UsbUart3 = UsbUart<3>; + +} // namespace modm::platform diff --git a/ext/st/module.lb b/ext/st/module.lb index 84e043dd99..d54d59d81d 100644 --- a/ext/st/module.lb +++ b/ext/st/module.lb @@ -112,6 +112,7 @@ def common_rcc_map(env): "folder": folder, "define": define, "device_header": device_header, + "family_header": family_header, "system_header": "system_" + family_header, "peripherals": peripherals, }) @@ -144,6 +145,8 @@ def build(env): env.outbasepath = "modm/ext/cmsis/device" env.copy(localpath(bprops["folder"], bprops["device_header"]), bprops["device_header"]) env.copy(localpath(bprops["folder"], bprops["system_header"]), bprops["system_header"]) + if env.has_module(":tinyusb"): # Requires the family header + env.copy(localpath(bprops["folder"], bprops["family_header"]), bprops["family_header"]) env.substitutions = bprops env.substitutions.update({ diff --git a/repo.lb b/repo.lb index 246e0764e2..e6fed9f37d 100644 --- a/repo.lb +++ b/repo.lb @@ -24,7 +24,7 @@ from os.path import normpath # Check for miminum required lbuild version import lbuild -min_lbuild_version = "1.13.2" +min_lbuild_version = "1.14.1" if StrictVersion(getattr(lbuild, "__version__", "0.1.0")) < StrictVersion(min_lbuild_version): print("modm requires at least lbuild v{}, please upgrade!\n" " pip3 install -U lbuild".format(min_lbuild_version)) diff --git a/src/modm/board/black_pill/board.hpp b/src/modm/board/black_pill/board.hpp index 2288b62008..8aab52dc0d 100644 --- a/src/modm/board/black_pill/board.hpp +++ b/src/modm/board/black_pill/board.hpp @@ -55,6 +55,8 @@ struct SystemClock { static constexpr uint32_t Timer3 = Apb1Timer; static constexpr uint32_t Timer4 = Apb1Timer; + static constexpr uint32_t Usb = Ahb / 1.5; + static bool inline enable() { @@ -62,7 +64,8 @@ struct SystemClock { // external clock * 9 = 72MHz, => 72/1.5 = 48 => good for USB const Rcc::PllFactors pllFactors{ - .pllMul = 9 + .pllMul = 9, + .usbPrediv = Rcc::UsbPrescaler::Div1_5 }; Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); @@ -88,6 +91,13 @@ struct SystemClock { } }; +namespace usb +{ +using Dm = GpioA11; // DM: USB_DM +using Dp = GpioA12; // DP: USB_DP +using Device = UsbFs; +} + // User LED (inverted, because connected to 3V3) using LedGreen = GpioInverted< GpioOutputB12 >; using Leds = SoftwareGpioPort< LedGreen >; @@ -103,6 +113,13 @@ initialize() LedGreen::setOutput(modm::Gpio::Low); } +inline void +initializeUsbFs() +{ + usb::Device::initialize(); + usb::Device::connect(); +} + } // Board namespace #endif // MODM_STM32_F103C8T6_BLACK_PILL_HPP diff --git a/src/modm/board/black_pill/module.lb b/src/modm/board/black_pill/module.lb index 289161895f..03a5881939 100644 --- a/src/modm/board/black_pill/module.lb +++ b/src/modm/board/black_pill/module.lb @@ -62,7 +62,8 @@ def prepare(module, options): ":architecture:clock", ":platform:clock", ":platform:core", - ":platform:gpio") + ":platform:gpio", + ":platform:usb") return True def build(env): diff --git a/src/modm/board/blue_pill/board.hpp b/src/modm/board/blue_pill/board.hpp index 8157575467..8cad641c9b 100644 --- a/src/modm/board/blue_pill/board.hpp +++ b/src/modm/board/blue_pill/board.hpp @@ -56,6 +56,8 @@ struct SystemClock { static constexpr uint32_t Timer3 = Apb1Timer; static constexpr uint32_t Timer4 = Apb1Timer; + static constexpr uint32_t Usb = Ahb / 1.5; + static bool inline enable() { @@ -63,7 +65,8 @@ struct SystemClock { // external clock * 9 = 72MHz, => 72/1.5 = 48 => good for USB const Rcc::PllFactors pllFactors{ - .pllMul = 9 + .pllMul = 9, + .usbPrediv = Rcc::UsbPrescaler::Div1_5 }; Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); @@ -89,6 +92,13 @@ struct SystemClock { } }; +namespace usb +{ +using Dm = GpioA11; // DM: USB_DM +using Dp = GpioA12; // DP: USB_DP +using Device = UsbFs; +} + // User LED (inverted, because connected to 3V3) using LedGreen = GpioInverted< GpioOutputC13 >; using Leds = SoftwareGpioPort< LedGreen >; @@ -104,6 +114,13 @@ initialize() LedGreen::setOutput(modm::Gpio::Low); } +inline void +initializeUsbFs() +{ + usb::Device::initialize(); + usb::Device::connect(); +} + } // Board namespace #endif // MODM_STM32_F103C8T6_BLUE_PILL_HPP diff --git a/src/modm/board/blue_pill/module.lb b/src/modm/board/blue_pill/module.lb index 303c309cac..09e1c109e0 100644 --- a/src/modm/board/blue_pill/module.lb +++ b/src/modm/board/blue_pill/module.lb @@ -62,7 +62,8 @@ def prepare(module, options): ":architecture:clock", ":platform:clock", ":platform:core", - ":platform:gpio") + ":platform:gpio", + ":platform:usb") return True def build(env): diff --git a/src/modm/board/disco_f303vc/board.hpp b/src/modm/board/disco_f303vc/board.hpp index 08280470d9..60e7cd8df9 100644 --- a/src/modm/board/disco_f303vc/board.hpp +++ b/src/modm/board/disco_f303vc/board.hpp @@ -81,13 +81,16 @@ struct SystemClock { static constexpr uint32_t Timer16 = Apb2Timer; static constexpr uint32_t Timer17 = Apb2Timer; + static constexpr uint32_t Usb = Ahb / 1.5; + static bool inline enable() { Rcc::enableExternalClock(); // 8MHz const Rcc::PllFactors pllFactors{ .pllMul = 9, - .pllPrediv = 1 + .pllPrediv = 1, + .usbPrediv = Rcc::UsbPrescaler::Div1_5 }; Rcc::enablePll(Rcc::PllSource::ExternalClock, pllFactors); // set flash latency for 72MHz @@ -155,7 +158,7 @@ namespace usb { using Dm = GpioA11; // DM: USB_DM using Dp = GpioA12; // DP: USB_DP -//using Device = UsbFs; +using Device = UsbFs; } @@ -165,14 +168,7 @@ initialize() SystemClock::enable(); SysTickTimer::initialize(); - LedNorth::setOutput(modm::Gpio::Low); - LedNorthEast::setOutput(modm::Gpio::Low); - LedEast::setOutput(modm::Gpio::Low); - LedSouthEast::setOutput(modm::Gpio::Low); - LedSouth::setOutput(modm::Gpio::Low); - LedSouthWest::setOutput(modm::Gpio::Low); - LedWest::setOutput(modm::Gpio::Low); - LedNorthWest::setOutput(modm::Gpio::Low); + Leds::setOutput(modm::Gpio::Low); Button::setInput(); Button::setInputTrigger(Gpio::InputTrigger::RisingEdge); @@ -225,12 +221,11 @@ initializeLsm3() } -/// not supported yet, due to missing USB driver inline void -initializeUsb() +initializeUsbFs() { -// usb::Dm::connect(usb::Device::Dm); -// usb::Dp::connect(usb::Device::Dp); + usb::Device::initialize(); + usb::Device::connect(); } } diff --git a/src/modm/board/disco_f303vc/module.lb b/src/modm/board/disco_f303vc/module.lb index 6f33a2afcb..e6f7a56aca 100644 --- a/src/modm/board/disco_f303vc/module.lb +++ b/src/modm/board/disco_f303vc/module.lb @@ -30,7 +30,8 @@ def prepare(module, options): ":platform:core", ":platform:gpio", ":platform:i2c:1", - ":platform:spi:1") + ":platform:spi:1", + ":platform:usb") return True def build(env): diff --git a/src/modm/board/disco_f407vg/board.hpp b/src/modm/board/disco_f407vg/board.hpp index 75a21006c0..e99e4af4ec 100644 --- a/src/modm/board/disco_f407vg/board.hpp +++ b/src/modm/board/disco_f407vg/board.hpp @@ -75,6 +75,8 @@ struct SystemClock { static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Usb = 48_MHz; + static bool inline enable() { @@ -82,7 +84,8 @@ struct SystemClock { const Rcc::PllFactors pllFactors{ .pllM = 4, // 8MHz / M=4 -> 2MHz .pllN = 168, // 2MHz * N=168 -> 336MHz - .pllP = 2 // 336MHz / P=2 -> 168MHz = F_cpu + .pllP = 2, // 336MHz / P=2 -> 168MHz = F_cpu + .pllQ = 7 // 336MHz / Q=7 -> 48MHz = F_usb }; Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); // set flash latency for 168MHz @@ -154,14 +157,15 @@ using Dout = GpioInputC3; // PDM_OUT: I2S2_SD namespace usb { +using Vbus = GpioInputA9; // VBUS_FS: USB_OTG_HS_VBUS +using Id = GpioA10; // OTG_FS_ID: USB_OTG_FS_ID using Dm = GpioA11; // OTG_FS_DM: USB_OTG_FS_DM using Dp = GpioA12; // OTG_FS_DP: USB_OTG_FS_DP -using Id = GpioA10; // OTG_FS_ID: USB_OTG_FS_ID -using Overcurrent = GpioD5; // OTG_FS_OverCurrent -using Power = GpioOutputC0; // OTG_FS_PowerSwitchOn -using VBus = GpioInputA9; // VBUS_FS: USB_OTG_HS_VBUS -//using Device = UsbFs; +using Overcurrent = GpioInputD5; // OTG_FS_OverCurrent +using Power = GpioOutputC0; // OTG_FS_PowerSwitchOn + +using Device = UsbFs; } @@ -171,10 +175,7 @@ initialize() SystemClock::enable(); SysTickTimer::initialize(); - LedOrange::setOutput(modm::Gpio::Low); - LedGreen::setOutput(modm::Gpio::Low); - LedRed::setOutput(modm::Gpio::Low); - LedBlue::setOutput(modm::Gpio::Low); + Leds::setOutput(modm::Gpio::Low); Button::setInput(); Button::setInputTrigger(Gpio::InputTrigger::RisingEdge); @@ -220,18 +221,17 @@ initializeMp45() // mp45::Dout::connect(mp45::I2sMaster::Sd); } -/// not supported yet, due to missing USB driver inline void -initializeUsb() +initializeUsbFs() { -// usb::Dm::connect(usb::Device::Dm); -// usb::Dp::connect(usb::Device::Dp); -// usb::Id::connect(usb::Device::Id); - - usb::Power::setOutput(Gpio::OutputType::PushPull, Gpio::OutputSpeed::MHz2); - - usb::Overcurrent::setInput(Gpio::InputType::Floating); - usb::VBus::setInput(Gpio::InputType::Floating); + usb::Device::initialize(); + usb::Device::connect(); + + usb::Overcurrent::setInput(); + usb::Vbus::setInput(); + // Enable VBUS sense (B device) via pin PA9 + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; } } diff --git a/src/modm/board/disco_f407vg/module.lb b/src/modm/board/disco_f407vg/module.lb index aea2bd53a5..f5adb050d1 100644 --- a/src/modm/board/disco_f407vg/module.lb +++ b/src/modm/board/disco_f407vg/module.lb @@ -30,7 +30,8 @@ def prepare(module, options): ":platform:core", ":platform:gpio", ":platform:i2c:1", - ":platform:spi:1") + ":platform:spi:1", + ":platform:usb:fs") return True def build(env): diff --git a/src/modm/board/disco_f469ni/board.hpp b/src/modm/board/disco_f469ni/board.hpp index a3cd351d21..84f0c49cea 100644 --- a/src/modm/board/disco_f469ni/board.hpp +++ b/src/modm/board/disco_f469ni/board.hpp @@ -79,6 +79,8 @@ struct SystemClock static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Usb = 48_MHz; + static bool inline enable() { @@ -86,7 +88,7 @@ struct SystemClock const Rcc::PllFactors pllFactors{ .pllM = 8, // 8MHz / M=8 -> 1MHz !!! Must be 1 MHz for PLLSAI !!! .pllN = 360, // 1MHz * N=360 -> 360MHz - .pllP = 2 // 360MHz / P=2 -> 180MHz = F_cpu + .pllP = 2, // 360MHz / P=2 -> 180MHz = F_cpu }; Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); PWR->CR |= PWR_CR_ODEN; // Enable overdrive mode @@ -97,6 +99,22 @@ struct SystemClock Rcc::setApb2Prescaler(Rcc::Apb2Prescaler::Div2); Rcc::updateCoreFrequency(); + { + // LCD clock configuration + // PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz + // PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 384 Mhz + // PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 384 MHz / 7 = 54.857 MHz + // LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_2 = 54.857 MHz / 2 = 27.429 MHz + RCC->PLLSAICFGR = (7 << 28) | (15 << 24) | (3 << 16) | (384 << 6); + // Select PLLSAI clock for 48MHz clocks + RCC->DCKCFGR |= RCC_DCKCFGR_CK48MSEL; + // Enable PLLSAI + RCC->CR |= RCC_CR_PLLSAION; + for (int t = 1'024; not (RCC->CR & RCC_CR_PLLSAIRDY) and t; t--) { + modm::delay_ms(1); + } + } + return true; } }; @@ -148,6 +166,19 @@ using I2cMaster = I2cMaster1; using Touch = modm::Ft6x06< I2cMaster >; } +namespace usb +{ +using Vbus = GpioA9; +using Id = GpioA10; +using Dm = GpioA11; +using Dp = GpioA12; + +using Overcurrent = GpioInputB7; // OTG_FS_OverCurrent +using Power = GpioOutputB2; // OTG_FS_PowerSwitchOn + +using Device = UsbFs; +} + namespace stlink { using Tx = GpioOutputB10; // STLK_RX [STLINK V2-1_U2_RX]: USART3_TX @@ -202,6 +233,18 @@ initialize() // Button::enableExternalInterruptVector(12); } +inline void +initializeUsbFs() +{ + usb::Device::initialize(); + usb::Device::connect(); + + usb::Overcurrent::setInput(); + usb::Vbus::setInput(); + // Enable VBUS sense (B device) via pin PA9 + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBDEN; +} + } #endif // MODM_STM32_F469_DISCOVERY_HPP diff --git a/src/modm/board/disco_f469ni/board_dsi.cpp b/src/modm/board/disco_f469ni/board_dsi.cpp index cd00bddb6e..f9f86792ae 100644 --- a/src/modm/board/disco_f469ni/board_dsi.cpp +++ b/src/modm/board/disco_f469ni/board_dsi.cpp @@ -119,6 +119,7 @@ board_initialize_display(uint8_t ColorCoding) DSI->WCR = DSI_WCR_DSIEN; } + /* done by SystemClock::enable() { // LCD clock configuration // PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz @@ -134,6 +135,7 @@ board_initialize_display(uint8_t ColorCoding) modm::delay_ms(1); } } + */ { // HAL_LTDC_Init(&hltdc_eval); diff --git a/src/modm/board/disco_f469ni/module.lb b/src/modm/board/disco_f469ni/module.lb index 5b284a1137..bf80752916 100644 --- a/src/modm/board/disco_f469ni/module.lb +++ b/src/modm/board/disco_f469ni/module.lb @@ -30,6 +30,7 @@ def prepare(module, options): ":platform:gpio", ":platform:i2c:1", ":platform:uart:3", + ":platform:usb:fs", ":ui:display", ":math:utils") return True diff --git a/src/modm/board/disco_f746ng/board.hpp b/src/modm/board/disco_f746ng/board.hpp index ef0ad80f82..67dfa02762 100644 --- a/src/modm/board/disco_f746ng/board.hpp +++ b/src/modm/board/disco_f746ng/board.hpp @@ -33,8 +33,8 @@ namespace Board struct SystemClock { static constexpr uint32_t Frequency = 216_MHz; - static constexpr uint32_t Apb1 = Frequency / 8; - static constexpr uint32_t Apb2 = Frequency / 4; + static constexpr uint32_t Apb1 = Frequency / 4; + static constexpr uint32_t Apb2 = Frequency / 2; static constexpr uint32_t Adc1 = Apb2; static constexpr uint32_t Adc2 = Apb2; @@ -78,6 +78,8 @@ struct SystemClock static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Usb = 48_MHz; + static bool inline enable() { @@ -85,17 +87,19 @@ struct SystemClock const Rcc::PllFactors pllFactors{ .pllM = 25, // 25MHz / M=25 -> 1MHz .pllN = 432, // 1MHz * N=432 -> 432MHz - .pllP = 2 // 432MHz / P=2 -> 216MHz = F_cpu + .pllP = 2, // 432MHz / P=2 -> 216MHz = F_cpu + .pllQ = 9 // 432MHz / Q=9 -> 48MHz = F_usb }; Rcc::enablePll(Rcc::PllSource::ExternalClock, pllFactors); PWR->CR1 |= PWR_CR1_ODEN; // Enable overdrive mode while (not (PWR->CSR1 & PWR_CSR1_ODRDY)) ; + Rcc::setFlashLatency(); Rcc::enableSystemClock(Rcc::SystemClockSource::Pll); - // APB1 is running only at 27MHz, since AHB / 4 = 54MHz > 45MHz limit! - Rcc::setApb1Prescaler(Rcc::Apb1Prescaler::Div8); - // APB2 is running only at 54MHz, since AHB / 2 = 108MHz > 90MHz limit! - Rcc::setApb2Prescaler(Rcc::Apb2Prescaler::Div4); + // APB1 is running at 54MHz + Rcc::setApb1Prescaler(Rcc::Apb1Prescaler::Div4); + // APB2 is running at 108MHz + Rcc::setApb2Prescaler(Rcc::Apb2Prescaler::Div2); Rcc::updateCoreFrequency(); return true; @@ -133,6 +137,37 @@ using D13 = GpioI1; using D14 = GpioB9; using D15 = GpioB8; +namespace usb_fs +{ +using Vbus = GpioA9; +using Id = GpioA10; +using Dm = GpioA11; +using Dp = GpioA12; + +using Device = UsbFs; +} + +namespace usb_hs +{ +using Ck = GpioA5; + +using D0 = GpioA3; +using D1 = GpioB0; +using D2 = GpioB1; +using D3 = GpioB10; +using D4 = GpioB11; +using D5 = GpioB12; +using D6 = GpioB13; +using D7 = GpioB5; + +using Stp = GpioC0; +using Dir = GpioC2; +using Nxt = GpioH4; + +using Overcurrent = GpioE3; + +using Device = UsbHs; +} namespace stlink { @@ -157,6 +192,51 @@ initialize() Button::setInputTrigger(Gpio::InputTrigger::RisingEdge); Button::enableExternalInterrupt(); // Button::enableExternalInterruptVector(12); + + // Disable Backlight + GpioK3::setOutput(modm::Gpio::Low); +} + +inline void +initializeUsbFs() +{ + usb_fs::Device::initialize(); + usb_fs::Device::connect(); + + USB_OTG_DeviceTypeDef *dev = (USB_OTG_DeviceTypeDef *) (USB_OTG_FS_PERIPH_BASE + USB_OTG_DEVICE_BASE); + dev->DCTL |= USB_OTG_DCTL_SDIS; + + // Deactivate VBUS Sensing B + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBDEN; + + // B-peripheral session valid override enable + USB_OTG_FS->GOTGCTL |= USB_OTG_GOTGCTL_BVALOEN; + USB_OTG_FS->GOTGCTL |= USB_OTG_GOTGCTL_BVALOVAL; +} + +inline void +initializeUsbHs() +{ + usb_hs::Device::initialize(); + usb_hs::Device::connect< + usb_hs::Ck::Ulpick, usb_hs::Stp::Ulpistp, + usb_hs::Dir::Ulpidir, usb_hs::Nxt::Ulpinxt, + usb_hs::D0::Ulpid0, usb_hs::D1::Ulpid1, + usb_hs::D2::Ulpid2, usb_hs::D3::Ulpid3, + usb_hs::D4::Ulpid4, usb_hs::D5::Ulpid5, + usb_hs::D6::Ulpid6, usb_hs::D7::Ulpid7>(); + usb_hs::Overcurrent::setInput(); + + // Deactivate VBUS Sensing B + USB_OTG_HS->GCCFG &= ~USB_OTG_GCCFG_VBDEN; + + // B-peripheral session valid override enable + USB_OTG_HS->GOTGCTL |= USB_OTG_GOTGCTL_BVALOEN; + USB_OTG_HS->GOTGCTL |= USB_OTG_GOTGCTL_BVALOVAL; + + // Force device mode + USB_OTG_HS->GUSBCFG &= ~USB_OTG_GUSBCFG_FHMOD; + USB_OTG_HS->GUSBCFG |= USB_OTG_GUSBCFG_FDMOD; } } diff --git a/src/modm/board/disco_f746ng/module.lb b/src/modm/board/disco_f746ng/module.lb index 0499aa4d0b..e80e42015e 100644 --- a/src/modm/board/disco_f746ng/module.lb +++ b/src/modm/board/disco_f746ng/module.lb @@ -29,7 +29,9 @@ def prepare(module, options): ":platform:clock", ":platform:core", ":platform:gpio", - ":platform:uart:1") + ":platform:uart:1", + ":platform:usb:fs", + ":platform:usb:hs") return True def build(env): diff --git a/src/modm/board/feather_m0/board.hpp b/src/modm/board/feather_m0/board.hpp index 4a669e354d..d4a7936f11 100644 --- a/src/modm/board/feather_m0/board.hpp +++ b/src/modm/board/feather_m0/board.hpp @@ -27,13 +27,13 @@ namespace Board using namespace modm::literals; -using ARef = GpioA03; -using A0 = GpioA02; -using A1 = GpioB08; -using A2 = GpioB09; -using A3 = GpioA04; -using A4 = GpioA05; -using A5 = GpioB02; +using ARef = GpioA03; +using A0 = GpioA02; +using A1 = GpioB08; +using A2 = GpioB09; +using A3 = GpioA04; +using A4 = GpioA05; +using A5 = GpioB02; using Sck = GpioB11; using Mosi = GpioB10; @@ -65,6 +65,7 @@ using Led = D13; struct SystemClock { static constexpr uint32_t Frequency = 48_MHz; + static constexpr uint32_t Usb = 48_MHz; // static constexpr uint32_t Ahb = Frequency; // static constexpr uint32_t Apba = Frequency; // static constexpr uint32_t Apbb = Frequency; @@ -90,7 +91,7 @@ struct SystemClock static bool inline enable() { - GenericClockController::setFlashLatency(); + GenericClockController::setFlashLatency(); GenericClockController::initExternalCrystal(); GenericClockController::initDFLL48MHz(); GenericClockController::initOsc8MHz(); @@ -114,4 +115,11 @@ initialize() Led::setOutput(modm::Gpio::Low); } +inline void +initializeUsbFs() +{ + modm::platform::Usb::initialize(); + modm::platform::Usb::connect(); +} + } // Board namespace diff --git a/src/modm/board/feather_m0/module.lb b/src/modm/board/feather_m0/module.lb index 0449076ed2..a60336d5f7 100644 --- a/src/modm/board/feather_m0/module.lb +++ b/src/modm/board/feather_m0/module.lb @@ -30,8 +30,13 @@ def prepare(module, options): if not options[":target"].partname.startswith("samd21g18a"): return False - module.depends(":platform:gclk", ":platform:core", ":platform:clock", - ":platform:gpio", ":platform:uart:0", ":debug") + module.depends(":platform:gclk", + ":platform:core", + ":platform:clock", + ":platform:gpio", + ":platform:uart:0", + ":platform:usb", + ":debug") return True def build(env): diff --git a/src/modm/board/mini_f401/module.lb b/src/modm/board/mini_f401/module.lb index f584724831..73c53f0a89 100644 --- a/src/modm/board/mini_f401/module.lb +++ b/src/modm/board/mini_f401/module.lb @@ -63,7 +63,8 @@ def prepare(module, options): ":architecture:clock", ":platform:clock", ":platform:core", - ":platform:gpio") + ":platform:gpio", + ":platform:usb:fs") return True def build(env): diff --git a/src/modm/board/mini_f411/board.hpp b/src/modm/board/mini_f411/board.hpp index 09eb572553..a9be66a49c 100644 --- a/src/modm/board/mini_f411/board.hpp +++ b/src/modm/board/mini_f411/board.hpp @@ -18,7 +18,7 @@ using namespace modm::platform; -/// @ingroup modm_board_blue_pill +/// @ingroup modm_board_mini_f411 namespace Board { using namespace modm::literals; @@ -58,6 +58,8 @@ struct SystemClock { static constexpr uint32_t Timer10 = Apb2Timer; static constexpr uint32_t Timer11 = Apb2Timer; + static constexpr uint32_t Usb = 48_MHz; + static bool inline enable() { @@ -66,6 +68,7 @@ struct SystemClock { .pllM = 25, // 25MHz / M=25 -> 1MHz .pllN = 336, // 1MHz * N=336 -> 336MHz .pllP = 4, // 336MHz / P=4 -> 84MHz = F_cpu + .pllQ = 7, // 336MHz / Q=7 -> 48MHz = F_usb }; Rcc::enablePll(Rcc::PllSource::ExternalCrystal, pllFactors); @@ -86,6 +89,15 @@ struct SystemClock { } }; +namespace usb +{ +using Id = GpioA10; +using Dm = GpioA11; +using Dp = GpioA12; + +using Device = UsbFs; +} + // User LED (inverted, because connected to 3V3) using LedGreen = GpioInverted< GpioOutputC13 >; using Leds = SoftwareGpioPort< LedGreen >; @@ -102,6 +114,19 @@ initialize() Button::setInput(Gpio::InputType::PullUp); } +inline void +initializeUsbFs() +{ + usb::Device::initialize(); + usb::Device::connect(); + usb::Id::configure(Gpio::InputType::PullUp); + + // explicitly disable VBUS sense (B device) + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSBSEN; + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSASEN; +} + } // Board namespace #endif // MODM_STM32_F4X1CEU_MINI_F4X1_HPP diff --git a/src/modm/board/mini_f411/module.lb b/src/modm/board/mini_f411/module.lb index ee16ff2471..93fbf7eb50 100644 --- a/src/modm/board/mini_f411/module.lb +++ b/src/modm/board/mini_f411/module.lb @@ -63,7 +63,8 @@ def prepare(module, options): ":architecture:clock", ":platform:clock", ":platform:core", - ":platform:gpio") + ":platform:gpio", + ":platform:usb:fs") return True def build(env): diff --git a/src/modm/board/nucleo_f429zi/board.hpp b/src/modm/board/nucleo_f429zi/board.hpp index dbf6ed0304..268f0bdb0e 100644 --- a/src/modm/board/nucleo_f429zi/board.hpp +++ b/src/modm/board/nucleo_f429zi/board.hpp @@ -26,10 +26,11 @@ namespace Board { using namespace modm::literals; -/// STM32F429 running at 180MHz from the external 8MHz STLink clock +/// STM32F429 running at 168MHz from the external 8MHz STLink clock struct SystemClock { - static constexpr uint32_t Frequency = 180_MHz; + static constexpr uint32_t Frequency = 168_MHz; + static constexpr uint32_t Ahb = Frequency; static constexpr uint32_t Apb1 = Frequency / 4; static constexpr uint32_t Apb2 = Frequency / 2; @@ -75,18 +76,19 @@ struct SystemClock static constexpr uint32_t Timer13 = Apb1Timer; static constexpr uint32_t Timer14 = Apb1Timer; + static constexpr uint32_t Usb = 48_MHz; + static bool inline enable() { Rcc::enableExternalClock(); // 8 MHz const Rcc::PllFactors pllFactors{ .pllM = 4, // 8MHz / M= 4 -> 2MHz - .pllN = 180, // 2MHz * N=180 -> 360MHz - .pllP = 2, // 360MHz / P= 2 -> 180MHz = F_cpu + .pllN = 168, // 2MHz * N=168 -> 336MHz + .pllP = 2, // 336MHz / P= 2 -> 168MHz = F_cpu + .pllQ = 7, // 336MHz / Q= 7 -> 48MHz = F_usb }; Rcc::enablePll(Rcc::PllSource::ExternalClock, pllFactors); - PWR->CR |= PWR_CR_ODEN; // Enable overdrive mode - while (not (PWR->CSR & PWR_CSR_ODRDY)) ; Rcc::setFlashLatency(); Rcc::enableSystemClock(Rcc::SystemClockSource::Pll); Rcc::setApb1Prescaler(Rcc::Apb1Prescaler::Div4); @@ -108,6 +110,19 @@ using LedBlue = GpioOutputB7; // LED2 [Blue] using LedRed = GpioOutputB14; // LED3 [Red] using Leds = SoftwareGpioPort< LedRed, LedBlue, LedGreen >; +namespace usb +{ +using Vbus = GpioA9; +using Id = GpioA10; +using Dm = GpioA11; +using Dp = GpioA12; + +using Overcurrent = GpioInputG7; // OTG_FS_OverCurrent +using Power = GpioOutputG6; // OTG_FS_PowerSwitchOn + +using Device = UsbFs; +} + namespace stlink { using Tx = GpioOutputD8; @@ -137,6 +152,19 @@ initialize() // Button::enableExternalInterruptVector(12); } +inline void +initializeUsbFs() +{ + usb::Device::initialize(); + usb::Device::connect(); + + usb::Overcurrent::setInput(); + usb::Vbus::setInput(); + // Enable VBUS sense (B device) via pin PA9 + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_VBUSBSEN; +} + } #endif // MODM_STM32_NUCLEO_F429ZI_HPP diff --git a/src/modm/board/nucleo_f429zi/module.lb b/src/modm/board/nucleo_f429zi/module.lb index 875ed25377..6d04de53a2 100644 --- a/src/modm/board/nucleo_f429zi/module.lb +++ b/src/modm/board/nucleo_f429zi/module.lb @@ -23,8 +23,15 @@ def prepare(module, options): if not options[":target"].partname.startswith("stm32f429zit"): return False - module.depends(":platform:core", ":platform:gpio", ":platform:clock", ":platform:uart:3", - ":debug", ":architecture:clock") + module.depends( + ":debug", + ":architecture:clock", + ":platform:core", + ":platform:gpio", + ":platform:clock", + ":platform:uart:3", + ":platform:usb:fs") + return True def build(env): diff --git a/src/modm/board/samd21_mini/board.hpp b/src/modm/board/samd21_mini/board.hpp index 456d46a29e..865a7b5bd1 100644 --- a/src/modm/board/samd21_mini/board.hpp +++ b/src/modm/board/samd21_mini/board.hpp @@ -25,12 +25,13 @@ using namespace modm::literals; struct SystemClock { - static constexpr uint32_t Frequency = 1_MHz; + static constexpr uint32_t Frequency = 48_MHz; // static constexpr uint32_t Ahb = Frequency; // static constexpr uint32_t Apba = Frequency; // static constexpr uint32_t Apbb = Frequency; // static constexpr uint32_t Apbc = Frequency; + static constexpr uint32_t Usb = 48_MHz; // static constexpr uint32_t Adc = Apb2; // static constexpr uint32_t SercomSlow = Apb2; @@ -51,23 +52,44 @@ struct SystemClock static bool inline enable() { - // GenericClockController::enableExternalCrystal(Frequency); - - // switch system clock to PLL output - // GenericClockController::enableSystemClock(ClockControl::SystemClockSource::Pll); - - // update frequencies for busy-wait delay functions - // GenericClockController::updateCoreFrequency(); - + GenericClockController::setFlashLatency(); + GenericClockController::initExternalCrystal(); + GenericClockController::initDFLL48MHz(); + GenericClockController::initOsc8MHz(); + GenericClockController::setSystemClock(ClockSource::DFLL48M); + GenericClockController::updateCoreFrequency(); return true; } }; +using ARef = GpioA03; +using A0 = GpioA02; +using A1 = GpioB08; +using A2 = GpioB09; +using A3 = GpioA04; +using A4 = GpioA05; +using A5 = GpioB02; + +using D0 = GpioA11; +using D1 = GpioA10; +using D2 = GpioA14; +using D3 = GpioA09; +using D4 = GpioA08; +using D5 = GpioA15; +using D6 = GpioA20; +using D7 = GpioA21; +using D8 = GpioA06; +using D9 = GpioA07; +using D10 = GpioA18; +using D11 = GpioA16; +using D12 = GpioA19; +using D13 = GpioA17; + // User LED (inverted, because connected to 3V3) using LedD13 = GpioInverted; using LedTx = GpioInverted; using LedRx = GpioInverted; -// using Leds = SoftwareGpioPort< LedRed >; +using Leds = SoftwareGpioPort< LedTx, LedRx >; using Button = GpioUnused; @@ -77,9 +99,14 @@ initialize() SystemClock::enable(); SysTickTimer::initialize(); - LedD13::setOutput(modm::Gpio::Low); - LedTx::setOutput(modm::Gpio::Low); - LedRx::setOutput(modm::Gpio::Low); + Leds::setOutput(modm::Gpio::Low); +} + +inline void +initializeUsbFs() +{ + modm::platform::Usb::initialize(); + modm::platform::Usb::connect(); } } // Board namespace diff --git a/src/modm/board/samd21_mini/module.lb b/src/modm/board/samd21_mini/module.lb index f6d243640c..c5ee204998 100644 --- a/src/modm/board/samd21_mini/module.lb +++ b/src/modm/board/samd21_mini/module.lb @@ -28,8 +28,11 @@ def prepare(module, options): if not options[":target"].partname.startswith("samd21g18a"): return False - module.depends(":platform:gclk", ":platform:core", ":platform:clock", - ":platform:gpio") + module.depends(":platform:gclk", + ":platform:core", + ":platform:clock", + ":platform:gpio", + ":platform:usb") return True def build(env): diff --git a/src/modm/platform/clock/sam/gclk.cpp.in b/src/modm/platform/clock/sam/gclk.cpp.in index afc8685926..a387b814cf 100644 --- a/src/modm/platform/clock/sam/gclk.cpp.in +++ b/src/modm/platform/clock/sam/gclk.cpp.in @@ -70,7 +70,6 @@ modm::platform::GenericClockController::initDFLL48MHz( GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(uint32_t(ClockMux::DFLL48M)) | GCLK_CLKCTRL_GEN(uint32_t(ClockGenerator::ExternalCrystal32K)) | - // GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_CLKEN; // Wait for synchronization. while (GCLK->STATUS.bit.SYNCBUSY and --waitCycles); @@ -83,7 +82,7 @@ modm::platform::GenericClockController::initDFLL48MHz( SYSCTRL->DFLLMUL.reg = SYSCTRL_DFLLMUL_CSTEP( 31 ) | SYSCTRL_DFLLMUL_FSTEP( 511 ) | - SYSCTRL_DFLLMUL_MUL((48_MHz + 32'768_Hz/2) / 32'768_Hz); + SYSCTRL_DFLLMUL_MUL(48_MHz / 32'768_Hz); // Wait for synchronization. while (!SYSCTRL->PCLKSR.bit.DFLLRDY and --waitCycles); // Write full configuration to DFLL control register diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 572cd60cf1..51b845bbbc 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -53,11 +53,7 @@ def build(env): properties["loops"] = loops # TODO: Move this data into the device files - properties["usbprescaler"] = ( - (target["family"] == "f0" and target["name"] in ["42", "48", "70", "72", "78"]) or - (target["family"] == "f1" and target["name"] in ["02", "03"]) or - (target["family"] == "f3" and (target["name"] in ["02", "73"] or - (target["name"] in ["03"] and target["size"] in ["b", "c", "d", "e"])))) + properties["usbprescaler"] = device.has_driver("usb") and target.family in ["f0", "f1", "f3"] properties["pllprediv"] = \ (target["family"] in ["f0", "f3"] or (target["family"] == "f1" and target["name"] in ["00", "05", "07"])) properties["pllprediv2"] = False # FIXME: not sure what value this should have @@ -78,7 +74,7 @@ def build(env): all_peripherals = set() all_drivers = [d for d in device._properties["driver"] if d["name"] not in ["gpio", "core"]] - translate = lambda s: "".join(p.capitalize() for p in s.split("_")) + translate = lambda s: s.replace("_", "").capitalize() for d in all_drivers: dname = translate(d["name"]) if "instance" in d: @@ -107,6 +103,11 @@ def build(env): nper = "DSI" if "Eth" in all_peripherals and per == "ETHMAC": per = "Eth" + # Fix USBOTG OTG + if "Usbotgfs" in all_peripherals and per.startswith("OTG"): + if per == "OTGH": per = "OTGHS"; + per = "USB"+per + # print(per, mode) if per.capitalize() not in all_peripherals: continue if "EN" in mode: diff --git a/src/modm/platform/clock/stm32/rcc.cpp.in b/src/modm/platform/clock/stm32/rcc.cpp.in index aa44bde649..9cb9d63c1c 100644 --- a/src/modm/platform/clock/stm32/rcc.cpp.in +++ b/src/modm/platform/clock/stm32/rcc.cpp.in @@ -146,7 +146,7 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u %% if target["family"] in ["f2", "f4", "f7"] // Read reserved values and clear all other values uint32_t tmp = RCC->PLLCFGR & ~(RCC_PLLCFGR_PLLSRC | RCC_PLLCFGR_PLLM - | RCC_PLLCFGR_PLLN | RCC_PLLCFGR_PLLP | RCC_PLLCFGR_PLLQ); + | RCC_PLLCFGR_PLLN | RCC_PLLCFGR_PLLP); // PLLSRC source for pll and for plli2s tmp |= static_cast(source); @@ -161,7 +161,10 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u tmp |= (((uint32_t) (pllFactors.pllP / 2) - 1) << RCC_PLLCFGR_PLLP_Pos) & RCC_PLLCFGR_PLLP; // PLLQ (24) divider for USB frequency; (0-15) - // tmp |= (((uint32_t) pllQ) << RCC_PLLCFGR_PLLQ_Pos) & RCC_PLLCFGR_PLLQ; + if (pllFactors.pllQ != 0xff) { + tmp &= ~RCC_PLLCFGR_PLLQ; + tmp |= (((uint32_t) pllFactors.pllQ) << RCC_PLLCFGR_PLLQ_Pos) & RCC_PLLCFGR_PLLQ; + } RCC->PLLCFGR = tmp; @@ -181,7 +184,6 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u %% if pll_p // RCC_PLLCFGR_PLLPEN | RCC_PLLCFGR_PLLP | %% endif - // RCC_PLLCFGR_PLLQEN | RCC_PLLCFGR_PLLQ | RCC_PLLCFGR_PLLREN | RCC_PLLCFGR_PLLR); // PLLSRC source for pll @@ -201,9 +203,12 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u %% else tmp |= ((uint32_t(pllFactors.pllR / 2) - 1) << RCC_PLLCFGR_PLLR_Pos) & RCC_PLLCFGR_PLLR; // PLLQ (21) divider for USB frequency; (00: PLLQ = 2, 01: PLLQ = 4, etc.) - // tmp |= (((uint32_t) (pllQ / 2) - 1) << RCC_PLLCFGR_PLLQ_Pos) & RCC_PLLCFGR_PLLQ; - // enable pll USB clock output - // tmp |= RCC_PLLCFGR_PLLQEN; + if (pllFactors.pllQ != 0xff) { + tmp &= ~RCC_PLLCFGR_PLLQ; + tmp |= (((uint32_t) (pllFactors.pllQ / 2) - 1) << RCC_PLLCFGR_PLLQ_Pos) & RCC_PLLCFGR_PLLQ; + // enable pll USB clock output + tmp |= RCC_PLLCFGR_PLLQEN; + } %% endif // enable pll CPU clock output @@ -245,7 +250,7 @@ modm::platform::Rcc::enablePll(PllSource source, const PllFactors& pllFactors, u uint32_t tmp = RCC->CFGR & ~(RCC_CFGR_USBPRE | {{pullmul}} | RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE); // Divide Output for USB Clock by 1.5? - // tmp |= static_cast(usb); + tmp |= static_cast(pllFactors.usbPrediv); %% else uint32_t tmp = RCC->CFGR & ~({{pullmul}} | RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE); %% endif diff --git a/src/modm/platform/clock/stm32/rcc.hpp.in b/src/modm/platform/clock/stm32/rcc.hpp.in index d5a7e86f33..0e912380cf 100644 --- a/src/modm/platform/clock/stm32/rcc.hpp.in +++ b/src/modm/platform/clock/stm32/rcc.hpp.in @@ -186,6 +186,14 @@ public: Div16 = RCC_CFGR_PPRE2_DIV16 }; %% endif +%% if usbprescaler + enum class + UsbPrescaler : uint32_t + { + Div1 = RCC_CFGR_USBPRE, ///< do not divide + Div1_5 = 0, ///< divide by 1.5 + }; +%% endif %% if target["family"] in ["f2", "f4", "f7"] enum class @@ -368,6 +376,7 @@ public: %% else const uint8_t pllP; %% endif + const uint8_t pllQ = 0xff; %% elif target["family"] in ["l1"] const PllMultiplier pllMul; const uint8_t pllDiv; @@ -379,6 +388,9 @@ public: %% elif pllprediv const uint8_t pllPrediv; %% endif + %% if usbprescaler + const UsbPrescaler usbPrediv = UsbPrescaler::Div1_5; + %% endif %% endif }; diff --git a/src/modm/platform/clock/stm32/rcc_impl.hpp.in b/src/modm/platform/clock/stm32/rcc_impl.hpp.in index 0d4192bf49..33eded4a0f 100644 --- a/src/modm/platform/clock/stm32/rcc_impl.hpp.in +++ b/src/modm/platform/clock/stm32/rcc_impl.hpp.in @@ -128,7 +128,8 @@ Rcc::enable() RCC->{{rcc_reset[st_per]}} |= RCC_{{rcc_reset[st_per]}}_{{st_per}}RST; __DSB(); RCC->{{rcc_reset[st_per]}} &= ~RCC_{{rcc_reset[st_per]}}_{{st_per}}RST;{% endif %}{% if peripheral == "Eth" %} __DSB(); RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}RXEN; __DSB(); - RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}TXEN;{% endif %} + RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}TXEN;{% elif peripheral == "Usbotghs" %} __DSB(); + RCC->{{bus}} |= RCC_{{bus}}_{{st_per}}ULPIEN;{% endif %} } %% endfor __DSB(); @@ -146,7 +147,8 @@ Rcc::disable() if constexpr (peripheral == Peripheral::{{ peripheral }}) RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}EN;{% if peripheral == "Eth" %} __DSB(); RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}RXEN; __DSB(); - RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}TXEN;{% endif %} + RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}TXEN;{% elif peripheral == "Usbotghs" %} __DSB(); + RCC->{{bus}} &= ~RCC_{{bus}}_{{st_per}}ULPIEN;{% endif %} %% endfor __DSB(); } diff --git a/src/modm/platform/core/stm32/startup_platform.c.in b/src/modm/platform/core/stm32/startup_platform.c.in index 21a5cbc54f..d721fb75d9 100644 --- a/src/modm/platform/core/stm32/startup_platform.c.in +++ b/src/modm/platform/core/stm32/startup_platform.c.in @@ -35,12 +35,7 @@ __modm_initialize_platform(void) RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; %% endif -%% if target.family == "f3" - %% if target["name"] in ["02"] or (target["name"] in ["03"] and target["size"] in ["c", "e"]) - // Remap USB Interrupts - SYSCFG->CFGR1 |= SYSCFG_CFGR1_USB_IT_RMP; - %% endif -%% elif target.family == "f4" +%% if target.family == "f4" // Only the more powerful F4 targets have CCM or Backup SRAM #ifdef RCC_AHB1ENR_CCMDATARAMEN // Enable power to backup domain diff --git a/src/modm/platform/gpio/sam/pin.hpp.in b/src/modm/platform/gpio/sam/pin.hpp.in index 81770a5e1c..47ae68d3c3 100644 --- a/src/modm/platform/gpio/sam/pin.hpp.in +++ b/src/modm/platform/gpio/sam/pin.hpp.in @@ -134,8 +134,7 @@ protected: instance == {{ extint.pin }}, "Gpio{{ port ~ pin }} only connects to ExtInt<{{ extint.pin }}>"); PORT->Group[uint32_t(port)].PINCFG[uint32_t(pin)].bit.PMUXEN = true; - PORT->Group[uint32_t(port)].PMUX[uint32_t(pin) >> 1].reg = - PORT_PMUX_{{ gpio.pmux }}_{{ extint.function | upper }}; + PORT->Group[uint32_t(port)].PMUX[uint32_t(pin) >> 1].bit.{{ gpio.pmux }} = PORT_PMUX_{{ gpio.pmux }}_{{ extint.function | upper }}_Val; } %% endfor }; @@ -160,8 +159,8 @@ struct Gpio{{ port ~ pin }}::{{ signal.name }} connect() { %% if signal.function - PORT->Group[uint32_t(Gpio::port)].PINCFG[uint32_t(Gpio::pin)].bit.PMUXEN = true; - PORT->Group[uint32_t(Gpio::port)].PMUX[uint32_t(Gpio::pin) >> 1].reg = PORT_PMUX_{{ gpio.pmux }}_{{ signal.function | upper }}; + PORT->Group[uint32_t(port)].PINCFG[uint32_t(pin)].bit.PMUXEN = true; + PORT->Group[uint32_t(port)].PMUX[uint32_t(pin) >> 1].bit.{{ gpio.pmux }} = PORT_PMUX_{{ gpio.pmux }}_{{ signal.function | upper }}_Val; %% endif } }; diff --git a/src/modm/platform/gpio/stm32/module.lb b/src/modm/platform/gpio/stm32/module.lb index 511d56d230..2e4814dcbe 100644 --- a/src/modm/platform/gpio/stm32/module.lb +++ b/src/modm/platform/gpio/stm32/module.lb @@ -27,10 +27,7 @@ def port_ranges(gpios): return ports def translate(s): - name = "" - for part in s.split("_"): - name += part.capitalize() - return name + return s.replace("_", "").capitalize() def get_driver(s): name = "None" diff --git a/src/modm/platform/usb/sam/module.lb b/src/modm/platform/usb/sam/module.lb new file mode 100644 index 0000000000..398033deb5 --- /dev/null +++ b/src/modm/platform/usb/sam/module.lb @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020, Niklas Hauser +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":platform:usb" + module.description = "Universal Serial Bus (USB)" + +def prepare(module, options): + device = options[":target"] + if not (device.has_driver("usb:sam*")): + return False + + module.depends( + ":architecture:interrupt", + ":cmsis:device", + ":platform:gpio", + ":platform:clock") + + return True + +def build(env): + env.outbasepath = "modm/src/modm/platform/usb" + env.copy("usb.hpp") + if env.has_module(":tinyusb:device:cdc"): + env.copy(repopath("ext/hathach/uart.hpp"), "uart.hpp") + diff --git a/src/modm/platform/usb/sam/usb.hpp b/src/modm/platform/usb/sam/usb.hpp new file mode 100644 index 0000000000..cadc08d422 --- /dev/null +++ b/src/modm/platform/usb/sam/usb.hpp @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2020, Erik Henriksson +* Copyright (c) 2020, Niklas Hauser +* +* This file is part of the modm project. +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include + +namespace modm::platform +{ + +class Usb +{ +public: + template< class SystemClock > + static void + initialize(uint8_t priority=3) + { + static_assert(SystemClock::Frequency == 48_MHz, "Usb must have a 48MHz clock!"); + PM->APBBMASK.reg |= PM_APBBMASK_USB; + PM->AHBMASK.reg |= PM_AHBMASK_USB; + GenericClockController::connect(ClockGenerator::System); + NVIC_SetPriority(USB_IRQn, priority); + } + + template< template class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Dp = typename Connector::template GetSignal< Gpio::Signal::Dp >; + using Dm = typename Connector::template GetSignal< Gpio::Signal::Dm >; + static_assert(((Connector::template IsValid and Connector::template IsValid) and sizeof...(Signals) == 2), + "Usb::connect() requires one Dp and one Dm signal!"); + + Connector::connect(); + } +}; + +} // namespace modm::platform diff --git a/src/modm/platform/usb/stm32/module.lb b/src/modm/platform/usb/stm32/module.lb new file mode 100644 index 0000000000..5cecd2517a --- /dev/null +++ b/src/modm/platform/usb/stm32/module.lb @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020, Niklas Hauser +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +from collections import defaultdict + +def common_usb_irqs(env): + """ + Filters the USB interrupts by port: + + - `usb_irqs`: All unfiltered USB interrupts + - `port_irqs`: Filtered USB interrupts categorized by port: `fs` or `hs`. + - `is_remap`: True when `port_irqs` contains remapped IRQs (specific to STM32F3) + + Interrupts used by USB FS: + + - OTG_FS (no suffix) + - USB + - USBWakeUp (no suffix) + - USB_LP* (any suffix) + - USB_HP* (any suffix) + + Interrupts used by USB HS: + + - OTG_HS (no suffix) + + :returns: a dictionary of USB interrupt properties + """ + usb_vectors = {v["name"] for v in env[":target"].get_driver("core")["vector"] + if any(v["name"].startswith(n) for n in ["USB", "OTG"])} + is_remap = any("_RMP" in v for v in usb_vectors) + + port_irqs = defaultdict(list) + port_irqs_remap = defaultdict(list) + for vector in usb_vectors: + port = "hs" if "_HS" in vector else "fs" + if is_remap and vector in ["USBWakeUp_RMP", "USB_LP", "USB_HP"]: + port_irqs_remap[port].append(vector) + else: + port_irqs[port].append(vector) + + port_irqs = {k:v for k,v in port_irqs.items()} + irqs = { + "usb_irqs": usb_vectors, + "is_remap": is_remap, + "port_irqs": port_irqs_remap if is_remap else port_irqs, + } + return irqs + +def generate_instance(env, port, otg=False): + env.outbasepath = "modm/src/modm/platform/usb" + irq_data = env.query(":platform:usb:irqs") + env.substitutions = { + "port": port, + "peripheral": "Usbotg{}".format(port) if otg else "Usb", + "is_otg": otg, + "is_remap": irq_data["is_remap"], + "irqs": irq_data["port_irqs"][port], + } + if otg: + env.template("usb.hpp.in", "usb_{}.hpp".format(port)) + else: + env.template("usb.hpp.in") + + +# ----------------------------------------------------------------------------- +def init(module): + module.name = ":platform:usb" + module.description = "Universal Serial Bus (USB)" + +def prepare(module, options): + device = options[":target"] + if not (device.has_driver("usb:stm32*") or + device.has_driver("usb_otg_fs:stm32*") or + device.has_driver("usb_otg_hs:stm32*")): + return False + + if device.has_driver("usb_otg_fs"): + module.add_submodule(UsbInstance("fs")) + if device.has_driver("usb_otg_hs"): + module.add_submodule(UsbInstance("hs")) + + module.depends( + ":architecture:interrupt", + ":cmsis:device", + ":platform:gpio", + ":platform:rcc") + + module.add_query(EnvironmentQuery(name="irqs", factory=common_usb_irqs)) + + return True + +def build(env): + env.outbasepath = "modm/src/modm/platform/usb" + if not (env[":target"].has_driver("usb_otg_fs") or + env[":target"].has_driver("usb_otg_hs")): + generate_instance(env, "fs") + if env.has_module(":tinyusb:device:cdc"): + env.copy(repopath("ext/hathach/uart.hpp"), "uart.hpp") + + +# ----------------------------------------------------------------------------- +class UsbInstance(Module): + def __init__(self, speed): + self.speed = speed + + def init(self, module): + module.name = self.speed + module.description = "{} Speed".format("Full" if self.speed == "fs" else "High") + + def prepare(self, module, options): + return True + + def build(self, env): + generate_instance(env, port=self.speed, otg=True) diff --git a/src/modm/platform/usb/stm32/usb.hpp.in b/src/modm/platform/usb/stm32/usb.hpp.in new file mode 100644 index 0000000000..9b88317918 --- /dev/null +++ b/src/modm/platform/usb/stm32/usb.hpp.in @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2020, Erik Henriksson +* Copyright (c) 2020, Niklas Hauser +* +* This file is part of the modm project. +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include + +%% set name = "Usb" ~ port | capitalize + +namespace modm::platform +{ + +class {{ name }} +{ +public: + template< class SystemClock > + static void + initialize(uint8_t priority=3) + { + static_assert(SystemClock::Usb == 48_MHz, "{{ name }} must have a 48MHz clock!"); + Rcc::enable(); +%% if is_remap + SYSCFG->CFGR1 |= SYSCFG_CFGR1_USB_IT_RMP; +%% endif +%% for irq in irqs | sort + NVIC_SetPriority({{ irq }}_IRQn, priority); +%% endfor + } + + template< template class... Signals > + static void + connect() + { + using Connector = GpioConnector; +%% if port == "fs" + using Dp = typename Connector::template GetSignal< Gpio::Signal::Dp >; + using Dm = typename Connector::template GetSignal< Gpio::Signal::Dm >; +%% if is_otg + using Id = typename Connector::template GetSignal< Gpio::Signal::Id >; +%% endif + static_assert(((Connector::template IsValid and Connector::template IsValid) and sizeof...(Signals) >= 2), + "{{ name }}::connect() requires at least one Dp and one Dm signal!"); + +%% if is_otg + Id::configure(Gpio::OutputType::OpenDrain, Gpio::OutputSpeed::High); +%% endif +%% endif + Connector::connect(); + } +}; + +} // namespace modm::platform diff --git a/test/modm/platform/gpio/module.lb b/test/modm/platform/gpio/module.lb index da6d1b4b6b..823beebc61 100644 --- a/test/modm/platform/gpio/module.lb +++ b/test/modm/platform/gpio/module.lb @@ -55,10 +55,7 @@ test_io_al_avrev_can = sorted([ ]) def translate(s): - name = "" - for part in s.split("_"): - name += part.capitalize() - return name + return s.replace("_", "").capitalize() def get_driver(s): name = "None" diff --git a/tools/build_script_generator/common.py b/tools/build_script_generator/common.py index 764b26c42d..b6b45e9bd5 100644 --- a/tools/build_script_generator/common.py +++ b/tools/build_script_generator/common.py @@ -206,6 +206,7 @@ def common_compiler_flags(compiler, target): "-Wlogical-op", "-Wpointer-arith", "-Wundef", + "-Wno-redundant-decls", # "-Wcast-align", # "-Wcast-qual", # "-Wmissing-declarations", diff --git a/tools/build_script_generator/scons/site_tools/dfu.py b/tools/build_script_generator/scons/site_tools/dfu.py index 39c122ddbd..497438c260 100644 --- a/tools/build_script_generator/scons/site_tools/dfu.py +++ b/tools/build_script_generator/scons/site_tools/dfu.py @@ -26,9 +26,7 @@ # ----------------------------------------------------------------------------- def dfu_stm32_programmer_flash(env, source, alias='dfu_stm32_program'): - # dfu-util -v -d 0483:df11 -i 0 -a 0 -s 0x08000000 -D build/iobox-stm32/iobox-stm32.bin - # 0483:df11 is for STM32 F4 only - actionString = '$DFU_STM32_PROGRAMMER -v -d 0483:df11 -i 0 -a 0 -s 0x08000000:leave -D $SOURCE' + actionString = '$DFU_STM32_PROGRAMMER -v -E2 -R -i 0 -a 0 -s 0x08000000:leave -D $SOURCE' action = Action(actionString, cmdstr = "$DFU_STM32_PROGRAMMER_COMSTR") return env.AlwaysBuild(env.Alias(alias, source, action)) @@ -36,8 +34,11 @@ def dfu_stm32_programmer_flash(env, source, alias='dfu_stm32_program'): # ----------------------------------------------------------------------------- def generate(env, **kw): # build messages - if ARGUMENTS.get('verbose') != '1': - env['DFU_STM32_PROGRAMMER_COMSTR'] = "dfu_stm32_programmer: program $SOURCE" + if not ARGUMENTS.get('verbose'): + env['DFU_STM32_PROGRAMMER_COMSTR'] = \ + "{0}.-------------- {1}$SOURCE\n" \ + "{0}'-----DFU-----> {2}$CONFIG_DEVICE_NAME{3}" \ + .format("\033[;0;32m", "\033[;0;33m", "\033[;1;33m", "\033[;0;0m") # Name of the binary program to run env['DFU_STM32_PROGRAMMER'] = 'dfu-util' diff --git a/tools/modm_tools/bossac.py b/tools/modm_tools/bossac.py index 2b22e67a11..aa4115b37b 100644 --- a/tools/modm_tools/bossac.py +++ b/tools/modm_tools/bossac.py @@ -34,7 +34,7 @@ # ----------------------------------------------------------------------------- def program(source, offset=None, port=None, erase=False, options=None): - command = ["bossac", "-b", "-R"] + command = ["bossac", "-b", "-R", "-a"] # Attempt to find a serial port automatically if port == "auto": diff --git a/tools/modm_tools/utils.py b/tools/modm_tools/utils.py index cc69c6de20..93bfd5576d 100644 --- a/tools/modm_tools/utils.py +++ b/tools/modm_tools/utils.py @@ -42,7 +42,10 @@ def guess_serial_port(port_hint=None): if "Windows" in platform.platform(): ports = glob.glob('COM[0-9]*') elif "Darwin" in platform.system(): - ports = glob.glob('/dev/tty.usb*') + if port_hint == "bossac": + ports = glob.glob('/dev/tty.usbmodem*') + else: + ports = glob.glob('/dev/tty.usb*') else: ports = glob.glob('/dev/tty[A-Za-z]*') return next(iter(ports), None) diff --git a/tools/scripts/generate_module_docs.py b/tools/scripts/generate_module_docs.py index dd8e7972c5..0712d06521 100755 --- a/tools/scripts/generate_module_docs.py +++ b/tools/scripts/generate_module_docs.py @@ -41,12 +41,12 @@ def get_modules(builder, limit=None): targets = [] for d in raw_targets: if d.startswith("stm32"): - # filter out temperature key - sd = d[:12] + d[13:] + # filter out a few keys + sd = d[:10] + d[13:] if sd not in short_targets: targets.append(d) short_targets.add(sd) - elif d.startswith("at"): + elif d.startswith("at") or d.startswith("sam"): # filter out package sd = d.split("-")[0] if sd not in short_targets: @@ -110,10 +110,14 @@ def get_modules(builder, limit=None): if o._dependency_handler: for valin in o.values: - valout = o._dependency_handler(o._in(valin)) - if valout: + valout = lbuild.utils.listify(o._dependency_handler(o._in(valin))) + if len(valout): opdep = op.addChild("dependency") - opdep.setValue("{} -> {}".format(valin, ("modm" + valout).replace("modmmodm", "modm"))) + vconcat = ", ".join(("modm" + v).replace("modmmodm", "modm") for v in valout) + if len(valout) == 1: + opdep.setValue("{} -> {}".format(valin, vconcat)) + else: + opdep.setValue("{} -> [{}]".format(valin, vconcat)) for c in init._collectors: cp = m.addChild(c.fullname.split(":")[-1]) @@ -268,6 +272,8 @@ def format_module(modules, node): mprops["graph"] = render_dependency_graphs(mprops) modules[fullname].append(mprops) + print(".", end ="", flush=True) + for child in [c for c in node.children if "filename" in c]: format_module(modules, child) @@ -285,10 +291,12 @@ def format_module(modules, node): f"Between {min(mlens[0])} and {max(mlens[0])} modules per target.\n" f"Up to {max(mlens[1])} options per module.") + print("Formatting modules", end ="") modules = defaultdict(list) for c in module_tree.children: format_module(modules, c) + print("\nWriting modules", end ="") env = Environment() env.line_statement_prefix = '%%' module_path = repopath("docs/src/reference/module/") @@ -306,7 +314,9 @@ def format_module(modules, node): rendered = env.from_string(repopath("docs/module.md.in").read_text()).render({"module": m}) (module_path / "{}.md".format(url)).write_text(rendered) modtable.append(" - \"{}\": \"reference/module/{}.md\"".format(nname.replace("modm:", ":"), url)) + print(".", end ="", flush=True) + print("\nWriting module table") config_path = Path(repopath("docs/mkdocs.yml")) modtable = "\n".join(sorted(modtable)) config = config_path.read_text()