From 5aed3e331664695ad68fb5851c6f834ba875ab17 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Sat, 18 Mar 2023 09:34:55 +0100 Subject: [PATCH] Audio device driver for STM32F4xx PWM-based 8-bit DAC emulation --- platform/drivers/audio/stm32_pwm.cpp | 174 +++++++++++++++++++++++++++ platform/drivers/audio/stm32_pwm.h | 69 +++++++++++ 2 files changed, 243 insertions(+) create mode 100644 platform/drivers/audio/stm32_pwm.cpp create mode 100644 platform/drivers/audio/stm32_pwm.h diff --git a/platform/drivers/audio/stm32_pwm.cpp b/platform/drivers/audio/stm32_pwm.cpp new file mode 100644 index 00000000..0239ad12 --- /dev/null +++ b/platform/drivers/audio/stm32_pwm.cpp @@ -0,0 +1,174 @@ +/*************************************************************************** + * Copyright (C) 2023 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 3 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 * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "stm32_pwm.h" + + +using Dma1_Stream2 = DmaStream< DMA1_BASE, 2, 1, 3 >; // DMA 1, Stream 2, channel 1, very high priority + + +static const struct PwmChannelCfg *config; // Config +static struct streamCtx *context; // Current stream context +static StreamHandler stream(Dma1_Stream2::init(10, DataSize::_16BIT, 1)); // DMA stream handler +static Timer tim(TIM7_BASE); // Trigger timebase + + +/** + * \internal + * Stop an ongoing transfer, deactivating timers and DMA stream. + */ +static void stopTransfer() +{ + tim.stop(); + config->stopCbk(); + context->running = 0; +} + +/** + * \internal + * Actual implementation of DMA interrupt handler. + */ +void __attribute__((used)) DMA_Handler() +{ + Dma1_Stream2::IRQhandleInterrupt(&stream); +} + +void __attribute__((naked)) DMA1_Stream2_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z11DMA_Handlerv"); + restoreContext(); +} + + + +void stm32pwm_init() +{ + // Enable peripherals + RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; + __DSB(); + + // Init DMA stream + stream.setEndTransferCallback(stopTransfer); +} + +void stm32pwm_terminate() +{ + if(stream.running()) + stream.halt(); + + RCC->APB1ENR &= ~RCC_APB1ENR_TIM7EN; + __DSB(); +} + +static int stm32pwm_start(const uint8_t instance, const void *cfg, + struct streamCtx *ctx) +{ + (void) instance; + + if(ctx == NULL) + return -EINVAL; + + if((ctx->running != 0) || stream.running()) + return -EBUSY; + + __disable_irq(); + ctx->running = 1; + __enable_irq(); + + context = ctx; + config = reinterpret_cast< const struct PwmChannelCfg *>(cfg); + + /* + * Convert buffer elements from int16_t to unsigned 12 bit values as required + * by the DAC. Processing can be done in-place because the API mandates that + * the function caller does not modify the buffer content once this function + * has been called. + */ + S16toU8(ctx->buffer, ctx->bufSize); + + bool circ = false; + if(ctx->bufMode == BUF_CIRC_DOUBLE) + circ = true; + + stream.start(config->pwmReg, ctx->buffer, ctx->bufSize, circ); + config->startCbk(); + + // Configure trigger + tim.setUpdateFrequency(ctx->sampleRate); + tim.enableDmaTrigger(true); + tim.start(); + + return 0; +} + +static int stm32pwm_idleBuf(struct streamCtx *ctx, stream_sample_t **buf) +{ + *buf = reinterpret_cast< stream_sample_t *>(stream.idleBuf()); + + return ctx->bufSize/2; +} + +static int stm32pwm_sync(struct streamCtx *ctx, uint8_t dirty) +{ + if((ctx->bufMode == BUF_CIRC_DOUBLE) && (dirty != 0)) + { + void *ptr = stream.idleBuf(); + S16toU8(reinterpret_cast< int16_t *>(ptr), ctx->bufSize/2); + } + + bool ok = stream.sync(); + if(ok) return 0; + + return -1; +} + +static void stm32pwm_stop(struct streamCtx *ctx) +{ + if(ctx->running == 0) + return; + + stream.stop(); +} + +static void stm32pwm_halt(struct streamCtx *ctx) +{ + if(ctx->running == 0) + return; + + stream.halt(); +} + +#pragma GCC diagnostic ignored "-Wpedantic" +const struct audioDriver stm32_pwm_audio_driver = +{ + .start = stm32pwm_start, + .data = stm32pwm_idleBuf, + .sync = stm32pwm_sync, + .stop = stm32pwm_stop, + .terminate = stm32pwm_halt +}; +#pragma GCC diagnostic pop diff --git a/platform/drivers/audio/stm32_pwm.h b/platform/drivers/audio/stm32_pwm.h new file mode 100644 index 00000000..da4298e1 --- /dev/null +++ b/platform/drivers/audio/stm32_pwm.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2023 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 3 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 * + ***************************************************************************/ + +#ifndef STM32_PWM_H +#define STM32_PWM_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Driver STM32 PWM used as audio output stream device. + * Input data format is signed 16-bit, internally converted to unsigned 8-bit + * values for compatibility with the hardware. + * + * The driver uses the following peripherals: DMA1_Stream2, TIM7 + */ + +/** + * Data structure holding the configuration for a given DAC channel. + */ +struct PwmChannelCfg +{ + volatile uint32_t *pwmReg; ///< Address of PWM duty cycle register. + void (*startCbk)(void); ///< Callback function for additional setup operations. + void (*stopCbk)(void); ///< Callback function for additional end operations. +}; + + +extern const struct audioDriver stm32_pwm_audio_driver; + + +/** + * Initialize the driver and the peripherals. + */ +void stm32pwm_init(); + +/** + * Shutdown the driver and the peripherals. + */ +void stm32pwm_terminate(); + +#ifdef __cplusplus +} +#endif + +#endif /* STM32_PWM_H */