diff --git a/meson.build b/meson.build
index ae53579f..7162ea64 100644
--- a/meson.build
+++ b/meson.build
@@ -214,6 +214,7 @@ stm32f405_src = ['platform/mcu/STM32F4xx/boot/startup.cpp',
'platform/mcu/STM32F4xx/drivers/rng.c',
'platform/mcu/STM32F4xx/drivers/i2c_stm32.c',
'platform/mcu/STM32F4xx/drivers/spi_stm32.c',
+ 'platform/mcu/STM32F4xx/drivers/adc_stm32.c',
'platform/drivers/audio/stm32_dac.cpp',
'platform/drivers/audio/stm32_adc.cpp',
'platform/drivers/audio/stm32_pwm.cpp',
diff --git a/platform/mcu/STM32F4xx/drivers/adc_stm32.c b/platform/mcu/STM32F4xx/drivers/adc_stm32.c
new file mode 100644
index 00000000..44c55936
--- /dev/null
+++ b/platform/mcu/STM32F4xx/drivers/adc_stm32.c
@@ -0,0 +1,116 @@
+/***************************************************************************
+ * Copyright (C) 2020 - 2023 by Silvano Seva IU2KWO *
+ * and Niccolò Izzo IU2KIN *
+ * *
+ * 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 "adc_stm32.h"
+
+int adcStm32_init(const struct Adc *adc)
+{
+ switch((uint32_t) adc->priv)
+ {
+ case ADC1_BASE:
+ RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
+ __DSB();
+ break;
+
+ case ADC2_BASE:
+ RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
+ __DSB();
+ break;
+
+ case ADC3_BASE:
+ RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
+ __DSB();
+ break;
+
+ default:
+ return -EINVAL;
+ break;
+ }
+
+ ADC_TypeDef *pAdc = ((ADC_TypeDef *) adc->priv);
+
+ /*
+ * ADC clock is APB2 frequency divided by 8, giving 10.5MHz.
+ * We set the sample time of each channel to 84 ADC cycles and we have that
+ * a conversion takes 12 cycles: total conversion time is then of ~9us.
+ */
+ ADC->CCR |= ADC_CCR_ADCPRE;
+ pAdc->SMPR2 = 0x4924924;
+ pAdc->SMPR1 = 0x24924924;
+
+ /*
+ * Convert one channel, no overrun interrupt, 12-bit resolution,
+ * no analog watchdog, discontinuous mode, no end of conversion interrupts,
+ * turn on ADC.
+ */
+ pAdc->SQR1 = 0;
+ pAdc->CR2 = ADC_CR2_ADON;
+
+ if(adc->mutex != NULL)
+ pthread_mutex_init((pthread_mutex_t *) adc->mutex, NULL);
+
+ return 0;
+}
+
+void adcStm32_terminate(const struct Adc *adc)
+{
+ /* A conversion may be in progress, wait until it finishes */
+ if(adc->mutex != NULL)
+ pthread_mutex_lock((pthread_mutex_t *) adc->mutex);
+
+ ((ADC_TypeDef *) adc->priv)->CR2 = 0;
+
+ switch((uint32_t) adc->priv)
+ {
+ case ADC1_BASE:
+ RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN;
+ __DSB();
+ break;
+
+ case ADC2_BASE:
+ RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN;
+ __DSB();
+ break;
+
+ case ADC3_BASE:
+ RCC->APB2ENR &= ~RCC_APB2ENR_ADC3EN;
+ __DSB();
+ break;
+ }
+
+ if(adc->mutex != NULL)
+ pthread_mutex_destroy((pthread_mutex_t *) adc->mutex);
+}
+
+uint16_t adcStm32_sample(const struct Adc *adc, const uint32_t channel)
+{
+ if(channel > 19)
+ return 0;
+
+ ADC_TypeDef *pAdc = ((ADC_TypeDef *) adc->priv);
+
+ pAdc->SQR3 = channel;
+ pAdc->CR2 |= ADC_CR2_SWSTART;
+
+ while((pAdc->SR & ADC_SR_EOC) == 0) ;
+
+ return pAdc->DR;
+}
diff --git a/platform/mcu/STM32F4xx/drivers/adc_stm32.h b/platform/mcu/STM32F4xx/drivers/adc_stm32.h
new file mode 100644
index 00000000..9764cc1c
--- /dev/null
+++ b/platform/mcu/STM32F4xx/drivers/adc_stm32.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+ * Copyright (C) 2024 by 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 ADC_STM32_H
+#define ADC_STM32_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Define an instance of an STM32 ADC device driver.
+ *
+ * @param name: instance name.
+ * @param periph: pointer to hardware peripheral.
+ * @param mutx: pointer to mutex for concurrent access, can be NULL.
+ * @param vref: ADC reference voltage, in uV.
+ */
+#define ADC_STM32_DEVICE_DEFINE(name, periph, mutx, vref) \
+extern uint16_t adcStm32_sample(const struct Adc *adc, \
+ const uint32_t channel); \
+const struct Adc name = \
+{ \
+ .sample = &adcStm32_sample, \
+ .priv = periph, \
+ .mutex = mutx, \
+ .countsTouV = ADC_COUNTS_TO_UV(vref, 12) \
+};
+
+/**
+ * Initialize an STM32 ADC peripheral.
+ *
+ * @param adc: pointer to ADC device handle.
+ * @return zero on success, a negative error code otherwise.
+ */
+int adcStm32_init(const struct Adc *adc);
+
+/**
+ * Shut down an STM32 ADC peripheral.
+ *
+ * @param adc: pointer to ADC device handle.
+ * @return zero on success, a negative error code otherwise.
+ */
+void adcStm32_terminate(const struct Adc *adc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ADC_STM32_H */