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 */