diff options
author | Clyne Sullivan <clyne@bitgloo.com> | 2023-08-08 23:10:02 -0400 |
---|---|---|
committer | Clyne Sullivan <clyne@bitgloo.com> | 2023-08-08 23:10:02 -0400 |
commit | f440728644ad3698ffd6af1abcfcc07aad5793c3 (patch) | |
tree | 68aff014ff17933717616f2f8d407b51611afe2b /firmware/source/periph |
initial commit
* combine all source files into this monorepo
* convert all third-party source packages into submodules
* small fixes due to changes in latest third-part packages
Diffstat (limited to 'firmware/source/periph')
-rw-r--r-- | firmware/source/periph/adc.cpp | 245 | ||||
-rw-r--r-- | firmware/source/periph/adc.hpp | 53 | ||||
-rw-r--r-- | firmware/source/periph/cordic.cpp | 113 | ||||
-rw-r--r-- | firmware/source/periph/cordic.hpp | 25 | ||||
-rw-r--r-- | firmware/source/periph/dac.cpp | 85 | ||||
-rw-r--r-- | firmware/source/periph/dac.hpp | 37 | ||||
-rw-r--r-- | firmware/source/periph/usbcfg.c | 346 | ||||
-rw-r--r-- | firmware/source/periph/usbcfg.h | 28 | ||||
-rw-r--r-- | firmware/source/periph/usbserial.cpp | 52 | ||||
-rw-r--r-- | firmware/source/periph/usbserial.hpp | 32 |
10 files changed, 1016 insertions, 0 deletions
diff --git a/firmware/source/periph/adc.cpp b/firmware/source/periph/adc.cpp new file mode 100644 index 0000000..4667307 --- /dev/null +++ b/firmware/source/periph/adc.cpp @@ -0,0 +1,245 @@ +/** + * @file adc.cpp + * @brief Manages signal reading through the ADC. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see <https://www.gnu.org/licenses/>. + */ + +#include "adc.hpp" + +#if defined(TARGET_PLATFORM_L4) +ADCDriver *ADC::m_driver = &ADCD1; +ADCDriver *ADC::m_driver2 = &ADCD3; +#else +ADCDriver *ADC::m_driver = &ADCD3; +//ADCDriver *ADC::m_driver2 = &ADCD1; // TODO +#endif + +const ADCConfig ADC::m_config = { + .difsel = 0, +#if defined(TARGET_PLATFORM_H7) + .calibration = 0, +#endif +}; + +const ADCConfig ADC::m_config2 = { + .difsel = 0, +#if defined(TARGET_PLATFORM_H7) + .calibration = 0, +#endif +}; + +ADCConversionGroup ADC::m_group_config = { + .circular = true, + .num_channels = 1, + .end_cb = ADC::conversionCallback, + .error_cb = nullptr, + .cfgr = ADC_CFGR_EXTEN_RISING | ADC_CFGR_EXTSEL_SRC(13), /* TIM6_TRGO */ + .cfgr2 = 0,//ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x +#if defined(TARGET_PLATFORM_H7) + .ccr = 0, + .pcsel = 0, + .ltr1 = 0, .htr1 = 4095, + .ltr2 = 0, .htr2 = 4095, + .ltr3 = 0, .htr3 = 4095, +#else + .tr1 = ADC_TR(0, 4095), + .tr2 = ADC_TR(0, 4095), + .tr3 = ADC_TR(0, 4095), + .awd2cr = 0, + .awd3cr = 0, +#endif + .smpr = { + ADC_SMPR1_SMP_AN5(ADC_SMPR_SMP_12P5), 0 + }, + .sqr = { + ADC_SQR1_SQ1_N(ADC_CHANNEL_IN5), + 0, 0, 0 + }, +}; + +static bool readAltDone = false; +static void readAltCallback(ADCDriver *) +{ + readAltDone = true; +} +ADCConversionGroup ADC::m_group_config2 = { + .circular = false, + .num_channels = 2, + .end_cb = readAltCallback, + .error_cb = nullptr, + .cfgr = ADC_CFGR_EXTEN_RISING | ADC_CFGR_EXTSEL_SRC(13), /* TIM6_TRGO */ + .cfgr2 = 0,//ADC_CFGR2_ROVSE | ADC_CFGR2_OVSR_1 | ADC_CFGR2_OVSS_0, // Oversampling 2x +#if defined(TARGET_PLATFORM_H7) + .ccr = 0, + .pcsel = 0, + .ltr1 = 0, .htr1 = 4095, + .ltr2 = 0, .htr2 = 4095, + .ltr3 = 0, .htr3 = 4095, +#else + .tr1 = ADC_TR(0, 4095), + .tr2 = ADC_TR(0, 4095), + .tr3 = ADC_TR(0, 4095), + .awd2cr = 0, + .awd3cr = 0, +#endif + .smpr = { + ADC_SMPR1_SMP_AN1(ADC_SMPR_SMP_2P5) | ADC_SMPR1_SMP_AN2(ADC_SMPR_SMP_2P5), 0 + }, + .sqr = { + ADC_SQR1_SQ1_N(ADC_CHANNEL_IN1) | ADC_SQR1_SQ2_N(ADC_CHANNEL_IN2), + 0, 0, 0 + }, +}; + +adcsample_t *ADC::m_current_buffer = nullptr; +size_t ADC::m_current_buffer_size = 0; +ADC::Operation ADC::m_operation = nullptr; + +void ADC::begin() +{ +#if defined(TARGET_PLATFORM_H7) + palSetPadMode(GPIOF, 3, PAL_MODE_INPUT_ANALOG); +#else + palSetPadMode(GPIOA, 0, PAL_MODE_INPUT_ANALOG); // Algorithm in + palSetPadMode(GPIOC, 0, PAL_MODE_INPUT_ANALOG); // Potentiometer 1 + palSetPadMode(GPIOC, 1, PAL_MODE_INPUT_ANALOG); // Potentiometer 2 +#endif + + adcStart(m_driver, &m_config); + adcStart(m_driver2, &m_config2); +} + +void ADC::start(adcsample_t *buffer, size_t count, Operation operation) +{ + m_current_buffer = buffer; + m_current_buffer_size = count; + m_operation = operation; + + adcStartConversion(m_driver, &m_group_config, buffer, count); + SClock::start(); +} + +void ADC::stop() +{ + SClock::stop(); + adcStopConversion(m_driver); + + m_current_buffer = nullptr; + m_current_buffer_size = 0; + m_operation = nullptr; +} + +adcsample_t ADC::readAlt(unsigned int id) +{ + if (id > 1) + return 0; + static adcsample_t result[16] = {}; + readAltDone = false; + adcStartConversion(m_driver2, &m_group_config2, result, 8); + while (!readAltDone); + //__WFI(); + adcStopConversion(m_driver2); + return result[id]; +} + +void ADC::setRate(SClock::Rate rate) +{ +#if defined(TARGET_PLATFORM_H7) + std::array<std::array<uint32_t, 2>, 6> m_rate_presets = {{ + // Rate PLL N PLL P + {/* 8k */ 80, 20}, + {/* 16k */ 80, 10}, + {/* 20k */ 80, 8}, + {/* 32k */ 80, 5}, + {/* 48k */ 96, 4}, + {/* 96k */ 288, 10} + }}; + + auto& preset = m_rate_presets[static_cast<unsigned int>(rate)]; + auto pllbits = (preset[0] << RCC_PLL2DIVR_N2_Pos) | + (preset[1] << RCC_PLL2DIVR_P2_Pos); + + adcStop(m_driver); + + // Adjust PLL2 + RCC->CR &= ~(RCC_CR_PLL2ON); + while ((RCC->CR & RCC_CR_PLL2RDY) == RCC_CR_PLL2RDY); + auto pll2divr = RCC->PLL2DIVR & + ~(RCC_PLL2DIVR_N2_Msk | RCC_PLL2DIVR_P2_Msk); + pll2divr |= pllbits; + RCC->PLL2DIVR = pll2divr; + RCC->CR |= RCC_CR_PLL2ON; + while ((RCC->CR & RCC_CR_PLL2RDY) != RCC_CR_PLL2RDY); + + m_group_config.smpr[0] = rate != SClock::Rate::R96K ? ADC_SMPR1_SMP_AN5(ADC_SMPR_SMP_12P5) + : ADC_SMPR1_SMP_AN5(ADC_SMPR_SMP_2P5); + + adcStart(m_driver, &m_config); +#elif defined(TARGET_PLATFORM_L4) + std::array<std::array<uint32_t, 3>, 6> m_rate_presets = {{ + // PLLSAI2 sources MSI of 4MHz, divided by PLLM of /1 = 4MHz. + // 4MHz is then multiplied by PLLSAI2N (x8 to x86), with result + // between 64 and 344 MHz. + // + // SAI2N MUST BE AT LEAST 16 TO MAKE 64MHz MINIMUM. + // + // That is then divided by PLLSAI2R: + // R of 0 = /2; 1 = /4, 2 = /6, 3 = /8. + // PLLSAI2 then feeds into the ADC, which has a prescaler of /10. + // Finally, the ADC's SMP value produces the desired sample rate. + // + // 4MHz * N / R / 10 / SMP = sample rate. + // + // With oversampling, must create faster clock + // (x2 oversampling requires x2 sample rate clock). + // + // Rate PLLSAI2N R SMPR + {/* 8k */ 16, 1, ADC_SMPR_SMP_12P5}, // R3=32k (min), R1=64k + {/* 16k */ 16, 0, ADC_SMPR_SMP_12P5}, + {/* 20k */ 20, 0, ADC_SMPR_SMP_12P5}, + {/* 32k */ 32, 0, ADC_SMPR_SMP_12P5}, + {/* 48k */ 48, 0, ADC_SMPR_SMP_12P5}, + {/* 96k */ 73, 0, ADC_SMPR_SMP_6P5} // Technically 96.05263kS/s + }}; + + auto& preset = m_rate_presets[static_cast<int>(rate)]; + auto pllnr = (preset[0] << RCC_PLLSAI2CFGR_PLLSAI2N_Pos) | + (preset[1] << RCC_PLLSAI2CFGR_PLLSAI2R_Pos); + auto smpr = preset[2]; + + // Adjust PLLSAI2 + RCC->CR &= ~(RCC_CR_PLLSAI2ON); + while ((RCC->CR & RCC_CR_PLLSAI2RDY) == RCC_CR_PLLSAI2RDY); + RCC->PLLSAI2CFGR = (RCC->PLLSAI2CFGR & ~(RCC_PLLSAI2CFGR_PLLSAI2N_Msk | RCC_PLLSAI2CFGR_PLLSAI2R_Msk)) | pllnr; + RCC->CR |= RCC_CR_PLLSAI2ON; + while ((RCC->CR & RCC_CR_PLLSAI2RDY) != RCC_CR_PLLSAI2RDY); + + m_group_config.smpr[0] = ADC_SMPR1_SMP_AN5(smpr); + + // 8x oversample + m_group_config.cfgr2 = ADC_CFGR2_ROVSE | (2 << ADC_CFGR2_OVSR_Pos) | (3 << ADC_CFGR2_OVSS_Pos); + m_group_config2.cfgr2 = ADC_CFGR2_ROVSE | (2 << ADC_CFGR2_OVSR_Pos) | (3 << ADC_CFGR2_OVSS_Pos); +#endif +} + +void ADC::setOperation(ADC::Operation operation) +{ + m_operation = operation; +} + +void ADC::conversionCallback(ADCDriver *driver) +{ + if (m_operation != nullptr) { + auto half_size = m_current_buffer_size / 2; + if (adcIsBufferComplete(driver)) + m_operation(m_current_buffer + half_size, half_size); + else + m_operation(m_current_buffer, half_size); + } +} + diff --git a/firmware/source/periph/adc.hpp b/firmware/source/periph/adc.hpp new file mode 100644 index 0000000..5f7fa08 --- /dev/null +++ b/firmware/source/periph/adc.hpp @@ -0,0 +1,53 @@ +/** + * @file adc.hpp + * @brief Manages signal reading through the ADC. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef STMDSP_ADC_HPP_ +#define STMDSP_ADC_HPP_ + +#include "hal.h" +#include "sclock.hpp" + +#include <array> + +class ADC +{ +public: + using Operation = void (*)(adcsample_t *buffer, size_t count); + + static void begin(); + + static void start(adcsample_t *buffer, size_t count, Operation operation); + static void stop(); + + static adcsample_t readAlt(unsigned int id); + + static void setRate(SClock::Rate rate); + static void setOperation(Operation operation); + +private: + static ADCDriver *m_driver; + static ADCDriver *m_driver2; + + static const ADCConfig m_config; + static const ADCConfig m_config2; + static ADCConversionGroup m_group_config; + static ADCConversionGroup m_group_config2; + + static adcsample_t *m_current_buffer; + static size_t m_current_buffer_size; + static Operation m_operation; + +public: + static void conversionCallback(ADCDriver *); +}; + +#endif // STMDSP_ADC_HPP_ + diff --git a/firmware/source/periph/cordic.cpp b/firmware/source/periph/cordic.cpp new file mode 100644 index 0000000..29ee068 --- /dev/null +++ b/firmware/source/periph/cordic.cpp @@ -0,0 +1,113 @@ +#include "cordic.hpp" +#include "hal.h" + +#if !defined(TARGET_PLATFORM_L4) +namespace cordic { + +void init() +{ + RCC->AHB2ENR |= RCC_AHB2ENR_CORDICEN; +} + +static void prepare() { + while (CORDIC->CSR & CORDIC_CSR_RRDY) + asm("mov r0, %0" :: "r" (CORDIC->RDATA)); +} + +static uint32_t dtoq(double) { + uint32_t res; + asm("vcvt.s32.f64 d0, d0, #31;" + "vmov %0, r5, d0" + : "=r" (res)); + return res; +} +__attribute__((naked)) +static double qtod(uint32_t) { + asm("eor r1, r1;" + "vmov d0, r0, r1;" + "vcvt.f64.s32 d0, d0, #31;" + "bx lr"); + return 0; +} +__attribute__((naked)) +double mod(double, double) { + asm("vdiv.f64 d2, d0, d1;" + "vrintz.f64 d2;" + "vmul.f64 d1, d1, d2;" + "vsub.f64 d0, d0, d1;" + "bx lr"); + return 0; +} + +double cos(double x) { + x = mod(x, 2 * math::PI) / math::PI; + auto input = dtoq(x > 1. ? x - 2 : x); + + prepare(); + CORDIC->CSR = CORDIC_CSR_NARGS | CORDIC_CSR_NRES | + (6 << CORDIC_CSR_PRECISION_Pos) | + (0 << CORDIC_CSR_FUNC_Pos); + + CORDIC->WDATA = input; + CORDIC->WDATA = input; + while (!(CORDIC->CSR & CORDIC_CSR_RRDY)); + + double cosx = qtod(CORDIC->RDATA) / x; + [[maybe_unused]] auto sinx = CORDIC->RDATA; + return cosx; +} + +double sin(double x) { + x = mod(x, 2 * math::PI) / math::PI; + auto input = dtoq(x > 1. ? x - 2 : x); + + prepare(); + CORDIC->CSR = CORDIC_CSR_NARGS | CORDIC_CSR_NRES | + (6 << CORDIC_CSR_PRECISION_Pos) | + (1 << CORDIC_CSR_FUNC_Pos); + + CORDIC->WDATA = input; + CORDIC->WDATA = input; + while (!(CORDIC->CSR & CORDIC_CSR_RRDY)); + + double sinx = qtod(CORDIC->RDATA) / x; + [[maybe_unused]] auto cosx = CORDIC->RDATA; + return sinx; +} + +double tan(double x) { + x = mod(x, 2 * math::PI) / math::PI; + auto input = dtoq(x > 1. ? x - 2 : x); + + prepare(); + CORDIC->CSR = CORDIC_CSR_NARGS | CORDIC_CSR_NRES | + (6 << CORDIC_CSR_PRECISION_Pos) | + (1 << CORDIC_CSR_FUNC_Pos); + + CORDIC->WDATA = input; + CORDIC->WDATA = input; + while (!(CORDIC->CSR & CORDIC_CSR_RRDY)); + + double sinx = qtod(CORDIC->RDATA) / x; + double tanx = sinx * x / qtod(CORDIC->RDATA); + return tanx; +} + +} +#else // L4 +#include <cmath> +namespace cordic { + +void init() {} + +float mod(float a, float b) { + return a - (b * std::floor(a / b)); +} + +float cos(float x) { return std::cos(x); } +float sin(float x) { return std::sin(x); } +float tan(float x) { return std::tan(x); } + +} +#endif + diff --git a/firmware/source/periph/cordic.hpp b/firmware/source/periph/cordic.hpp new file mode 100644 index 0000000..5d640cc --- /dev/null +++ b/firmware/source/periph/cordic.hpp @@ -0,0 +1,25 @@ +#ifndef CORDIC_HPP_ +#define CORDIC_HPP_ + +namespace cordic { + constexpr double PI = 3.1415926535L; + + void init(); + +#if !defined(TARGET_PLATFORM_L4) + double mod(double n, double d); + + double cos(double x); + double sin(double x); + double tan(double x); +#else + float mod(float n, float d); + + float cos(float x); + float sin(float x); + float tan(float x); +#endif +} + +#endif // CORDIC_HPP_ + diff --git a/firmware/source/periph/dac.cpp b/firmware/source/periph/dac.cpp new file mode 100644 index 0000000..35c2908 --- /dev/null +++ b/firmware/source/periph/dac.cpp @@ -0,0 +1,85 @@ +/** + * @file dac.cpp + * @brief Manages signal creation using the DAC. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see <https://www.gnu.org/licenses/>. + */ + +#include "dac.hpp" +#include "sclock.hpp" + +DACDriver *DAC::m_driver[2] = { + &DACD1, &DACD2 +}; + +const DACConfig DAC::m_config = { + .init = 2048, + .datamode = DAC_DHRM_12BIT_RIGHT, + .cr = 0 +}; + +static int dacIsDone = -1; +static void dacEndCallback(DACDriver *dacd) +{ + if (dacd == &DACD2) + dacIsDone = dacIsBufferComplete(dacd) ? 1 : 0; +} + +const DACConversionGroup DAC::m_group_config = { + .num_channels = 1, + .end_cb = dacEndCallback, + .error_cb = nullptr, +#if defined(TARGET_PLATFORM_H7) + .trigger = 5 // TIM6_TRGO +#elif defined(TARGET_PLATFORM_L4) + .trigger = 0 // TIM6_TRGO +#endif +}; + +void DAC::begin() +{ + palSetPadMode(GPIOA, 4, PAL_STM32_MODE_ANALOG); + palSetPadMode(GPIOA, 5, PAL_STM32_MODE_ANALOG); + + dacStart(m_driver[0], &m_config); + dacStart(m_driver[1], &m_config); +} + +void DAC::start(int channel, dacsample_t *buffer, size_t count) +{ + if (channel >= 0 && channel < 2) { + if (channel == 1) + dacIsDone = -1; + dacStartConversion(m_driver[channel], &m_group_config, buffer, count); + SClock::start(); + } +} + +int DAC::sigGenWantsMore() +{ + if (dacIsDone != -1) { + int tmp = dacIsDone; + dacIsDone = -1; + return tmp; + } else { + return -1; + } +} + +int DAC::isSigGenRunning() +{ + return m_driver[1]->state == DAC_ACTIVE; +} + +void DAC::stop(int channel) +{ + if (channel >= 0 && channel < 2) { + dacStopConversion(m_driver[channel]); + SClock::stop(); + } +} + diff --git a/firmware/source/periph/dac.hpp b/firmware/source/periph/dac.hpp new file mode 100644 index 0000000..7250a52 --- /dev/null +++ b/firmware/source/periph/dac.hpp @@ -0,0 +1,37 @@ +/** + * @file dac.hpp + * @brief Manages signal creation using the DAC. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef STMDSP_DAC_HPP_ +#define STMDSP_DAC_HPP_ + +#include "hal.h" +#undef DAC + +class DAC +{ +public: + static void begin(); + + static void start(int channel, dacsample_t *buffer, size_t count); + static void stop(int channel); + + static int sigGenWantsMore(); + static int isSigGenRunning(); + +private: + static DACDriver *m_driver[2]; + + static const DACConfig m_config; + static const DACConversionGroup m_group_config; +}; + +#endif // STMDSP_DAC_HPP_ + diff --git a/firmware/source/periph/usbcfg.c b/firmware/source/periph/usbcfg.c new file mode 100644 index 0000000..b726e23 --- /dev/null +++ b/firmware/source/periph/usbcfg.c @@ -0,0 +1,346 @@ +/*
+ ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include "hal.h"
+
+/* Virtual serial port over USB.*/
+SerialUSBDriver SDU1;
+
+/*
+ * Endpoints to be used for USBD1.
+ */
+#define USBD1_DATA_REQUEST_EP 1
+#define USBD1_DATA_AVAILABLE_EP 1
+#define USBD1_INTERRUPT_REQUEST_EP 2
+
+/*
+ * USB Device Descriptor.
+ */
+static const uint8_t vcom_device_descriptor_data[18] = {
+ USB_DESC_DEVICE (0x0110, /* bcdUSB (1.1). */
+ 0x02, /* bDeviceClass (CDC). */
+ 0x00, /* bDeviceSubClass. */
+ 0x00, /* bDeviceProtocol. */
+ 0x40, /* bMaxPacketSize. */
+ 0x0483, /* idVendor (ST). */
+ 0x5740, /* idProduct. */
+ 0x0200, /* bcdDevice. */
+ 1, /* iManufacturer. */
+ 2, /* iProduct. */
+ 3, /* iSerialNumber. */
+ 1) /* bNumConfigurations. */
+};
+
+/*
+ * Device Descriptor wrapper.
+ */
+static const USBDescriptor vcom_device_descriptor = {
+ sizeof vcom_device_descriptor_data,
+ vcom_device_descriptor_data
+};
+
+/* Configuration Descriptor tree for a CDC.*/
+static const uint8_t vcom_configuration_descriptor_data[67] = {
+ /* Configuration Descriptor.*/
+ USB_DESC_CONFIGURATION(67, /* wTotalLength. */
+ 0x02, /* bNumInterfaces. */
+ 0x01, /* bConfigurationValue. */
+ 0, /* iConfiguration. */
+ 0xC0, /* bmAttributes (self powered). */
+ 50), /* bMaxPower (100mA). */
+ /* Interface Descriptor.*/
+ USB_DESC_INTERFACE (0x00, /* bInterfaceNumber. */
+ 0x00, /* bAlternateSetting. */
+ 0x01, /* bNumEndpoints. */
+ 0x02, /* bInterfaceClass (Communications
+ Interface Class, CDC section
+ 4.2). */
+ 0x02, /* bInterfaceSubClass (Abstract
+ Control Model, CDC section 4.3). */
+ 0x01, /* bInterfaceProtocol (AT commands,
+ CDC section 4.4). */
+ 0), /* iInterface. */
+ /* Header Functional Descriptor (CDC section 5.2.3).*/
+ USB_DESC_BYTE (5), /* bLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x00), /* bDescriptorSubtype (Header
+ Functional Descriptor. */
+ USB_DESC_BCD (0x0110), /* bcdCDC. */
+ /* Call Management Functional Descriptor. */
+ USB_DESC_BYTE (5), /* bFunctionLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x01), /* bDescriptorSubtype (Call Management
+ Functional Descriptor). */
+ USB_DESC_BYTE (0x00), /* bmCapabilities (D0+D1). */
+ USB_DESC_BYTE (0x01), /* bDataInterface. */
+ /* ACM Functional Descriptor.*/
+ USB_DESC_BYTE (4), /* bFunctionLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x02), /* bDescriptorSubtype (Abstract
+ Control Management Descriptor). */
+ USB_DESC_BYTE (0x02), /* bmCapabilities. */
+ /* Union Functional Descriptor.*/
+ USB_DESC_BYTE (5), /* bFunctionLength. */
+ USB_DESC_BYTE (0x24), /* bDescriptorType (CS_INTERFACE). */
+ USB_DESC_BYTE (0x06), /* bDescriptorSubtype (Union
+ Functional Descriptor). */
+ USB_DESC_BYTE (0x00), /* bMasterInterface (Communication
+ Class Interface). */
+ USB_DESC_BYTE (0x01), /* bSlaveInterface0 (Data Class
+ Interface). */
+ /* Endpoint 2 Descriptor.*/
+ USB_DESC_ENDPOINT (USBD1_INTERRUPT_REQUEST_EP|0x80,
+ 0x03, /* bmAttributes (Interrupt). */
+ 0x0008, /* wMaxPacketSize. */
+ 0xFF), /* bInterval. */
+ /* Interface Descriptor.*/
+ USB_DESC_INTERFACE (0x01, /* bInterfaceNumber. */
+ 0x00, /* bAlternateSetting. */
+ 0x02, /* bNumEndpoints. */
+ 0x0A, /* bInterfaceClass (Data Class
+ Interface, CDC section 4.5). */
+ 0x00, /* bInterfaceSubClass (CDC section
+ 4.6). */
+ 0x00, /* bInterfaceProtocol (CDC section
+ 4.7). */
+ 0x00), /* iInterface. */
+ /* Endpoint 3 Descriptor.*/
+ USB_DESC_ENDPOINT (USBD1_DATA_AVAILABLE_EP, /* bEndpointAddress.*/
+ 0x02, /* bmAttributes (Bulk). */
+ 0x0040, /* wMaxPacketSize. */
+ 0x00), /* bInterval. */
+ /* Endpoint 1 Descriptor.*/
+ USB_DESC_ENDPOINT (USBD1_DATA_REQUEST_EP|0x80, /* bEndpointAddress.*/
+ 0x02, /* bmAttributes (Bulk). */
+ 0x0040, /* wMaxPacketSize. */
+ 0x00) /* bInterval. */
+};
+
+/*
+ * Configuration Descriptor wrapper.
+ */
+static const USBDescriptor vcom_configuration_descriptor = {
+ sizeof vcom_configuration_descriptor_data,
+ vcom_configuration_descriptor_data
+};
+
+/*
+ * U.S. English language identifier.
+ */
+static const uint8_t vcom_string0[] = {
+ USB_DESC_BYTE(4), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ USB_DESC_WORD(0x0409) /* wLANGID (U.S. English). */
+};
+
+/*
+ * Vendor string.
+ */
+static const uint8_t vcom_string1[] = {
+ USB_DESC_BYTE(38), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ 'S', 0, 'T', 0, 'M', 0, 'i', 0, 'c', 0, 'r', 0, 'o', 0, 'e', 0,
+ 'l', 0, 'e', 0, 'c', 0, 't', 0, 'r', 0, 'o', 0, 'n', 0, 'i', 0,
+ 'c', 0, 's', 0
+};
+
+/*
+ * Device Description string.
+ */
+static const uint8_t vcom_string2[] = {
+ USB_DESC_BYTE(56), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ 'C', 0, 'h', 0, 'i', 0, 'b', 0, 'i', 0, 'O', 0, 'S', 0, '/', 0,
+ 'R', 0, 'T', 0, ' ', 0, 'V', 0, 'i', 0, 'r', 0, 't', 0, 'u', 0,
+ 'a', 0, 'l', 0, ' ', 0, 'C', 0, 'O', 0, 'M', 0, ' ', 0, 'P', 0,
+ 'o', 0, 'r', 0, 't', 0
+};
+
+/*
+ * Serial Number string.
+ */
+static const uint8_t vcom_string3[] = {
+ USB_DESC_BYTE(8), /* bLength. */
+ USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */
+ '0' + CH_KERNEL_MAJOR, 0,
+ '0' + CH_KERNEL_MINOR, 0,
+ '0' + CH_KERNEL_PATCH, 0
+};
+
+/*
+ * Strings wrappers array.
+ */
+static const USBDescriptor vcom_strings[] = {
+ {sizeof vcom_string0, vcom_string0},
+ {sizeof vcom_string1, vcom_string1},
+ {sizeof vcom_string2, vcom_string2},
+ {sizeof vcom_string3, vcom_string3}
+};
+
+/*
+ * Handles the GET_DESCRIPTOR callback. All required descriptors must be
+ * handled here.
+ */
+static const USBDescriptor *get_descriptor(USBDriver *usbp,
+ uint8_t dtype,
+ uint8_t dindex,
+ uint16_t lang) {
+
+ (void)usbp;
+ (void)lang;
+ switch (dtype) {
+ case USB_DESCRIPTOR_DEVICE:
+ return &vcom_device_descriptor;
+ case USB_DESCRIPTOR_CONFIGURATION:
+ return &vcom_configuration_descriptor;
+ case USB_DESCRIPTOR_STRING:
+ if (dindex < 4)
+ return &vcom_strings[dindex];
+ }
+ return NULL;
+}
+
+/**
+ * @brief IN EP1 state.
+ */
+static USBInEndpointState ep1instate;
+
+/**
+ * @brief OUT EP1 state.
+ */
+static USBOutEndpointState ep1outstate;
+
+/**
+ * @brief EP1 initialization structure (both IN and OUT).
+ */
+static const USBEndpointConfig ep1config = {
+ USB_EP_MODE_TYPE_BULK,
+ NULL,
+ sduDataTransmitted,
+ sduDataReceived,
+ 0x0040,
+ 0x0040,
+ &ep1instate,
+ &ep1outstate,
+ 1,
+ NULL
+};
+
+/**
+ * @brief IN EP2 state.
+ */
+static USBInEndpointState ep2instate;
+
+/**
+ * @brief EP2 initialization structure (IN only).
+ */
+static const USBEndpointConfig ep2config = {
+ USB_EP_MODE_TYPE_INTR,
+ NULL,
+ sduInterruptTransmitted,
+ NULL,
+ 0x0010,
+ 0x0000,
+ &ep2instate,
+ NULL,
+ 1,
+ NULL
+};
+
+/*
+ * Handles the USB driver global events.
+ */
+static void usb_event(USBDriver *usbp, usbevent_t event) {
+ extern SerialUSBDriver SDU1;
+
+ switch (event) {
+ case USB_EVENT_ADDRESS:
+ return;
+ case USB_EVENT_CONFIGURED:
+ chSysLockFromISR();
+
+ /* Enables the endpoints specified into the configuration.
+ Note, this callback is invoked from an ISR so I-Class functions
+ must be used.*/
+ usbInitEndpointI(usbp, USBD1_DATA_REQUEST_EP, &ep1config);
+ usbInitEndpointI(usbp, USBD1_INTERRUPT_REQUEST_EP, &ep2config);
+
+ /* Resetting the state of the CDC subsystem.*/
+ sduConfigureHookI(&SDU1);
+
+ chSysUnlockFromISR();
+ return;
+ case USB_EVENT_RESET:
+ /* Falls into.*/
+ case USB_EVENT_UNCONFIGURED:
+ /* Falls into.*/
+ case USB_EVENT_SUSPEND:
+ chSysLockFromISR();
+
+ /* Disconnection event on suspend.*/
+ sduSuspendHookI(&SDU1);
+
+ chSysUnlockFromISR();
+ return;
+ case USB_EVENT_WAKEUP:
+ chSysLockFromISR();
+
+ /* Connection event on wakeup.*/
+ sduWakeupHookI(&SDU1);
+
+ chSysUnlockFromISR();
+ return;
+ case USB_EVENT_STALLED:
+ return;
+ }
+ return;
+}
+
+/*
+ * Handles the USB driver global events.
+ */
+static void sof_handler(USBDriver *usbp) {
+
+ (void)usbp;
+
+ osalSysLockFromISR();
+ sduSOFHookI(&SDU1);
+ osalSysUnlockFromISR();
+}
+
+/*
+ * USB driver configuration.
+ */
+const USBConfig usbcfg = {
+ usb_event,
+ get_descriptor,
+ sduRequestsHook,
+ sof_handler
+};
+
+/*
+ * Serial over USB driver configuration.
+ */
+const SerialUSBConfig serusbcfg = {
+#if defined(TARGET_PLATFORM_H7)
+ &USBD2,
+#else
+ &USBD1,
+#endif
+ USBD1_DATA_REQUEST_EP,
+ USBD1_DATA_AVAILABLE_EP,
+ USBD1_INTERRUPT_REQUEST_EP
+};
diff --git a/firmware/source/periph/usbcfg.h b/firmware/source/periph/usbcfg.h new file mode 100644 index 0000000..2fceccb --- /dev/null +++ b/firmware/source/periph/usbcfg.h @@ -0,0 +1,28 @@ +/*
+ ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#ifndef USBCFG_H
+#define USBCFG_H
+
+#include "hal.h"
+
+extern const USBConfig usbcfg;
+extern SerialUSBConfig serusbcfg;
+extern SerialUSBDriver SDU1;
+
+#endif /* USBCFG_H */
+
+/** @} */
diff --git a/firmware/source/periph/usbserial.cpp b/firmware/source/periph/usbserial.cpp new file mode 100644 index 0000000..775a911 --- /dev/null +++ b/firmware/source/periph/usbserial.cpp @@ -0,0 +1,52 @@ +/** + * @file usbserial.cpp + * @brief Wrapper for ChibiOS's SerialUSBDriver. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see <https://www.gnu.org/licenses/>. + */ + +#include "usbserial.hpp" + +SerialUSBDriver *USBSerial::m_driver = &SDU1; + +void USBSerial::begin() +{ + palSetPadMode(GPIOA, 11, PAL_MODE_ALTERNATE(10)); + palSetPadMode(GPIOA, 12, PAL_MODE_ALTERNATE(10)); + + sduObjectInit(m_driver); + sduStart(m_driver, &serusbcfg); + + // Reconnect bus so device can re-enumerate on reset + usbDisconnectBus(serusbcfg.usbp); + chThdSleepMilliseconds(1500); + usbStart(serusbcfg.usbp, &usbcfg); + usbConnectBus(serusbcfg.usbp); +} + +bool USBSerial::isActive() +{ + if (auto config = m_driver->config; config != nullptr) { + if (auto usbp = config->usbp; usbp != nullptr) + return usbp->state == USB_ACTIVE && !ibqIsEmptyI(&m_driver->ibqueue); + } + + return false; +} + +size_t USBSerial::read(unsigned char *buffer, size_t count) +{ + auto bss = reinterpret_cast<BaseSequentialStream *>(m_driver); + return streamRead(bss, buffer, count); +} + +size_t USBSerial::write(const unsigned char *buffer, size_t count) +{ + auto bss = reinterpret_cast<BaseSequentialStream *>(m_driver); + return streamWrite(bss, buffer, count); +} + diff --git a/firmware/source/periph/usbserial.hpp b/firmware/source/periph/usbserial.hpp new file mode 100644 index 0000000..58113c9 --- /dev/null +++ b/firmware/source/periph/usbserial.hpp @@ -0,0 +1,32 @@ +/** + * @file usbserial.hpp + * @brief Wrapper for ChibiOS's SerialUSBDriver. + * + * Copyright (C) 2021 Clyne Sullivan + * + * Distributed under the GNU GPL v3 or later. You should have received a copy of + * the GNU General Public License along with this program. + * If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef STMDSP_USBSERIAL_HPP_ +#define STMDSP_USBSERIAL_HPP_ + +#include "usbcfg.h" + +class USBSerial +{ +public: + static void begin(); + + static bool isActive(); + + static size_t read(unsigned char *buffer, size_t count); + static size_t write(const unsigned char *buffer, size_t count); + +private: + static SerialUSBDriver *m_driver; +}; + +#endif // STMDSP_USBSERIAL_HPP_ + |