Compare commits

...

16 Commits

11 changed files with 442 additions and 65 deletions

3
.gitmodules vendored
View File

@ -1,5 +1,4 @@
[submodule "libopencm3"]
path = libopencm3
url = https://github.com/manuelbl/libopencm3
url = https://github.com/libopencm3/libopencm3/
ignore = all
branch = no-vbus-sensing

View File

@ -24,6 +24,7 @@
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/desig.h> // design utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/usb/dwc/otg_fs.h> // USB definitions
/* own libraries */
#include "global.h" // board definitions
@ -379,6 +380,8 @@ void main(void)
board_setup(); // setup board
uart_setup(); // setup USART (for printing)
usb_cdcacm_setup(); // setup USB CDC ACM (for printing)
OTG_FS_GCCFG |= OTG_GCCFG_NOVBUSSENS | OTG_GCCFG_PWRDWN; // disable VBUS sensing
OTG_FS_GCCFG &= ~(OTG_GCCFG_VBUSBSEN | OTG_GCCFG_VBUSASEN); // force USB device mode
puts("\nwelcome to the CuVoodoo STM32F4 example firmware\n"); // print welcome message
#if DEBUG

View File

@ -37,9 +37,8 @@ void main(void)
__dfu_magic[2] = 0;
__dfu_magic[3] = 0;
} else { // check if the force DFU mode input is set
// disable SWJ pin to use as GPIO
#if (defined(DFU_FORCE_PIN) && defined(DFU_FORCE_VALUE))
rcc_periph_clock_enable(GPIO_RCC(DFU_FORCE_PIN)); // enable clock for button
rcc_periph_clock_enable(GPIO_RCC(DFU_FORCE_PIN)); // enable clock for button
#if (DFU_FORCE_VALUE == 1)
gpio_mode_setup(GPIO_PORT(DFU_FORCE_PIN), GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN(DFU_FORCE_PIN)); // set GPIO to input
if (gpio_get(GPIO_PORT(DFU_FORCE_PIN), GPIO_PIN(DFU_FORCE_PIN))) { // check if output is set to the value to force DFU mode
@ -49,9 +48,9 @@ void main(void)
#endif // DFU_FORCE_VALUE
dfu_force = true; // DFU mode forced
}
#endif // defined(DFU_FORCE_PIN)
rcc_periph_reset_pulse(GPIO_RST(DFU_FORCE_PIN)); // reset pin GPIO domain
rcc_periph_clock_disable(GPIO_RCC(DFU_FORCE_PIN)); // disable pin GPIO domain
#endif // defined(DFU_FORCE_PIN)
}
// start application if valid

View File

@ -404,6 +404,8 @@ int32_t adds32_safe(int32_t a, int32_t b);
#define tim10_isr tim1_up_tim10_isr
/** interrupt service routine for timer 11 */
#define tim11_isr tim1_trg_com_tim11_isr
/** get TIM_OC based on CHx identifier */
#define TIM_OC(x) CAT2(TIM_OC,x)
/** get TIM_IC based on CHx identifier */
#define TIM_IC(x) CAT2(TIM_IC,x)
/** get TIM_IC_IN_TI based on CHx identifier */

213
lib/led_rgbpanel.c Normal file
View File

