diff options
Diffstat (limited to 'platforms/chibios/drivers')
-rw-r--r-- | platforms/chibios/drivers/analog.c | 12 | ||||
-rw-r--r-- | platforms/chibios/drivers/audio_pwm_hardware.c | 152 | ||||
-rw-r--r-- | platforms/chibios/drivers/serial_protocol.c | 12 | ||||
-rw-r--r-- | platforms/chibios/drivers/uart.c | 12 | ||||
-rw-r--r-- | platforms/chibios/drivers/uart.h | 46 | ||||
-rw-r--r-- | platforms/chibios/drivers/vendor/RP/RP2040/serial_vendor.c | 24 | ||||
-rw-r--r-- | platforms/chibios/drivers/vendor/RP/RP2040/ws2812_vendor.c | 238 | ||||
-rw-r--r-- | platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h | 4 | ||||
-rw-r--r-- | platforms/chibios/drivers/ws2812_pwm.c | 24 | ||||
-rw-r--r-- | platforms/chibios/drivers/ws2812_spi.c | 8 |
10 files changed, 302 insertions, 230 deletions
diff --git a/platforms/chibios/drivers/analog.c b/platforms/chibios/drivers/analog.c index a7b7ec76d7..8b03e73849 100644 --- a/platforms/chibios/drivers/analog.c +++ b/platforms/chibios/drivers/analog.c @@ -22,7 +22,7 @@ # error "You need to set HAL_USE_ADC to TRUE in your halconf.h to use the ADC." #endif -#if !STM32_ADC_USE_ADC1 && !STM32_ADC_USE_ADC2 && !STM32_ADC_USE_ADC3 && !STM32_ADC_USE_ADC4 +#if !STM32_ADC_USE_ADC1 && !STM32_ADC_USE_ADC2 && !STM32_ADC_USE_ADC3 && !STM32_ADC_USE_ADC4 && !WB32_ADC_USE_ADC1 # error "You need to set one of the 'STM32_ADC_USE_ADCx' settings to TRUE in your mcuconf.h to use the ADC." #endif @@ -37,7 +37,7 @@ // Otherwise assume V3 #if defined(STM32F0XX) || defined(STM32L0XX) # define USE_ADCV1 -#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) +#elif defined(STM32F1XX) || defined(STM32F2XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) # define USE_ADCV2 #endif @@ -74,7 +74,7 @@ /* User configurable ADC options */ #ifndef ADC_COUNT -# if defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) +# if defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F4XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) # define ADC_COUNT 1 # elif defined(STM32F3XX) # define ADC_COUNT 4 @@ -121,7 +121,7 @@ static ADCConversionGroup adcConversionGroup = { .cfgr1 = ADC_CFGR1_CONT | ADC_RESOLUTION, .smpr = ADC_SAMPLING_RATE, #elif defined(USE_ADCV2) -# if !defined(STM32F1XX) && !defined(GD32VF103) +# if !defined(STM32F1XX) && !defined(GD32VF103) && !defined(WB32F3G71xx) && !defined(WB32FQ95xx) .cr2 = ADC_CR2_SWSTART, // F103 seem very unhappy with, F401 seems very unhappy without... # endif .smpr2 = ADC_SMPR2_SMP_AN0(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN1(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN2(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN3(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN4(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN5(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN6(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN7(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN8(ADC_SAMPLING_RATE) | ADC_SMPR2_SMP_AN9(ADC_SAMPLING_RATE), @@ -219,7 +219,7 @@ __attribute__((weak)) adc_mux pinToMux(pin_t pin) { case F9: return TO_MUX( ADC_CHANNEL_IN7, 2 ); case F10: return TO_MUX( ADC_CHANNEL_IN8, 2 ); # endif -#elif defined(STM32F1XX) || defined(GD32VF103) +#elif defined(STM32F1XX) || defined(GD32VF103) || defined(WB32F3G71xx) || defined(WB32FQ95xx) case A0: return TO_MUX( ADC_CHANNEL_IN0, 0 ); case A1: return TO_MUX( ADC_CHANNEL_IN1, 0 ); case A2: return TO_MUX( ADC_CHANNEL_IN2, 0 ); @@ -248,7 +248,7 @@ __attribute__((weak)) adc_mux pinToMux(pin_t pin) { static inline ADCDriver* intToADCDriver(uint8_t adcInt) { switch (adcInt) { -#if STM32_ADC_USE_ADC1 +#if STM32_ADC_USE_ADC1 || WB32_ADC_USE_ADC1 case 0: return &ADCD1; #endif diff --git a/platforms/chibios/drivers/audio_pwm_hardware.c b/platforms/chibios/drivers/audio_pwm_hardware.c index 710f397609..54dac46605 100644 --- a/platforms/chibios/drivers/audio_pwm_hardware.c +++ b/platforms/chibios/drivers/audio_pwm_hardware.c @@ -1,29 +1,15 @@ -/* Copyright 2020 Jack Humbert - * Copyright 2020 JohSchneider - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/* -Audio Driver: PWM - -the duty-cycle is always kept at 50%, and the pwm-period is adjusted to match the frequency of a note to be played back. - -this driver uses the chibios-PWM system to produce a square-wave on specific output pins that are connected to the PWM hardware. -The hardware directly toggles the pin via its alternate function. see your MCUs data-sheet for which pin can be driven by what timer - looking for TIMx_CHy and the corresponding alternate function. - - */ +// Copyright 2022 Stefan Kerkmann +// Copyright 2020 Jack Humbert +// Copyright 2020 JohSchneider +// SPDX-License-Identifier: GPL-2.0-or-later + +// Audio Driver: PWM the duty-cycle is always kept at 50%, and the pwm-period is +// adjusted to match the frequency of a note to be played back. This driver uses +// the chibios-PWM system to produce a square-wave on specific output pins that +// are connected to the PWM hardware. The hardware directly toggles the pin via +// its alternate function. see your MCUs data-sheet for which pin can be driven +// by what timer - looking for TIMx_CHy and the corresponding alternate +// function. #include "audio.h" #include "ch.h" @@ -33,53 +19,36 @@ The hardware directly toggles the pin via its alternate function. see your MCUs # error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings" #endif +#if !defined(AUDIO_PWM_COUNTER_FREQUENCY) +# define AUDIO_PWM_COUNTER_FREQUENCY 100000 +#endif + extern bool playing_note; extern bool playing_melody; extern uint8_t note_timbre; -static PWMConfig pwmCFG = { - .frequency = 100000, /* PWM clock frequency */ - // CHIBIOS-BUG? can't set the initial period to <2, or the pwm (hard or software) takes ~130ms with .frequency=500000 for a pwmChangePeriod to take effect; with no output=silence in the meantime - .period = 2, /* initial PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */ - .callback = NULL, /* no callback, the hardware directly toggles the pin */ - .channels = - { -#if AUDIO_PWM_CHANNEL == 4 - {PWM_OUTPUT_DISABLED, NULL}, /* channel 0 -> TIMx_CH1 */ - {PWM_OUTPUT_DISABLED, NULL}, /* channel 1 -> TIMx_CH2 */ - {PWM_OUTPUT_DISABLED, NULL}, /* channel 2 -> TIMx_CH3 */ - {PWM_OUTPUT_ACTIVE_HIGH, NULL} /* channel 3 -> TIMx_CH4 */ -#elif AUDIO_PWM_CHANNEL == 3 - {PWM_OUTPUT_DISABLED, NULL}, - {PWM_OUTPUT_DISABLED, NULL}, - {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH3 */ - {PWM_OUTPUT_DISABLED, NULL} -#elif AUDIO_PWM_CHANNEL == 2 - {PWM_OUTPUT_DISABLED, NULL}, - {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH2 */ - {PWM_OUTPUT_DISABLED, NULL}, - {PWM_OUTPUT_DISABLED, NULL} -#else /*fallback to CH1 */ - {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH1 */ - {PWM_OUTPUT_DISABLED, NULL}, - {PWM_OUTPUT_DISABLED, NULL}, - {PWM_OUTPUT_DISABLED, NULL} -#endif - }, -}; +static PWMConfig pwmCFG = {.frequency = AUDIO_PWM_COUNTER_FREQUENCY, /* PWM clock frequency */ + .period = 2, + .callback = NULL, + .channels = {[(AUDIO_PWM_CHANNEL - 1)] = {.mode = PWM_OUTPUT_ACTIVE_HIGH, .callback = NULL}}}; static float channel_1_frequency = 0.0f; -void channel_1_set_frequency(float freq) { + +void channel_1_set_frequency(float freq) { channel_1_frequency = freq; - if (freq <= 0.0) // a pause/rest has freq=0 + if (freq <= 0.0) { + // a pause/rest has freq=0 return; + } pwmcnt_t period = (pwmCFG.frequency / freq); - pwmChangePeriod(&AUDIO_PWM_DRIVER, period); - pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1, - // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH - PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100)); + chSysLockFromISR(); + pwmChangePeriodI(&AUDIO_PWM_DRIVER, period); + pwmEnableChannelI(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1, + // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH + PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100)); + chSysUnlockFromISR(); } float channel_1_get_frequency(void) { @@ -95,54 +64,53 @@ void channel_1_stop(void) { pwmStop(&AUDIO_PWM_DRIVER); } -static void gpt_callback(GPTDriver *gptp); -GPTConfig gptCFG = { - /* a whole note is one beat, which is - per definition in musical_notes.h - set to 64 - the longest note is BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4 - the tempo (which might vary!) is in bpm (beats per minute) - therefore: if the timer ticks away at .frequency = (60*64)Hz, - and the .interval counts from 64 downwards - audio_update_state is - called just often enough to not miss any notes - */ - .frequency = 60 * 64, - .callback = gpt_callback, -}; +static virtual_timer_t audio_vt; +static void audio_callback(virtual_timer_t *vtp, void *p); + +// a regular timer task, that checks the note to be currently played and updates +// the pwm to output that frequency. +static void audio_callback(virtual_timer_t *vtp, void *p) { + float freq; // TODO: freq_alt + + if (audio_update_state()) { + freq = audio_get_processed_frequency(0); // freq_alt would be index=1 + channel_1_set_frequency(freq); + } + + chSysLockFromISR(); + chVTSetI(&audio_vt, TIME_MS2I(16), audio_callback, NULL); + chSysUnlockFromISR(); +} void audio_driver_initialize(void) { pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); // connect the AUDIO_PIN to the PWM hardware -#if defined(USE_GPIOV1) // STM32F103C8 - palSetLineMode(AUDIO_PIN, PAL_MODE_ALTERNATE_PUSHPULL); +#if defined(USE_GPIOV1) // STM32F103C8, RP2040 + palSetLineMode(AUDIO_PIN, AUDIO_PWM_PAL_MODE); #else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command) palSetLineMode(AUDIO_PIN, PAL_MODE_ALTERNATE(AUDIO_PWM_PAL_MODE)); #endif - gptStart(&AUDIO_STATE_TIMER, &gptCFG); + chVTObjectInit(&audio_vt); } void audio_driver_start(void) { channel_1_stop(); channel_1_start(); - if (playing_note || playing_melody) { - gptStartContinuous(&AUDIO_STATE_TIMER, 64); + if ((playing_note || playing_melody) && !chVTIsArmed(&audio_vt)) { + // a whole note is one beat, which is - per definition in + // musical_notes.h - set to 64 the longest note is + // BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4 the tempo (which + // might vary!) is in bpm (beats per minute) therefore: if the timer + // ticks away at 64Hz (~16.6ms) audio_update_state is called just often + // enough to not miss any notes + chVTSet(&audio_vt, TIME_MS2I(16), audio_callback, NULL); } } void audio_driver_stop(void) { channel_1_stop(); - gptStopTimer(&AUDIO_STATE_TIMER); -} - -/* a regular timer task, that checks the note to be currently played - * and updates the pwm to output that frequency - */ -static void gpt_callback(GPTDriver *gptp) { - float freq; // TODO: freq_alt - - if (audio_update_state()) { - freq = audio_get_processed_frequency(0); // freq_alt would be index=1 - channel_1_set_frequency(freq); - } + chVTReset(&audio_vt); } diff --git a/platforms/chibios/drivers/serial_protocol.c b/platforms/chibios/drivers/serial_protocol.c index c95aed9885..ccaf73282d 100644 --- a/platforms/chibios/drivers/serial_protocol.c +++ b/platforms/chibios/drivers/serial_protocol.c @@ -102,15 +102,11 @@ static inline bool react_to_transaction(void) { * @return bool Indicates success of transaction. */ bool soft_serial_transaction(int index) { - bool result = initiate_transaction((uint8_t)index); + /* Clear the receive queue, to start with a clean slate. + * Parts of failed transactions or spurious bytes could still be in it. */ + serial_transport_driver_clear(); - if (unlikely(!result)) { - /* Clear the receive queue, to start with a clean slate. - * Parts of failed transactions or spurious bytes could still be in it. */ - serial_transport_driver_clear(); - } - - return result; + return initiate_transaction((uint8_t)index); } /** diff --git a/platforms/chibios/drivers/uart.c b/platforms/chibios/drivers/uart.c index 396803f33b..b16130d80b 100644 --- a/platforms/chibios/drivers/uart.c +++ b/platforms/chibios/drivers/uart.c @@ -18,7 +18,9 @@ #include "quantum.h" -#if defined(WB32F3G71xx) || defined(WB32FQ95xx) +#if defined(MCU_KINETIS) +static SerialConfig serialConfig = {SERIAL_DEFAULT_BITRATE}; +#elif defined(WB32F3G71xx) || defined(WB32FQ95xx) static SerialConfig serialConfig = {SERIAL_DEFAULT_BITRATE, SD1_WRDLEN, SD1_STPBIT, SD1_PARITY, SD1_ATFLCT}; #else static SerialConfig serialConfig = {SERIAL_DEFAULT_BITRATE, SD1_CR1, SD1_CR2, SD1_CR3}; @@ -30,11 +32,15 @@ void uart_init(uint32_t baud) { if (!is_initialised) { is_initialised = true; +#if defined(MCU_KINETIS) + serialConfig.sc_speed = baud; +#else serialConfig.speed = baud; +#endif #if defined(USE_GPIOV1) - palSetLineMode(SD1_TX_PIN, PAL_MODE_ALTERNATE_OPENDRAIN); - palSetLineMode(SD1_RX_PIN, PAL_MODE_ALTERNATE_OPENDRAIN); + palSetLineMode(SD1_TX_PIN, SD1_TX_PAL_MODE); + palSetLineMode(SD1_RX_PIN, SD1_RX_PAL_MODE); #else palSetLineMode(SD1_TX_PIN, PAL_MODE_ALTERNATE(SD1_TX_PAL_MODE) | PAL_OUTPUT_TYPE_OPENDRAIN); palSetLineMode(SD1_RX_PIN, PAL_MODE_ALTERNATE(SD1_RX_PAL_MODE) | PAL_OUTPUT_TYPE_OPENDRAIN); diff --git a/platforms/chibios/drivers/uart.h b/platforms/chibios/drivers/uart.h index 603d51037b..db97840270 100644 --- a/platforms/chibios/drivers/uart.h +++ b/platforms/chibios/drivers/uart.h @@ -28,32 +28,50 @@ # define SD1_TX_PIN A9 #endif -#ifndef SD1_TX_PAL_MODE -# define SD1_TX_PAL_MODE 7 -#endif - #ifndef SD1_RX_PIN # define SD1_RX_PIN A10 #endif -#ifndef SD1_RX_PAL_MODE -# define SD1_RX_PAL_MODE 7 -#endif - #ifndef SD1_CTS_PIN # define SD1_CTS_PIN A11 #endif -#ifndef SD1_CTS_PAL_MODE -# define SD1_CTS_PAL_MODE 7 -#endif - #ifndef SD1_RTS_PIN # define SD1_RTS_PIN A12 #endif -#ifndef SD1_RTS_PAL_MODE -# define SD1_RTS_PAL_MODE 7 +#ifdef USE_GPIOV1 +# ifndef SD1_TX_PAL_MODE +# define SD1_TX_PAL_MODE PAL_MODE_ALTERNATE_OPENDRAIN +# endif + +# ifndef SD1_RX_PAL_MODE +# define SD1_RX_PAL_MODE PAL_MODE_ALTERNATE_OPENDRAIN +# endif + +# ifndef SD1_CTS_PAL_MODE +# define SD1_CTS_PAL_MODE PAL_MODE_ALTERNATE_OPENDRAIN +# endif + +# ifndef SD1_RTS_PAL_MODE +# define SD1_RTS_PAL_MODE PAL_MODE_ALTERNATE_OPENDRAIN +# endif +#else +# ifndef SD1_TX_PAL_MODE +# define SD1_TX_PAL_MODE 7 +# endif + +# ifndef SD1_RX_PAL_MODE +# define SD1_RX_PAL_MODE 7 +# endif + +# ifndef SD1_CTS_PAL_MODE +# define SD1_CTS_PAL_MODE 7 +# endif + +# ifndef SD1_RTS_PAL_MODE +# define SD1_RTS_PAL_MODE 7 +# endif #endif #ifndef SD1_CR1 diff --git a/platforms/chibios/drivers/vendor/RP/RP2040/serial_vendor.c b/platforms/chibios/drivers/vendor/RP/RP2040/serial_vendor.c index 764764b3f9..dd4723a086 100644 --- a/platforms/chibios/drivers/vendor/RP/RP2040/serial_vendor.c +++ b/platforms/chibios/drivers/vendor/RP/RP2040/serial_vendor.c @@ -140,9 +140,8 @@ void pio_serve_interrupt(void) { // strength is chosen because the transmitting side must still be able to drive // the signal low. With this configuration the rise times are fast enough and // the generated low level with 360mV will generate a logical zero. -static inline void enter_rx_state(void) { +static void __no_inline_not_in_flash_func(enter_rx_state)(void) { osalSysLock(); - nvicEnableVector(RP_USBCTRL_IRQ_NUMBER, RP_IRQ_USB0_PRIORITY); // Wait for the transmitting state machines FIFO to run empty. At this point // the last byte has been pulled from the transmitting state machines FIFO // into the output shift register. We have to wait a tiny bit more until @@ -162,11 +161,8 @@ static inline void enter_rx_state(void) { osalSysUnlock(); } -static inline void leave_rx_state(void) { +static void __no_inline_not_in_flash_func(leave_rx_state)(void) { osalSysLock(); - // We don't want to be interrupted by frequent (1KHz) USB interrupts while - // doing our timing critical sending operation. - nvicDisableVector(RP_USBCTRL_IRQ_NUMBER); // In Half-duplex operation the tx pin dual-functions as sender and // receiver. To not receive the data we will send, we disable the receiving // state machine. @@ -185,12 +181,13 @@ static inline void leave_rx_state(void) {} #endif /** - * @brief Clear the RX and TX hardware FIFOs of the state machines. + * @brief Clear the FIFO of the RX state machine. */ inline void serial_transport_driver_clear(void) { osalSysLock(); - pio_sm_clear_fifos(pio, rx_state_machine); - pio_sm_clear_fifos(pio, tx_state_machine); + while (!pio_sm_is_rx_fifo_empty(pio, rx_state_machine)) { + pio_sm_clear_fifos(pio, rx_state_machine); + } osalSysUnlock(); } @@ -198,11 +195,6 @@ static inline msg_t sync_tx(sysinterval_t timeout) { msg_t msg = MSG_OK; osalSysLock(); while (pio_sm_is_tx_fifo_full(pio, tx_state_machine)) { -#if !defined(SERIAL_USART_FULL_DUPLEX) - // Enable USB interrupts again, because we might sleep for a long time - // here and don't want to be disconnected from the host. - nvicEnableVector(RP_USBCTRL_IRQ_NUMBER, RP_IRQ_USB0_PRIORITY); -#endif pio_set_irq0_source_enabled(pio, pis_sm0_tx_fifo_not_full + tx_state_machine, true); msg = osalThreadSuspendTimeoutS(&tx_thread, timeout); if (msg < MSG_OK) { @@ -210,10 +202,6 @@ static inline msg_t sync_tx(sysinterval_t timeout) { break; } } -#if !defined(SERIAL_USART_FULL_DUPLEX) - // Entering timing critical territory again. - nvicDisableVector(RP_USBCTRL_IRQ_NUMBER); -#endif osalSysUnlock(); return msg; } diff --git a/platforms/chibios/drivers/vendor/RP/RP2040/ws2812_vendor.c b/platforms/chibios/drivers/vendor/RP/RP2040/ws2812_vendor.c index bc34eded14..bc03213f3b 100644 --- a/platforms/chibios/drivers/vendor/RP/RP2040/ws2812_vendor.c +++ b/platforms/chibios/drivers/vendor/RP/RP2040/ws2812_vendor.c @@ -1,4 +1,4 @@ -// Copyright 2022 Stefan Kerkmann +// Copyright 2022 Stefan Kerkmann (@KarlK90) // SPDX-License-Identifier: GPL-2.0-or-later #include "quantum.h" @@ -17,53 +17,156 @@ static const PIO pio = pio0; #endif #if !defined(RP_DMA_PRIORITY_WS2812) -# define RP_DMA_PRIORITY_WS2812 12 +# define RP_DMA_PRIORITY_WS2812 3 #endif -static int state_machine = -1; +#if defined(WS2812_EXTERNAL_PULLUP) +# pragma message "The GPIOs of the RP2040 are NOT 5V tolerant! Make sure to NOT apply any voltage over 3.3V to the RGB data pin." +#endif -#define WS2812_WRAP_TARGET 0 -#define WS2812_WRAP 3 +/*================== WS2812 PIO TIMINGS =================*/ -#define WS2812_T1 2 -#define WS2812_T2 5 -#define WS2812_T3 3 +// WS2812_T1L rounded to 50ns intervals and split into two wait timings +#define PIO_T1L (WS2812_T1L / 50) +#define PIO_T1L_A (MAX(CEILING(PIO_T1L, 2) - 1, 0)) +#define PIO_T1L_B (MAX(PIO_T1L / 2 - 1, 0)) -#if defined(WS2812_EXTERNAL_PULLUP) +// WS2812_T0L rounded to 50ns intervals +#define PIO_T0L (MAX(WS2812_T0L / 50 - PIO_T1L, 0)) +#define PIO_T0L_A (MAX(PIO_T0L - 1, 0)) -# pragma message "The GPIOs of the RP2040 are NOT 5V tolerant! Make sure to NOT apply any voltage over 3.3V to the RGB data pin." +// WS2812_T0H rounded to 50ns intervals +#define PIO_T0H (WS2812_T0H / 50) +#define PIO_T0H_A MAX(PIO_T0H - 1, 0) -// clang-format off -static const uint16_t ws2812_program_instructions[] = { - // .wrap_target - 0x7221, // 0: out x, 1 side 1 [2] - 0x0123, // 1: jmp !x, 3 side 0 [1] - 0x0400, // 2: jmp 0 side 0 [4] - 0xb442, // 3: nop side 1 [4] - // .wrap -}; +// WS2812_T1H rounded to 50ns intervals and split into two wait timings +#define PIO_T1H (MAX(WS2812_T1H / 50 - PIO_T0H, 0)) +#define PIO_T1H_A (MAX((CEILING(PIO_T1H, 2) - 1), 0)) +#define PIO_T1H_B (MAX((PIO_T1H / 2) - 1, 0)) -#else +#if (WS2812_T0L % 50) != 0 +# pragma message "WS2812_T0L is not given in an 50ns interval, it will be rounded to the next 50ns" +#endif + +#if (WS2812_T0H % 50) != 0 +# pragma message "WS2812_T0H is not given in an 50ns interval, it will be rounded to the next 50ns" +#endif + +#if (WS2812_T1L % 50) != 0 +# pragma message "WS2812_T0L is not given in an 50ns interval, it will be rounded to the next 50ns" +#endif + +#if (WS2812_T1H % 50) != 0 +# pragma message "WS2812_T0H is not given in an 50ns interval, it will be rounded to the next 50ns" +#endif + +#if WS2812_T0L < WS2812_T1L +# error WS2812_T0L is shorter than WS2812_T1L, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T1H < WS2812_T0H +# error WS2812_T1H is shorter than WS2812_T0H, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T0L > (850 + WS2812_T1L) +# error WS2812_T0L is longer than 850ns + WS2812_T1L, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T0H > 850 +# error WS2812_T0H is longer than 850ns, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T1H > (1700 + WS2812_T0H) +# error WS2812_T1H is longer than 1700ns + WS2812_T0H, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T1L > 1700 +# error WS2812_T1L is longer than 1700ns, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T0L < (50 + WS2812_T1L) +# error WS2812_T0L is shorter than 50ns + WS2812_T1L, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T0H < 50 +# error WS2812_T0H is shorter than 50ns, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T1H < (100 + WS2812_T0H) +# error WS2812_T1H is longer than 100ns + WS2812_T0H, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +#if WS2812_T1L < 100 +# error WS2812_T1L is longer than 1700ns, this is impossible to express in the RP2040 PIO driver. Please correct your timings. +#endif + +/** + * @brief Helper macro to binary patch the delay part of an per-compiled PIO + * opcode. + */ +#define PIO_DELAY(delay, opcode) (((delay & 0xF) << 8U) | opcode) + +#define WS2812_WRAP_TARGET 0 +#define WS2812_WRAP 5 static const uint16_t ws2812_program_instructions[] = { - // .wrap_target - 0x6221, // 0: out x, 1 side 0 [2] - 0x1123, // 1: jmp !x, 3 side 1 [1] - 0x1400, // 2: jmp 0 side 1 [4] - 0xa442, // 3: nop side 0 [4] - // .wrap + // .wrap_target + PIO_DELAY(PIO_T1L_A, 0x6021), // 0: out x, 1 side 0 // T1L (max. 1700ns) + PIO_DELAY(PIO_T1L_B, 0xa042), // 1: nop side 0 // T1L + PIO_DELAY(PIO_T0H_A, 0x1025), // 2: jmp !x, 5 side 1 // T0H (max. 850ns) + PIO_DELAY(PIO_T1H_A, 0xb042), // 3: nop side 1 // T1H (max. 1700ns + T0H) + PIO_DELAY(PIO_T1H_B, 0x1000), // 4: jmp 0 side 1 // T1H + PIO_DELAY(PIO_T0L_A, 0xa042), // 5: nop side 0 // T0L (max. 850ns + T1L) + // .wrap }; -// clang-format on -#endif static const pio_program_t ws2812_program = { .instructions = ws2812_program_instructions, - .length = 4, + .length = ARRAY_SIZE(ws2812_program_instructions), .origin = -1, }; -static uint32_t WS2812_BUFFER[RGBLED_NUM]; +static uint32_t WS2812_BUFFER[WS2812_LED_COUNT]; static const rp_dma_channel_t* WS2812_DMA_CHANNEL; +static uint32_t RP_DMA_MODE_WS2812; +static int STATE_MACHINE = -1; + +static SEMAPHORE_DECL(TRANSFER_COUNTER, 1); +static rtcnt_t LAST_TRANSFER; + +/** + * @brief Convert RGBW value into WS2812 compatible 32-bit data word. + */ +__always_inline static uint32_t rgbw8888_to_u32(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) { +#if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB) + return ((uint32_t)green << 24) | ((uint32_t)red << 16) | ((uint32_t)blue << 8) | ((uint32_t)white); +#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB) + return ((uint32_t)red << 24) | ((uint32_t)green << 16) | ((uint32_t)blue << 8) | ((uint32_t)white); +#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR) + return ((uint32_t)blue << 24) | ((uint32_t)green << 16) | ((uint32_t)red << 8) | ((uint32_t)white); +#endif +} + +static void ws2812_dma_callback(void* p, uint32_t ct) { + // We assume that there is at least one frame left in the OSR even if the TX + // FIFO is already empty. + rtcnt_t time_to_completion = (pio_sm_get_tx_fifo_level(pio, STATE_MACHINE) + 1) * MAX(WS2812_T1H + WS2812_T1L, WS2812_T0H + WS2812_T0L); + +#if defined(RGBW) + time_to_completion *= 32; +#else + time_to_completion *= 24; +#endif + + // Convert from ns to us + time_to_completion /= 1000; + + LAST_TRANSFER = chSysGetRealtimeCounterX() + time_to_completion + WS2812_TRST_US; + + osalSysLockFromISR(); + chSemSignalI(&TRANSFER_COUNTER); + osalSysUnlockFromISR(); +} bool ws2812_init(void) { uint pio_idx = pio_get_index(pio); @@ -73,20 +176,23 @@ bool ws2812_init(void) { // clang-format off iomode_t rgb_pin_mode = PAL_RP_PAD_SLEWFAST | PAL_RP_GPIO_OE | +#if defined(WS2812_EXTERNAL_PULLUP) + PAL_RP_IOCTRL_OEOVER_DRVINVPERI | +#endif (pio_idx == 0 ? PAL_MODE_ALTERNATE_PIO0 : PAL_MODE_ALTERNATE_PIO1); // clang-format on palSetLineMode(RGB_DI_PIN, rgb_pin_mode); - state_machine = pio_claim_unused_sm(pio, true); - if (state_machine < 0) { + STATE_MACHINE = pio_claim_unused_sm(pio, true); + if (STATE_MACHINE < 0) { dprintln("ERROR: Failed to acquire state machine for WS2812 output!"); return false; } uint offset = pio_add_program(pio, &ws2812_program); - pio_sm_set_consecutive_pindirs(pio, state_machine, RGB_DI_PIN, 1, true); + pio_sm_set_consecutive_pindirs(pio, STATE_MACHINE, RGB_DI_PIN, 1, true); pio_sm_config config = pio_get_default_sm_config(); sm_config_set_wrap(&config, offset + WS2812_WRAP_TARGET, offset + WS2812_WRAP); @@ -113,57 +219,44 @@ bool ws2812_init(void) { sm_config_set_out_shift(&config, false, true, 24); #endif - int cycles_per_bit = WS2812_T1 + WS2812_T2 + WS2812_T3; - float div = clock_get_hz(clk_sys) / (800.0f * KHZ * cycles_per_bit); + // Every instruction takes 50ns to execute with a clock speed of 20 MHz, + // giving the WS2812 PIO driver its time resolution + float div = clock_get_hz(clk_sys) / (20.0f * MHZ); sm_config_set_clkdiv(&config, div); - pio_sm_init(pio, state_machine, offset, &config); - pio_sm_set_enabled(pio, state_machine, true); + pio_sm_init(pio, STATE_MACHINE, offset, &config); + pio_sm_set_enabled(pio, STATE_MACHINE, true); - WS2812_DMA_CHANNEL = dmaChannelAlloc(RP_DMA_CHANNEL_ID_ANY, RP_DMA_PRIORITY_WS2812, NULL, NULL); + WS2812_DMA_CHANNEL = dmaChannelAlloc(RP_DMA_CHANNEL_ID_ANY, RP_DMA_PRIORITY_WS2812, (rp_dmaisr_t)ws2812_dma_callback, NULL); + dmaChannelEnableInterruptX(WS2812_DMA_CHANNEL); + dmaChannelSetDestinationX(WS2812_DMA_CHANNEL, (uint32_t)&pio->txf[STATE_MACHINE]); // clang-format off - uint32_t mode = DMA_CTRL_TRIG_INCR_READ | - DMA_CTRL_TRIG_DATA_SIZE_WORD | - DMA_CTRL_TRIG_IRQ_QUIET | - DMA_CTRL_TRIG_TREQ_SEL(pio_idx == 0 ? state_machine : state_machine + 8); + RP_DMA_MODE_WS2812 = DMA_CTRL_TRIG_INCR_READ | + DMA_CTRL_TRIG_DATA_SIZE_WORD | + DMA_CTRL_TRIG_TREQ_SEL(pio == pio0 ? STATE_MACHINE : STATE_MACHINE + 8) | + DMA_CTRL_TRIG_PRIORITY(RP_DMA_PRIORITY_WS2812); // clang-format on - dmaChannelSetModeX(WS2812_DMA_CHANNEL, mode); - dmaChannelSetDestinationX(WS2812_DMA_CHANNEL, (uint32_t)&pio->txf[state_machine]); return true; } -/** - * @brief Convert RGBW value into WS2812 compatible 32-bit data word. - */ -__always_inline static uint32_t rgbw8888_to_u32(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) { -#if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB) - return ((uint32_t)green << 24) | ((uint32_t)red << 16) | ((uint32_t)blue << 8) | ((uint32_t)white); -#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB) - return ((uint32_t)red << 24) | ((uint32_t)green << 16) | ((uint32_t)blue << 8) | ((uint32_t)white); -#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR) - return ((uint32_t)blue << 24) | ((uint32_t)green << 16) | ((uint32_t)red << 8) | ((uint32_t)white); -#endif -} - static inline void sync_ws2812_transfer(void) { - if (unlikely(dmaChannelIsBusyX(WS2812_DMA_CHANNEL) || !pio_sm_is_tx_fifo_empty(pio, state_machine))) { - fast_timer_t start = timer_read_fast(); - do { - // Abort the synchronization if we have to wait longer than the total - // count of LEDs in millisecounds. This is safely much longer than it - // would take to push all the data out. - if (unlikely(timer_elapsed_fast(start) > RGBLED_NUM)) { - dprintln("ERROR: WS2812 DMA transfer has stalled, aborting!"); - dmaChannelDisableX(WS2812_DMA_CHANNEL); - return; - } - - } while (dmaChannelIsBusyX(WS2812_DMA_CHANNEL) || !pio_sm_is_tx_fifo_empty(pio, state_machine)); - // We wait for the WS2812 chain to reset after all data has been pushed - // out. + if (chSemWaitTimeout(&TRANSFER_COUNTER, TIME_MS2I(WS2812_LED_COUNT)) == MSG_TIMEOUT) { + // Abort the synchronization if we have to wait longer than the total + // count of LEDs in milliseconds. This is safely much longer than it + // would take to push all the data out. + dprintln("ERROR: WS2812 DMA transfer has stalled, aborting!"); + dmaChannelDisableX(WS2812_DMA_CHANNEL); + pio_sm_clear_fifos(pio, STATE_MACHINE); + pio_sm_restart(pio, STATE_MACHINE); + chSemReset(&TRANSFER_COUNTER, 0); wait_us(WS2812_TRST_US); + return; + } + + // Busy wait until last transfer has finished + while (unlikely(!timer_expired32(chSysGetRealtimeCounterX(), LAST_TRANSFER))) { } } @@ -185,5 +278,6 @@ void ws2812_setleds(LED_TYPE* ledarray, uint16_t leds) { dmaChannelSetSourceX(WS2812_DMA_CHANNEL, (uint32_t)WS2812_BUFFER); dmaChannelSetCounterX(WS2812_DMA_CHANNEL, leds); + dmaChannelSetModeX(WS2812_DMA_CHANNEL, RP_DMA_MODE_WS2812); dmaChannelEnableX(WS2812_DMA_CHANNEL); } diff --git a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h index 244c87cb7f..e74cf85efd 100644 --- a/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h +++ b/platforms/chibios/drivers/wear_leveling/wear_leveling_efl_config.h @@ -20,7 +20,9 @@ # if defined(STM32_FLASH_LINE_SIZE) // from some family's stm32_registry.h file # define BACKING_STORE_WRITE_SIZE (STM32_FLASH_LINE_SIZE) # else -# if defined(QMK_MCU_SERIES_STM32F1XX) +# if defined(QMK_MCU_SERIES_STM32F0XX) +# define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c +# elif defined(QMK_MCU_SERIES_STM32F1XX) # define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c # elif defined(QMK_MCU_SERIES_STM32F3XX) # define BACKING_STORE_WRITE_SIZE 2 // from hal_efl_lld.c diff --git a/platforms/chibios/drivers/ws2812_pwm.c b/platforms/chibios/drivers/ws2812_pwm.c index 792de85ce9..c4a591c10b 100644 --- a/platforms/chibios/drivers/ws2812_pwm.c +++ b/platforms/chibios/drivers/ws2812_pwm.c @@ -88,8 +88,8 @@ */ #define WS2812_COLOR_BITS (WS2812_CHANNELS * 8) #define WS2812_RESET_BIT_N (1000 * WS2812_TRST_US / WS2812_TIMING) -#define WS2812_COLOR_BIT_N (RGBLED_NUM * WS2812_COLOR_BITS) /**< Number of data bits */ -#define WS2812_BIT_N (WS2812_COLOR_BIT_N + WS2812_RESET_BIT_N) /**< Total number of bits in a frame */ +#define WS2812_COLOR_BIT_N (WS2812_LED_COUNT * WS2812_COLOR_BITS) /**< Number of data bits */ +#define WS2812_BIT_N (WS2812_COLOR_BIT_N + WS2812_RESET_BIT_N) /**< Total number of bits in a frame */ /** * @brief High period for a zero, in ticks @@ -133,7 +133,7 @@ /** * @brief Determine the index in @ref ws2812_frame_buffer "the frame buffer" of a given bit * - * @param[in] led: The led index [0, @ref RGBLED_NUM) + * @param[in] led: The led index [0, @ref WS2812_LED_COUNT) * @param[in] byte: The byte number [0, 2] * @param[in] bit: The bit number [0, 7] * @@ -147,7 +147,7 @@ * * @note The red byte is the middle byte in the color packet * - * @param[in] led: The led index [0, @ref RGBLED_NUM) + * @param[in] led: The led index [0, @ref WS2812_LED_COUNT) * @param[in] bit: The bit number [0, 7] * * @return The bit index @@ -159,7 +159,7 @@ * * @note The red byte is the first byte in the color packet * - * @param[in] led: The led index [0, @ref RGBLED_NUM) + * @param[in] led: The led index [0, @ref WS2812_LED_COUNT) * @param[in] bit: The bit number [0, 7] * * @return The bit index @@ -171,7 +171,7 @@ * * @note The red byte is the last byte in the color packet * - * @param[in] led: The led index [0, @ref RGBLED_NUM) + * @param[in] led: The led index [0, @ref WS2812_LED_COUNT) |