[Core] Adjust PWM hardware audio driver for RP2040 (#17723)

This commit is contained in:
Stefan Kerkmann 2022-10-27 19:26:16 +02:00 committed by GitHub
parent efe520645e
commit 19145704e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 94 additions and 145 deletions

View file

@ -159,7 +159,6 @@ A configuration example for the STM32F103C8 would be:
//halconf.h:
#define HAL_USE_PWM TRUE
#define HAL_USE_PAL TRUE
#define HAL_USE_GPT TRUE
#include_next <halconf.h>
```
@ -168,8 +167,6 @@ A configuration example for the STM32F103C8 would be:
#include_next <mcuconf.h>
#undef STM32_PWM_USE_TIM1
#define STM32_PWM_USE_TIM1 TRUE
#undef STM32_GPT_USE_TIM4
#define STM32_GPT_USE_TIM4 TRUE
```
If we now target pin A8, looking through the data-sheet of the STM32F103C8, for the timers and alternate functions
@ -184,7 +181,6 @@ with all this information, the configuration would contain these lines:
#define AUDIO_PIN A8
#define AUDIO_PWM_DRIVER PWMD1
#define AUDIO_PWM_CHANNEL 1
#define AUDIO_STATE_TIMER GPTD4
```
ChibiOS uses GPIOv1 for the F103, which only knows of one alternate function.
@ -208,14 +204,14 @@ You can also change the timer used for software PWM by defining the driver. For
While not an exhaustive list, the following table provides the scenarios that have been partially validated:
| | DAC basic | DAC additive | PWM hardware | PWM software |
|--------------------------|--------------------|--------------------|--------------------|--------------------|
| ------------------------ | ------------------ | ------------------ | ------------------ | ------------------ |
| Atmega32U4 | :o: | :o: | :heavy_check_mark: | :o: |
| RP2040 | :x: | :x: | :heavy_check_mark: | ? |
| STM32F103C8 (bluepill) | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: |
| STM32F303CCT6 (proton-c) | :heavy_check_mark: | :heavy_check_mark: | ? | :heavy_check_mark: |
| STM32F405VG | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| L0xx | :x: (no Tim8) | ? | ? | ? |
:heavy_check_mark: : works and was tested
:o: : does not apply
:x: : not supported by MCU

View file

@ -5,7 +5,7 @@ The following table shows the current driver status for peripherals on RP2040 MC
| System | Support |
| ---------------------------------------------------------------- | ---------------------------------------------- |
| [ADC driver](adc_driver.md) | Support planned (no ETA) |
| [Audio](audio_driver.md) | Support planned (no ETA) |
| [Audio](audio_driver.md#pwm-hardware) | :heavy_check_mark: |
| [Backlight](feature_backlight.md) | :heavy_check_mark: |
| [I2C driver](i2c_driver.md) | :heavy_check_mark: |
| [SPI driver](spi_driver.md) | :heavy_check_mark: |

View file

@ -60,7 +60,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define AUDIO_PWM_DRIVER PWMD3
#define AUDIO_PWM_CHANNEL 4
#define AUDIO_PWM_PAL_MODE 2
#define AUDIO_STATE_TIMER GPTD4
/* serial.c configuration for split keyboard */
#undef SOFT_SERIAL_PIN

View file

@ -21,6 +21,5 @@
#define HAL_USE_SPI TRUE
#define SPI_USE_WAIT TRUE
#define SPI_SELECT_MODE SPI_SELECT_MODE_PAD
#define HAL_USE_GPT TRUE
#include_next <halconf.h>

View file

@ -38,8 +38,5 @@
#undef STM32_SERIAL_USE_USART2
#define STM32_SERIAL_USE_USART2 TRUE
#undef STM32_GPT_USE_TIM4
#define STM32_GPT_USE_TIM4 TRUE
#undef STM32_ST_USE_TIMER
#define STM32_ST_USE_TIMER 5

View file

@ -56,7 +56,6 @@
#define AUDIO_PWM_DRIVER PWMD4
#define AUDIO_PWM_CHANNEL 2
#define AUDIO_PWM_PAL_MODE 2
#define AUDIO_STATE_TIMER GPTD3
#define AUDIO_INIT_DELAY
#define AUDIO_ENABLE_TONE_MULTIPLEXING
#define AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT 10

View file

@ -17,7 +17,6 @@
#define HAL_USE_PWM TRUE
#define HAL_USE_PAL TRUE
#define HAL_USE_GPT TRUE
#define HAL_USE_SERIAL TRUE
// #define HAL_USE_I2C TRUE
#define HAL_USE_SPI TRUE

View file

@ -36,8 +36,5 @@
#undef STM32_SERIAL_USE_USART1
#define STM32_SERIAL_USE_USART1 TRUE
#undef STM32_GPT_USE_TIM3
#define STM32_GPT_USE_TIM3 TRUE
#undef STM32_ST_USE_TIMER
#define STM32_ST_USE_TIMER 5

View file

@ -27,7 +27,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define AUDIO_PWM_DRIVER PWMD2
#define AUDIO_PWM_CHANNEL 3
#define AUDIO_PWM_PAL_MODE 1
#define AUDIO_STATE_TIMER GPTD1
#define AUDIO_CLICKY
#define AUDIO_CLICKY_FREQ_RANDOMNESS 1.5f

View file

@ -23,6 +23,5 @@
#pragma once
#define HAL_USE_PWM TRUE
#define HAL_USE_GPT TRUE
#include_next <halconf.h>

View file

@ -23,8 +23,5 @@
#undef STM32_PWM_USE_TIM4
#define STM32_PWM_USE_TIM4 TRUE
#undef STM32_GPT_USE_TIM1
#define STM32_GPT_USE_TIM1 TRUE
#undef STM32_ST_USE_TIMER
#define STM32_ST_USE_TIMER 5

View file

@ -16,3 +16,7 @@
#define BACKLIGHT_PWM_DRIVER PWMD4
#define BACKLIGHT_PWM_CHANNEL RP2040_PWM_CHANNEL_B
#define AUDIO_PIN GP16
#define AUDIO_PWM_DRIVER PWMD0
#define AUDIO_PWM_CHANNEL RP2040_PWM_CHANNEL_A

View file

@ -5,5 +5,8 @@
#include_next "mcuconf.h"
#undef RP_PWM_USE_PWM0
#define RP_PWM_USE_PWM0 TRUE
#undef RP_PWM_USE_PWM4
#define RP_PWM_USE_PWM4 TRUE

View file

@ -53,7 +53,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define AUDIO_PWM_DRIVER PWMD3
#define AUDIO_PWM_CHANNEL 4
#define AUDIO_PWM_PAL_MODE 2
#define AUDIO_STATE_TIMER GPTD4
/* serial.c configuration for split keyboard */
#define SERIAL_USART_FULL_DUPLEX // Enable full duplex operation mode.

View file

@ -21,6 +21,5 @@
#define HAL_USE_SPI TRUE
#define SPI_USE_WAIT TRUE
#define SPI_SELECT_MODE SPI_SELECT_MODE_PAD
#define HAL_USE_GPT TRUE
#include_next <halconf.h>

View file

@ -38,8 +38,5 @@
#undef STM32_SERIAL_USE_USART2
#define STM32_SERIAL_USE_USART2 TRUE
#undef STM32_GPT_USE_TIM4
#define STM32_GPT_USE_TIM4 TRUE
#undef STM32_ST_USE_TIMER
#define STM32_ST_USE_TIMER 5

View file

@ -46,7 +46,6 @@
#define AUDIO_PWM_PAL_MODE 1
#define AUDIO_PWM_DRIVER PWMD1
#define AUDIO_PWM_CHANNEL 1
#define AUDIO_STATE_TIMER GPTD4
/* RGB LED */
#define RGB_DI_PIN B1

View file

@ -18,9 +18,6 @@
/* PWM for AUDIO and RGB LED */
#define HAL_USE_PWM TRUE
/* GPT and PAL for Audio */
#define HAL_USE_GPT TRUE
#define HAL_USE_PAL TRUE
/* I2C for OLED display */
#define HAL_USE_I2C TRUE

View file

@ -21,9 +21,6 @@
/* TIM1 PWM used for audio driver */
#undef STM32_PWM_USE_TIM1
#define STM32_PWM_USE_TIM1 TRUE
/* TIM5 GPT used for audio driver */
#undef STM32_GPT_USE_TIM4
#define STM32_GPT_USE_TIM4 TRUE
/* TIM3 used for WS2812 driver */
#undef STM32_PWM_USE_TIM3

View file

@ -28,10 +28,18 @@
# define USE_GPIOV1
# define PAL_OUTPUT_TYPE_OPENDRAIN _Static_assert(0, "RP2040 has no Open Drain GPIO configuration, setting this is not possible");
/* Aliases for GPIO PWM channels - every pin has at least one PWM channel
* assigned */
# define RP2040_PWM_CHANNEL_A 1U
# define RP2040_PWM_CHANNEL_B 2U
# define BACKLIGHT_PAL_MODE (PAL_MODE_ALTERNATE_PWM | PAL_RP_PAD_DRIVE12 | PAL_RP_GPIO_OE)
# define BACKLIGHT_PWM_COUNTER_FREQUENCY 1000000
# define BACKLIGHT_PWM_PERIOD BACKLIGHT_PWM_COUNTER_FREQUENCY / 2048
# define AUDIO_PWM_PAL_MODE (PAL_MODE_ALTERNATE_PWM | PAL_RP_PAD_DRIVE12 | PAL_RP_GPIO_OE)
# define AUDIO_PWM_COUNTER_FREQUENCY 500000
# define usb_lld_endpoint_fields
# define I2C1_SCL_PAL_MODE (PAL_MODE_ALTERNATE_I2C | PAL_RP_PAD_SLEWFAST | PAL_RP_PAD_PUE | PAL_RP_PAD_DRIVE4)
@ -55,6 +63,7 @@
# define USE_GPIOV1
# define PAL_MODE_ALTERNATE_OPENDRAIN PAL_MODE_STM32_ALTERNATE_OPENDRAIN
# define PAL_MODE_ALTERNATE_PUSHPULL PAL_MODE_STM32_ALTERNATE_PUSHPULL
# define AUDIO_PWM_PAL_MODE PAL_MODE_ALTERNATE_PUSHPULL
# else
# define PAL_OUTPUT_TYPE_OPENDRAIN PAL_STM32_OTYPE_OPENDRAIN
# define PAL_OUTPUT_TYPE_PUSHPULL PAL_STM32_OTYPE_PUSHPULL
@ -76,6 +85,7 @@
# define USE_I2CV1
# define PAL_MODE_ALTERNATE_OPENDRAIN PAL_MODE_GD32_ALTERNATE_OPENDRAIN
# define PAL_MODE_ALTERNATE_PUSHPULL PAL_MODE_GD32_ALTERNATE_PUSHPULL
# define AUDIO_PWM_PAL_MODE PAL_MODE_GD32_ALTERNATE_PUSHPULL
# endif
#endif

View file

@ -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/>.
*/
// 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.
*/
// 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) {
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,
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);
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);
#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);
}
void audio_driver_start(void) {
channel_1_stop();
channel_1_start();
if (playing_note || playing_melody) {
gptStartContinuous(&AUDIO_STATE_TIMER, 64);
}
}
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) {
// 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, 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
chVTObjectInit(&audio_vt);
}
void audio_driver_start(void) {
channel_1_stop();
channel_1_start();
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();
chVTReset(&audio_vt);
}

View file

@ -35,8 +35,3 @@
#define GP28 28U
#define GP29 29U
#define GP30 30U
/* Aliases for GPIO PWM channels - every pin has at least one PWM channel
* assigned */
#define RP2040_PWM_CHANNEL_A 1U
#define RP2040_PWM_CHANNEL_B 2U