@ -0,0 +1,213 @@
/** control RGB LED matrix panels through shift registers
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2022
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/dma.h> // DMA library
#include <libopencm3/stm32/timer.h> // timer library
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include "led_rgbpanel.h" // library API
#include "global.h" // common methods
// RGB matrix pins (warning: hard coded)
#define RGBPANEL_OE_PIN PB10 /**< pin to enable output (active low) */
#define RGBPANEL_A_PIN PB0 /**< pin to select line */
#define RGBPANEL_B_PIN PB1 /**< pin to select line */
#define RGBPANEL_C_PIN PB2 /**< pin to select line */
#define RGBPANEL_D_PIN PB3 /**< pin to select line */
#define RGBPANEL_CLK_PIN PA0 /**< pin to generate clock for serial data */
#define RGBPANEL_LAT_PIN PA1 /**< pin to latch data on rising edge */
#define RGBPANEL_R1_PIN PA2 /**< pin to enable red color on top half */
#define RGBPANEL_G1_PIN PA3 /**< pin to enable green color on top half */
#define RGBPANEL_B1_PIN PA4 /**< pin to enable blue color on top half */
#define RGBPANEL_R2_PIN PA5 /**< pin to enable red color on bottom half */
#define RGBPANEL_G2_PIN PA6 /**< pin to enable green color on bottom half */
#define RGBPANEL_B2_PIN PA7 /**< pin to enable blue color on bottom half */
#define RGBPANEL_DMA DMA2 /**< DMA used to send data to the RGB matrix (only DMA2 can be used for memory-to-memory transfer) */
#define RGBPANEL_RCC_DMA RCC_DMA2 /**< RCC for DMA used for the RGB matrix */
#define RGBPANEL_STREAM DMA_STREAM1 /**< stream used to send data to the RGB matrix (any stream can be used for memory-to-memory transfer) */
#define RGBPANEL_CHANNEL DMA_SxCR_CHSEL_0 /**< channel used to send data to the RGB matrix (any channel can be used for memory-to-memory transfer) */
#define RGBPANEL_IRQ NVIC_DMA2_STREAM1_IRQ /**< IRQ for when a line transfer is complete */
#define RGBPANEL_ISR dma2_stream1_isr /**< ISR for when a line transfer is complete */
#define RGBPANEL_TIMER 3 /**< timer to update lines */
static uint8_t rgbpanel_data[RGBPANEL_HEIGHT / 2][RGBPANEL_WIDTH * 2]; /**< data to be sent to RGB matrix (one byte includes upper and lower half values, each byte has 2 clock edges) */
static volatile uint8_t rgbpanel_line = 0; // line being transferred
void rgbpanel_setup(void)
{
// configure pin for output enable
rcc_periph_clock_enable(GPIO_RCC(RGBPANEL_OE_PIN)); // enable clock for GPIO port peripheral
gpio_set(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_PIN(RGBPANEL_OE_PIN)); // disable output
gpio_set_output_options(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN(RGBPANEL_OE_PIN)); // set fast edge
gpio_mode_setup(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_OE_PIN)); // set pin as output
// configure pins for data and clock lines
const uint32_t rgbpanel_serial_port = GPIO_PORT(RGBPANEL_LAT_PIN); // common port for pins controlling the serial data
const uint16_t rgbpanel_serial_pins = GPIO_PIN(RGBPANEL_LAT_PIN) | GPIO_PIN(RGBPANEL_CLK_PIN) | GPIO_PIN(RGBPANEL_R1_PIN) | GPIO_PIN(RGBPANEL_G1_PIN) | GPIO_PIN(RGBPANEL_B1_PIN) | GPIO_PIN(RGBPANEL_R2_PIN) | GPIO_PIN(RGBPANEL_G2_PIN) | GPIO_PIN(RGBPANEL_B2_PIN); // pins controlling the serial data
rcc_periph_clock_enable(GPIO_RCC(RGBPANEL_LAT_PIN)); // enable clock for GPIO port peripheral
gpio_clear(rgbpanel_serial_port, rgbpanel_serial_pins); // disable LEDs
gpio_set_output_options(rgbpanel_serial_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, rgbpanel_serial_pins); // set fast edge
gpio_mode_setup(rgbpanel_serial_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, rgbpanel_serial_pins); // set pin as output
// configure pins for address lines
rcc_periph_clock_enable(RCC_GPIOB); // enable clock for GPIO port peripheral
gpio_clear(RCC_GPIOB, GPIO0 | GPIO1 | GPIO2 | GPIO3); // unselect line
gpio_set_output_options(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO0 | GPIO1 | GPIO2 | GPIO3); // set fast edge
gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0 | GPIO1 | GPIO2 | GPIO3); // set pin as output
// configure DMA to sent line data
// because there is no peripheral request for data, this is a memory to memory transfer
rcc_periph_clock_enable(RGBPANEL_RCC_DMA); // enable clock for DMA peripheral (any DMA and channel can be used)
dma_disable_stream(RGBPANEL_DMA, RGBPANEL_STREAM); // disable stream before re-configuring
while (DMA_SCR(RGBPANEL_DMA, RGBPANEL_STREAM) & DMA_SxCR_EN); // wait until transfer is finished before we can reconfigure
dma_stream_reset(RGBPANEL_DMA, RGBPANEL_STREAM); // use default values
dma_set_peripheral_size(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_PSIZE_8BIT); // we only write the 8 first bit
dma_enable_peripheral_increment_mode(RGBPANEL_DMA, RGBPANEL_STREAM); // increment address of memory to read
dma_set_memory_address(RGBPANEL_DMA, RGBPANEL_STREAM, (uint32_t) &GPIOA_ODR); // set GPIOA as destination (for memory-to-memory transfer, the destination is the memory)
dma_set_memory_size(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_MSIZE_8BIT); // read 8 bits for transfer
dma_disable_memory_increment_mode(RGBPANEL_DMA, RGBPANEL_STREAM); // don't increment GPIO address
dma_set_number_of_data(RGBPANEL_DMA, RGBPANEL_STREAM, LENGTH(rgbpanel_data[0])); // set transfer size (one line)
dma_channel_select(RGBPANEL_DMA, RGBPANEL_STREAM, RGBPANEL_CHANNEL); // set the channel for this stream
dma_set_transfer_mode(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_DIR_MEM_TO_MEM); // set transfer from memory to memory
dma_set_priority(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_SxCR_PL_LOW); // there is no need to rush
dma_enable_transfer_complete_interrupt(RGBPANEL_DMA, RGBPANEL_STREAM); // interrupt when line transfer is complete
nvic_enable_irq(RGBPANEL_IRQ); // enable interrupt
rgbpanel_clear(); // clear matrix
gpio_clear(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_PIN(RGBPANEL_OE_PIN)); // enable output
// configure timer to go through rows/lines
rcc_periph_clock_enable(RCC_TIM(RGBPANEL_TIMER)); // enable clock for timer domain
rcc_periph_reset_pulse(RST_TIM(RGBPANEL_TIMER)); // reset timer state
timer_set_mode(TIM(RGBPANEL_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
timer_set_prescaler(TIM(RGBPANEL_TIMER), 2 - 1); // hand tuned prescale to minimize inter-line ghosting
timer_set_period(TIM(RGBPANEL_TIMER), 0x9fff - 1); // hand tuned period to minimize inter-line ghosting
timer_clear_flag(TIM(RGBPANEL_TIMER), TIM_SR_UIF); // clear update (overflow) flag
timer_update_on_overflow(TIM(RGBPANEL_TIMER)); // only use counter overflow as UEV source (use overflow as next line update indication)
timer_enable_irq(TIM(RGBPANEL_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
nvic_enable_irq(NVIC_TIM_IRQ(RGBPANEL_TIMER)); // catch interrupt in service routine
timer_enable_counter(TIM(RGBPANEL_TIMER)); // start timer to update RGB matrix
}
void rgbpanel_release(void)
{
// release timer
timer_disable_counter(TIM(RGBPANEL_TIMER)); // stop timer
nvic_disable_irq(NVIC_TIM_IRQ(RGBPANEL_TIMER)); // disable interrupt
rcc_periph_reset_pulse(RST_TIM(RGBPANEL_TIMER)); // reset timer state
// release DMA (it will stop onve completed
while (DMA_SCR(RGBPANEL_DMA, RGBPANEL_STREAM) & DMA_SxCR_EN); // wait until DMA is complete
dma_disable_stream(RGBPANEL_DMA, RGBPANEL_STREAM); // disable stream
dma_stream_reset(RGBPANEL_DMA, RGBPANEL_STREAM); // use default values
nvic_disable_irq(RGBPANEL_IRQ); // disable interrupt
// release GPIO
gpio_mode_setup(GPIO_PORT(RGBPANEL_OE_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_OE_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_A_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_A_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_B_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_B_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_C_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_C_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_D_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_D_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_CLK_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_CLK_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_LAT_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_LAT_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_R1_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_R1_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_G1_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_G1_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_B1_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_B1_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_R2_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_R2_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_G2_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_G2_PIN));
gpio_mode_setup(GPIO_PORT(RGBPANEL_B2_PIN), GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN(RGBPANEL_B2_PIN));
}
void rgbpanel_clear(void)
{
for (uint8_t i = 0; i < LENGTH(rgbpanel_data); i++) { // for each line
for (uint8_t j = 0; j < LENGTH(rgbpanel_data[0]); j += 2) { // for each clock cycle
rgbpanel_data[i][j + 0] = 0; // create clock falling edge
rgbpanel_data[i][j + 1] = 1; // create clock rising edge
}
rgbpanel_data[i][LENGTH(rgbpanel_data[0]) - 1] |= (1 << 1); // latch data (next line will remove the latch)
}
}
void rgbpanel_set(int16_t x, int16_t y, bool r, bool g, bool b)
{
if (x < 0 || x >= RGBPANEL_WIDTH) {
return;
}
if (y < 0 || y >= RGBPANEL_HEIGHT) {
return;
}
const uint8_t row = y % (RGBPANEL_HEIGHT / 2); // get the actual line/row
const uint8_t col = x * 2; // get the actual column
uint8_t data = 0; // there we will set the color bits
if (y < (RGBPANEL_HEIGHT / 2)) {
data = rgbpanel_data[row][col] & 0xe0; // keep lower line colors
if (r) {
data |= (1 << 2);
}
if (g) {
data |= (1 << 3);
}
if (b) {
data |= (1 << 4);
}
} else {
data = rgbpanel_data[row][col] & 0x1c; // keep upper line colors
if (r) {
data |= (1 << 5);
}
if (g) {
data |= (1 << 6);
}
if (b) {
data |= (1 << 7);
}
}
// set data on low edge
rgbpanel_data[row][col + 0] &= 0x3; // clear color data (don't touch clock and latch data)
rgbpanel_data[row][col + 0] |= data; // set the LED data
// set data on high edge
rgbpanel_data[row][col + 1] &= 0x3; // clear color data (don't touch clock and latch data)
rgbpanel_data[row][col + 1] |= data; // set the LED data on clock high edge
}
/** ISR triggered when the data for the line of the RGB matrix has been transferred
* we switch line just after the data is latched, to reduce ghosting
*/
void RGBPANEL_ISR(void)
{
if (dma_get_interrupt_flag(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_TCIF)) {
dma_clear_interrupt_flags(RGBPANEL_DMA, RGBPANEL_STREAM, DMA_TCIF);
GPIOB_ODR = (GPIOB_ODR & 0xfff0) | (rgbpanel_line & 0xf); // select line (line on lower and upper half are updated at once)
rgbpanel_line = (rgbpanel_line + 1) % (RGBPANEL_HEIGHT / 2); // go to next line (two lines are updated at once)
}
}
/** interrupt service routine called to update next line of RGB matrix
* @note ideally the next line should be updated when the current one is complete, but the DMA is too fast.
* @note switching lines too fast causes inter-line ghosting of the LEDs (on the same column), due to capacitance and driver switching limitations
*/
void TIM_ISR(RGBPANEL_TIMER)(void)
{
if (timer_get_flag(TIM(RGBPANEL_TIMER), TIM_SR_UIF)) { // update event happened
timer_clear_flag(TIM(RGBPANEL_TIMER), TIM_SR_UIF); // clear flag
if (DMA_SCR(RGBPANEL_DMA, RGBPANEL_STREAM) & DMA_SxCR_EN) { // DMA is not complete
return;
}
dma_set_peripheral_address(RGBPANEL_DMA, RGBPANEL_STREAM, (uint32_t)&rgbpanel_data[rgbpanel_line]); // set memory containing line data to be transferred (for memory-to-memory transfer, the source is the peripheral)
dma_enable_stream(RGBPANEL_DMA, RGBPANEL_STREAM); // start sending next line
}
}

23
lib/led_rgbpanel.h Normal file
View File

@ -0,0 +1,23 @@
/** control RGB LED matrix panels through shift registers
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2022
*/
#define RGBPANEL_HEIGHT 32 /**< number of rows in the RGB matrix */
#define RGBPANEL_WIDTH 64 /**< number of columns in the RGB matrix */
/** setup peripheral to control RGB panel */
void rgbpanel_setup(void);
/** release peripheral used to control RGB panel */
void rgbpanel_release(void);
/** switch off all LEDs on the RGB panel */
void rgbpanel_clear(void);
/** set color of the LED on the RGB panel
* @param[in] x horizontal position (0 = left)
* @param[in] y vertical position (0 = top)
* @param[in] r if the red LED should be on
* @param[in] g if the green LED should be on
* @param[in] b if the blue LED should be on
*/
void rgbpanel_set(int16_t x, int16_t y, bool r, bool g, bool b);

View File

@ -2,7 +2,7 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2020
* @date 2016-2022
* @note peripherals used: SPI @ref led_ws2812b_spi, DMA
*/
@ -26,9 +26,16 @@
* @{
*/
/** SPI peripheral
* @note SPI2 is 5V tolerant and can be operated in open-drain mode will 1 kO pull-up resistor to 5V to get 5V signal level
* @note if pin is 5V tolerant, it can be operated in open-drain mode will 1 kOhm pull-up resistor to 5V to get 5V signal level
*/
#define LED_WS2812B_SPI 2 /**< SPI peripheral */
#define LED_WS2812B_SPI 1 /**< SPI peripheral */
#define LED_WS2812B_DR &SPI1_DR /**< SPI Data Register to transmit data */
#define LED_WS2812B_DOUT PB5 /**< SPI MOSI pin used for data output */
#define LED_WS2812B_AF GPIO_AF5 /**< alternate function for SPI pin */
#define LED_WS2812B_RCC_DMA RCC_DMA2 /**< RCC for DMA for SPI peripheral */
#define LED_WS2812B_DMA DMA2 /**< DMA for SPI peripheral */
#define LED_WS2812B_STREAM DMA_STREAM3 /**< DMA stream for SPI TX */
#define LED_WS2812B_CHANNEL DMA_SxCR_CHSEL_3 /**< DMA channel for SPI TX */
/** @} */
/** bit template to encode one byte to be shifted out by SPI to the WS2812B LEDs
@ -40,8 +47,8 @@
*/
#define LED_WS2812B_SPI_TEMPLATE 0x924924
/** data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset (~40 data bits) */
uint8_t led_ws2812b_data[LED_WS2812B_LEDS * 3 * 3 + 40 * 3 / 8 + 1] = {0};
/** data encoded to be shifted out by SPI for the WS2812B, plus the 50us reset */
uint8_t led_ws2812b_data[LED_WS2812B_LEDS * 3 * 3 + 25] = {0};
void led_ws2812b_set_rgb(uint16_t led, uint8_t red, uint8_t green, uint8_t blue)
{
@ -94,13 +101,14 @@ void led_ws2812b_setup(void)
}
// setup SPI to transmit data
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(LED_WS2812B_SPI)); // enable clock for SPI output peripheral
gpio_set_mode(SPI_MOSI_PORT(LED_WS2812B_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, SPI_MOSI_PIN(LED_WS2812B_SPI)); // set MOSI as output (push-pull needs to be shifted to 5V, open-drain consumes up to 5 mA with pull-up resistor of 1 kO)
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(GPIO_RCC(LED_WS2812B_DOUT)); // enable clock for SPI output peripheral
gpio_mode_setup(GPIO_PORT(LED_WS2812B_DOUT), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(LED_WS2812B_DOUT)); // set SPI MOSI pin to alternate function
gpio_set_output_options(GPIO_PORT(LED_WS2812B_DOUT), GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO_PIN(LED_WS2812B_DOUT)); // set SPI MOSI pin output as open-drain
gpio_set_af(GPIO_PORT(LED_WS2812B_DOUT), LED_WS2812B_AF, GPIO_PIN(LED_WS2812B_DOUT)); // set alternate function to SPI
rcc_periph_clock_enable(RCC_SPI(LED_WS2812B_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(LED_WS2812B_SPI)); // clear SPI values to default
spi_set_master_mode(SPI(LED_WS2812B_SPI)); // set SPI as master since we generate the clock
spi_set_baudrate_prescaler(SPI(LED_WS2812B_SPI), SPI_CR1_BR_FPCLK_DIV_16); // set clock to 0.44 us per bit
spi_set_baudrate_prescaler(SPI(LED_WS2812B_SPI), SPI_CR1_BR_FPCLK_DIV_32); // set clock to 0.30 us per bit
spi_set_clock_polarity_1(SPI(LED_WS2812B_SPI)); // clock is high when idle
spi_set_clock_phase_1(SPI(LED_WS2812B_SPI)); // output data on second edge (rising)
spi_set_dff_8bit(SPI(LED_WS2812B_SPI)); // use 8 bits for simpler encoding (but there will be more interrupts)
@ -109,20 +117,25 @@ void led_ws2812b_setup(void)
spi_set_unidirectional_mode(SPI(LED_WS2812B_SPI)); // we only need to transmit data
spi_enable_software_slave_management(SPI(LED_WS2812B_SPI)); // control the slave select in software (so we can start transmission)
spi_set_nss_high(SPI(LED_WS2812B_SPI)); // set NSS high so we can output
spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transferred
spi_enable(SPI(LED_WS2812B_SPI)); // enable SPI
// configure DMA to provide the pattern to be shifted out from SPI to the WS2812B LEDs
rcc_periph_clock_enable(RCC_DMA_SPI(LED_WS2812B_SPI)); // enable clock for DMA peripheral
dma_channel_reset(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // start with fresh channel configuration
dma_set_read_from_memory(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // set direction from memory to peripheral
dma_set_memory_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_MSIZE_8BIT); // read 8 bits from memory
dma_set_memory_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)led_ws2812b_data); // set bit pattern as source address
dma_enable_memory_increment_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // go through bit pattern
dma_set_number_of_data(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), LENGTH(led_ws2812b_data)); // set the size of the data to transmit
dma_set_peripheral_address(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), (uint32_t)&SPI_DR(SPI(LED_WS2812B_SPI))); // set SPI as peripheral destination address
dma_set_peripheral_size(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PSIZE_8BIT); // write 8 bits to peripheral
dma_set_priority(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI), DMA_CCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
dma_enable_circular_mode(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // always send the data
dma_enable_channel(DMA_SPI(LED_WS2812B_SPI), DMA_CHANNEL_SPI_TX(LED_WS2812B_SPI)); // enable DMA channel
spi_enable_tx_dma(SPI(LED_WS2812B_SPI)); // use DMA to provide data stream to be transferred
// configure DMA
rcc_periph_clock_enable(LED_WS2812B_RCC_DMA); // enable clock for DMA peripheral
dma_disable_stream(LED_WS2812B_DMA, LED_WS2812B_STREAM); // disable stream before re-configuring
while (DMA_SCR(LED_WS2812B_DMA, LED_WS2812B_STREAM) & DMA_SxCR_EN); // wait until transfer is finished before we can reconfigure
dma_stream_reset(LED_WS2812B_DMA, LED_WS2812B_STREAM); // use default values
dma_channel_select(LED_WS2812B_DMA, LED_WS2812B_STREAM, LED_WS2812B_CHANNEL); // set the channel for this stream
dma_set_transfer_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_DIR_MEM_TO_PERIPHERAL); // set transfer from memory to memory
dma_set_memory_size(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_MSIZE_8BIT); // read 8 bits for transfer
dma_set_memory_address(LED_WS2812B_DMA, LED_WS2812B_STREAM, (uint32_t)led_ws2812b_data); // set memory address to read from
dma_enable_memory_increment_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // increment address of memory to read
dma_set_peripheral_address(LED_WS2812B_DMA, LED_WS2812B_STREAM, (uint32_t)LED_WS2812B_DR); // set peripheral address to write data to
dma_set_peripheral_size(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_PSIZE_8BIT); // we only write the 8 first bit
dma_disable_peripheral_increment_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // always write to the SPI data register
dma_set_number_of_data(LED_WS2812B_DMA, LED_WS2812B_STREAM, LENGTH(led_ws2812b_data)); // set transfer size
dma_set_priority(LED_WS2812B_DMA, LED_WS2812B_STREAM, DMA_SxCR_PL_HIGH); // set priority to high since time is crucial for the peripheral
dma_enable_circular_mode(LED_WS2812B_DMA, LED_WS2812B_STREAM); // always send the data
dma_enable_stream(LED_WS2812B_DMA, LED_WS2812B_STREAM); // enable DMA channel
}

View File

@ -2,14 +2,13 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016-2020
* @date 2016-2022
* @note peripherals used: SPI @ref led_ws2812b_spi, DMA
*/
#pragma once
#error not converted for STM32F4
/** number of LEDs on the WS2812B strip */
#define LED_WS2812B_LEDS 48
#define LED_WS2812B_LEDS 3U
/** setup WS2812B LED driver
* @note this starts the continuous transmission

View File

@ -2,7 +2,7 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016
* @date 2016-2022
* @note peripherals used: USART @ref radio_esp8266_usart
*/
@ -25,17 +25,22 @@
/** @defgroup radio_esp8266_usart USART peripheral used for communication with radio
* @{
*/
#define RADIO_ESP8266_USART 2 /**< USART peripheral */
#define RADIO_ESP8266_USART 1 /**< USART peripheral */
#define RADIO_ESP8266_TX PA9 /**< pin used for USART TX */
#define RADIO_ESP8266_RX PA10 /**< pin used for USART RX */
#define RADIO_ESP8266_AF GPIO_AF7 /**< alternate function for UART pins */
/** @} */
/* input and output buffers and used memory */
static uint8_t rx_buffer[24] = {0}; /**< buffer for received data (we only expect AT responses) */
static uint8_t rx_buffer[24 + 530] = {0}; /**< buffer for received data */
static volatile uint16_t rx_used = 0; /**< number of byte in receive buffer */
static uint8_t tx_buffer[256] = {0}; /**< buffer for data to transmit */
static volatile uint16_t tx_used = 0; /**< number of bytes used in transmit buffer */
volatile bool radio_esp8266_activity = false;
volatile bool radio_esp8266_success = false;
uint8_t radio_esp8266_received[512 + 18];
uint16_t radio_esp8266_received_len = 0;
/** transmit data to radio
* @param[in] data data to transmit
@ -47,26 +52,55 @@ static void radio_esp8266_transmit(uint8_t* data, uint8_t length) {
__WFI(); // sleep until something happened
}
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // ensure transmit interrupt is disable to prevent index corruption (the ISR should already have done it)
rx_used = 0; // reset receive buffer
radio_esp8266_activity = false; // reset status because of new activity
for (tx_used=0; tx_used<length && tx_used<LENGTH(tx_buffer); tx_used++) { // copy data
tx_buffer[tx_used] = data[length-1-tx_used]; // put character in buffer (in reverse order)
for (tx_used = 0; tx_used < length && tx_used < LENGTH(tx_buffer); tx_used++) { // copy data
tx_buffer[tx_used] = data[length - 1 - tx_used]; // put character in buffer (in reverse order)
}
if (tx_used) {
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable interrupt to send bytes
}
}
/** transmit string to radio
* @param[in] data data to transmit
* @param[in] length length of data to transmit
*/
static void radio_esp8266_transmits(char* str) {
if (NULL == str) {
return;
}
const uint16_t length = strlen(str);
radio_esp8266_transmit((uint8_t*)str, length);
}
/** transmit string to radio
* @param[in] data data to transmit
* @param[in] length length of data to transmit
*/
static void radio_esp8266_transmit_at(char* at) {
if (NULL == at) {
return;
}
radio_esp8266_transmits("AT");
radio_esp8266_transmits(at);
radio_esp8266_transmits("\r\n");
}
void radio_esp8266_setup(void)
{
/* enable USART I/O peripheral */
rcc_periph_clock_enable(RCC_AFIO); // enable pin alternate function (USART)
rcc_periph_clock_enable(USART_PORT_RCC(RADIO_ESP8266_USART)); // enable clock for USART port peripheral
rcc_periph_clock_enable(USART_RCC(RADIO_ESP8266_USART)); // enable clock for USART peripheral
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, USART_PIN_TX(RADIO_ESP8266_USART)); // setup GPIO pin USART transmit
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, USART_PIN_RX(RADIO_ESP8266_USART)); // setup GPIO pin USART receive
gpio_set(USART_PORT(RADIO_ESP8266_USART), USART_PIN_RX(RADIO_ESP8266_USART)); // pull up to avoid noise when not connected
// configure pins
rcc_periph_clock_enable(GPIO_RCC(RADIO_ESP8266_TX)); // enable clock for USART TX pin port peripheral
gpio_mode_setup(GPIO_PORT(RADIO_ESP8266_TX), GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN(RADIO_ESP8266_TX)); // set TX pin to alternate function
gpio_set_output_options(GPIO_PORT(RADIO_ESP8266_TX), GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN(RADIO_ESP8266_TX)); // set TX pin output as push-pull
gpio_set_af(GPIO_PORT(RADIO_ESP8266_TX), RADIO_ESP8266_AF, GPIO_PIN(RADIO_ESP8266_TX)); // set alternate function to USART
rcc_periph_clock_enable(GPIO_RCC(RADIO_ESP8266_RX)); // enable clock for USART RX pin port peripheral
gpio_mode_setup(GPIO_PORT(RADIO_ESP8266_RX), GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN(RADIO_ESP8266_RX)); // set GPIO to alternate function, with pull up to avoid noise in case it is not connected
gpio_set_af(GPIO_PORT(RADIO_ESP8266_RX), RADIO_ESP8266_AF, GPIO_PIN(RADIO_ESP8266_RX)); // set alternate function to USART
/* setup USART parameters for ESP8266 AT firmware */
// configure USART for ESP8266 AT firmware
rcc_periph_clock_enable(RCC_USART(RADIO_ESP8266_USART)); // enable clock for USART peripheral
rcc_periph_reset_pulse(RST_USART(RADIO_ESP8266_USART)); // reset peripheral
usart_set_baudrate(USART(RADIO_ESP8266_USART), 115200); // AT firmware 0.51 (SDK 1.5.0) uses 115200 bps
usart_set_databits(USART(RADIO_ESP8266_USART), 8);
usart_set_stopbits(USART(RADIO_ESP8266_USART), USART_STOPBITS_1);
@ -74,9 +108,9 @@ void radio_esp8266_setup(void)
usart_set_parity(USART(RADIO_ESP8266_USART), USART_PARITY_NONE);
usart_set_flow_control(USART(RADIO_ESP8266_USART), USART_FLOWCONTROL_NONE);
nvic_enable_irq(USART_IRQ(RADIO_ESP8266_USART)); // enable the USART interrupt
nvic_enable_irq(USART_IRQ(RADIO_ESP8266_USART)); // enable the UART interrupt
usart_enable_rx_interrupt(USART(RADIO_ESP8266_USART)); // enable receive interrupt
usart_enable(USART(RADIO_ESP8266_USART)); // enable USART
usart_enable(USART(RADIO_ESP8266_USART)); // enable UART
/* reset buffer states */
rx_used = 0;
@ -84,37 +118,88 @@ void radio_esp8266_setup(void)
radio_esp8266_activity = false;
radio_esp8266_success = false;
radio_esp8266_transmit((uint8_t*)"AT\r\n",4); // verify if module is present
radio_esp8266_transmit_at(""); // verify if module is present
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
radio_esp8266_transmit((uint8_t*)"AT+RST\r\n",8); // reset module
radio_esp8266_transmit_at("E0"); // disable echoing
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
while(rx_used<13 || memcmp((char*)&rx_buffer[rx_used-13], "WIFI GOT IP\r\n", 13)!=0) { // wait to have IP
}
void radio_esp8266_reset(void)
{
radio_esp8266_transmit_at("+RST"); // reset module
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
radio_esp8266_transmit_at("E0"); // disable echoing
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
while(rx_used < 13 || memcmp((char*)&rx_buffer[rx_used - 13], "WIFI GOT IP\r\n", 13) != 0) { // wait to have IP
__WFI(); // sleep until something happened
}
radio_esp8266_transmit((uint8_t*)"ATE0\r\n",6); // disable echoing
}
bool radio_esp8266_connected(void)
{
// verify if we ware connect to access point
radio_esp8266_transmit_at("+CWJAP_CUR?");
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (rx_used < 5 || 0 == memcmp((char*)&rx_buffer[rx_used - 5], "No AP", 5)) {
rx_used = 0; // finished using the buffer
return false;
}
// check if we have an IP
radio_esp8266_transmit_at("+CIPSTA_CUR?"); // verify if module is present
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (rx_used < 17 || '0' == rx_buffer[16]) { // check for +CIPSTA_CUR:ip:"0.0.0.0"
rx_used = 0;
return false;
}
rx_used = 0; // clear buffer
return true;
}
void radio_esp8266_tcp_open(char* host, uint16_t port)
{
char command[256] = {0}; // string to create command
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"TCP\",\"%s\",%u\r\n", host, port); // create AT command to establish a TCP connection
if (length>0) {
if (length > 0) {
radio_esp8266_transmit((uint8_t*)command, length);
}
}
bool radio_esp8266_listen(bool udp, uint16_t port)
{
char command[256] = {0}; // string to create command
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"%s\",\"0.0.0.0\",0,%u\r\n", udp ? "UDP" : "TCP", port); // create AT command to establish a listening connection
if (!length) {
return false;
}
radio_esp8266_transmit((uint8_t*)command, length);
while (!radio_esp8266_activity) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) { // send AT command did not succeed
return false;
}
return true;
}
void radio_esp8266_send(uint8_t* data, uint8_t length)
{
char command[16+1] = {0}; // string to create command
char command[16 + 1] = {0}; // string to create command
int command_length = snprintf(command, LENGTH(command), "AT+CIPSEND=%u\r\n", length); // create AT command to send data
if (command_length>0) {
if (command_length > 0) {
radio_esp8266_transmit((uint8_t*)command, command_length); // transmit AT command
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
@ -128,35 +213,58 @@ void radio_esp8266_send(uint8_t* data, uint8_t length)
void radio_esp8266_close(void)
{
radio_esp8266_transmit((uint8_t*)"AT+CIPCLOSE\r\n", 13); // send AT command to close established connection
radio_esp8266_transmit_at("+CIPCLOSE"); // send AT command to close established connection
}
/** USART interrupt service routine called when data has been transmitted or received */
void USART_ISR(RADIO_ESP8266_USART)(void)
{
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // data has been transmitted
if (usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // data has been transmitted
if (tx_used) { // there is still data in the buffer to transmit
usart_send(USART(RADIO_ESP8266_USART),tx_buffer[tx_used-1]); // put data in transmit register
usart_send(USART(RADIO_ESP8266_USART), tx_buffer[tx_used - 1]); // put data in transmit register
tx_used--; // update used size
} else { // no data in the buffer to transmit
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // disable transmit interrupt
}
}
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_RXNE)) { // data has been received
while (rx_used>=LENGTH(rx_buffer)) { // if buffer is full
memmove(rx_buffer,&rx_buffer[1],LENGTH(rx_buffer)-1); // drop old data to make space (ring buffer are more efficient but harder to handle)
if (usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_RXNE)) { // data has been received
while (rx_used >= LENGTH(rx_buffer)) { // if buffer is full
memmove(rx_buffer, &rx_buffer[1], LENGTH(rx_buffer) - 1); // drop old data to make space (ring buffer are more efficient but harder to handle)
rx_used--; // update used buffer information
}
rx_buffer[rx_used++] = usart_recv(USART(RADIO_ESP8266_USART)); // put character in buffer
/*
if ('\n' == rx_buffer[rx_used - 1]) {
rx_buffer[rx_used] = '\0';
printf((char*)rx_buffer);
printf("\n");
}
*/
// if the used send a packet with these strings during the commands detection the AT command response will break (AT commands are hard to handle perfectly)
if (rx_used>=4 && memcmp((char*)&rx_buffer[rx_used-4], "OK\r\n", 4)==0) { // OK received
if (rx_used >= 4 && memcmp((char*)&rx_buffer[rx_used - 4], "OK\r\n", 4) == 0) { // OK received
radio_esp8266_activity = true; // response received
radio_esp8266_success = true; // command succeeded
rx_used = 0; // reset buffer
} else if (rx_used>=7 && memcmp((char*)&rx_buffer[rx_used-7], "ERROR\r\n", 7)==0) { // ERROR received
} else if (rx_used >= 7 && memcmp((char*)&rx_buffer[rx_used - 7], "ERROR\r\n", 7) == 0) { // ERROR received
radio_esp8266_activity = true; // response received
radio_esp8266_success = false; // command failed
rx_used = 0; // reset buffer
} else if (rx_used >= 7 && memcmp((char*)&rx_buffer[0], "\r\n+IPD,", 7) == 0) { // IP data being received
// find if we received the length
int32_t data_length = 0;
uint32_t data_start = 0;
for (uint16_t i = 7; i < rx_used; i++) {
if (':' == rx_buffer[i]) {
data_start = i + 1;
data_length = atoi((char*)&rx_buffer[7]);
break;
}
}
if (data_length > 0 && rx_used >= data_start + data_length) {
if ((uint32_t)data_length <= LENGTH(radio_esp8266_received)) {
memcpy(radio_esp8266_received, &rx_buffer[data_start], data_length);
radio_esp8266_received_len = data_length;
}
rx_used = 0; // reset buffer
}
}
}
}

View File

@ -2,27 +2,42 @@
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2016
* @date 2016-2022
* @note peripherals used: USART @ref radio_esp8266_usart
*/
#pragma once
#error not converted for STM32F4
/** a response has been returned by the radio */
extern volatile bool radio_esp8266_activity;
/** the last command has succeeded */
extern volatile bool radio_esp8266_success;
/** data received (overwritten upon next receive) */
extern uint8_t radio_esp8266_received[];
/** length of receive data */
extern uint16_t radio_esp8266_received_len;
/** setup peripherals to communicate with radio
* @note this is blocking to ensure we are connected to the WiFi network
*/
void radio_esp8266_setup(void);
/** reset ESP */
void radio_esp8266_reset(void);
/** check if connect to the network
* @return if connected to network
*/
bool radio_esp8266_connected(void);
/** establish TCP connection
* @param[in] host host to connect to
* @param[in] port TCP port to connect to
* @note wait for activity to get success status
*/
void radio_esp8266_tcp_open(char* host, uint16_t port);
/** open listening connection
* @param[in] udp if it's an UDP or TCP connection
* @param[in] port port to listen to
* @return if operation succeeded
*/
bool radio_esp8266_listen(bool udp, uint16_t port);
/** send data (requires established connection)
* @param[in] data data to send
* @param[in] length size of data to send

View File

@ -20,6 +20,7 @@
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/usb/usbd.h> // USB library
#include <libopencm3/usb/dfu.h> // USB DFU library
#include <libopencm3/usb/dwc/otg_fs.h> // additional USB definitions
#include "global.h" // global utilities
#include "usb_dfu.h" // USB DFU header and definitions
@ -366,6 +367,8 @@ void usb_dfu_setup(void)
gpio_set_af(GPIOA, GPIO_AF10, GPIO11 | GPIO12); // set alternate function to USB
usb_device = usbd_init(&otgfs_usb_driver, &usb_dfu_device, &usb_dfu_configuration, usb_dfu_strings, LENGTH(usb_dfu_strings), usbd_control_buffer, sizeof(usbd_control_buffer)); // configure USB device
usbd_register_control_callback(usb_device, USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, usb_dfu_control_request); // set control request handling DFU operations
OTG_FS_GCCFG |= OTG_GCCFG_NOVBUSSENS | OTG_GCCFG_PWRDWN; // disable VBUS sensing
OTG_FS_GCCFG &= ~(OTG_GCCFG_VBUSBSEN | OTG_GCCFG_VBUSASEN); // force USB device mode
}
void usb_dfu_start(void)