summaryrefslogtreecommitdiff
path: root/platforms/chibios/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'platforms/chibios/drivers')
-rw-r--r--platforms/chibios/drivers/analog.c12
-rw-r--r--platforms/chibios/drivers/audio_pwm_hardware.c152
-rw-r--r--platforms/chibios/drivers/serial_protocol.c12
-rw-r--r--platforms/chibios/drivers/uart.c12
-rw-r--r--platforms/chibios/drivers/uart.h46
-rw-r--r--platforms/chibios/drivers/vendor/RP/RP2040/serial_vendor.c24
-rw-r--r--platforms/chibios/drivers/vendor/RP/RP2040/ws2812_vendor.c238
-rw-r--r--platforms/chibios/drivers/ws2812_pwm.c24
-rw-r--r--platforms/chibios/drivers/ws2812_spi.c8
9 files changed, 299 insertions, 229 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/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)
* @param[in] bit: The bit index [0, 7]
*
* @return The bit index
@@ -184,7 +184,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
@@ -196,7 +196,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
@@ -208,7 +208,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)
* @param[in] bit: The bit index [0, 7]
*
* @return The bit index
@@ -221,7 +221,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
@@ -233,7 +233,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
@@ -245,7 +245,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)
* @param[in] bit: The bit index [0, 7]
*
* @return The bit index
diff --git a/platforms/chibios/drivers/ws2812_spi.c b/platforms/chibios/drivers/ws2812_spi.c
index a73eb69720..03ffbd7f82 100644
--- a/platforms/chibios/drivers/ws2812_spi.c
+++ b/platforms/chibios/drivers/ws2812_spi.c
@@ -80,7 +80,7 @@
# define WS2812_CHANNELS 3
#endif
#define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * WS2812_CHANNELS)
-#define DATA_SIZE (BYTES_FOR_LED * RGBLED_NUM)
+#define DATA_SIZE (BYTES_FOR_LED * WS2812_LED_COUNT)
#define RESET_SIZE (1000 * WS2812_TRST_US / (2 * WS2812_TIMING))
#define PREAMBLE_SIZE 4
@@ -181,7 +181,7 @@ void ws2812_init(void) {
spiStart(&WS2812_SPI, &spicfg); /* Setup transfer parameters. */
spiSelect(&WS2812_SPI); /* Slave Select assertion. */
#ifdef WS2812_SPI_USE_CIRCULAR_BUFFER
- spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
+ spiStartSend(&WS2812_SPI, ARRAY_SIZE(txbuf), txbuf);
#endif
}
@@ -200,9 +200,9 @@ void ws2812_setleds(LED_TYPE* ledarray, uint16_t leds) {
// Instead spiSend can be used to send synchronously (or the thread logic can be added back).
#ifndef WS2812_SPI_USE_CIRCULAR_BUFFER
# ifdef WS2812_SPI_SYNC
- spiSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
+ spiSend(&WS2812_SPI, ARRAY_SIZE(txbuf), txbuf);
# else
- spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
+ spiStartSend(&WS2812_SPI, ARRAY_SIZE(txbuf), txbuf);
# endif
#endif
}