diff --git a/platformio.ini b/platformio.ini
index 040aa5f736256f4b46772c1def8b9a3027d11591..5f90dae9621cfe1e22fce37b7b58305212680b5e 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -17,7 +17,7 @@ extra_configs = src/owntech.ini
 
 framework = zephyr
 
-platform = ststm32@14.1.0
+platform = ststm32@14.2.0
 
 # Serial monitor baud rate
 monitor_speed = 115200
diff --git a/zephyr/dts/adc-channels.dtsi b/zephyr/dts/adc-channels.dtsi
index b2727ceba18e85f205be9a5922a509e7fdefcc63..b7b5acd452e495766773ab9af86a9921035b67d8 100644
--- a/zephyr/dts/adc-channels.dtsi
+++ b/zephyr/dts/adc-channels.dtsi
@@ -1,34 +1,80 @@
 / {
 	mychannels: adc-inputs {
 		compatible = "adc-inputs";
-		v1-low {
+
+		/* Voltage channels */
+
+		v1-low-adc1 {
+			io-channels = <&adc1 1>;
+			label = "V1_LOW";
+		};
+
+		v1-low-adc2 {
 			io-channels = <&adc2 1>;
 			label = "V1_LOW";
 		};
 
-		i1-low {
-			io-channels = <&adc1 2>;
-			label = "I1_LOW";
+		v2-low-adc1 {
+			io-channels = <&adc1 6>;
+			label = "V2_LOW";
 		};
 
-		v2-low {
+		v2-low-adc2 {
 			io-channels = <&adc2 6>;
 			label = "V2_LOW";
 		};
 
-		i2-low {
-			io-channels = <&adc1 7>;
-			label = "I2_LOW";
+		v-high-adc1 {
+
+			io-channels = <&adc1 9>;
+			label = "V_HIGH";
 		};
 
-		v-high {
+		v-high-adc2 {
 			io-channels = <&adc2 9>;
 			label = "V_HIGH";
 		};
 
-		i-high {
+		/* Current channels */
+
+		i1-low-adc1 {
+			io-channels = <&adc1 2>;
+			label = "I1_LOW";
+		};
+
+		i1-low-adc2 {
+			io-channels = <&adc2 2>;
+			label = "I1_LOW";
+		};
+
+		i2-low-adc1 {
+			io-channels = <&adc1 7>;
+			label = "I2_LOW";
+		};
+
+		i2-low-adc2 {
+			io-channels = <&adc2 7>;
+			label = "I2_LOW";
+		};
+
+		i-high-adc1 {
 			io-channels = <&adc1 8>;
 			label = "I_HIGH";
 		};
+
+		i-high-adc2 {
+			io-channels = <&adc2 8>;
+			label = "I_HIGH";
+		};
+
+		/* Temperature channel */
+
+		/*
+		temp-sensor {
+			io-channels = <&adc3 1>;
+			label = "TEMP_SENSOR";
+		};
+		*/
+
 	};
 };
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/CMakeLists.txt b/zephyr/modules/owntech_data_acquisition/zephyr/CMakeLists.txt
index 5c9d9cb90d5624b6768cfe5b05a0da76af60164a..a49b0624d96360eb8cce77994eaa7ccd79bbc8c4 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/CMakeLists.txt
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/CMakeLists.txt
@@ -1,6 +1,6 @@
 
 if(CONFIG_OWNTECH_DATA_ACQUISITION)
-  zephyr_include_directories(.)
+  zephyr_include_directories(./public_include)
 
   zephyr_library()
   zephyr_library_sources(
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.c b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.c
index bde6cb59e09088d8c404e702b8236918a42829c5..fdf683f4aeea4ae4eb35ea1783756235bf034c9a 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.c
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.c
@@ -22,6 +22,9 @@
  */
 
 
+// Stdlib
+#include <stdint.h>
+
 // STM32 LL
 #include <stm32g4xx_ll_adc.h>
 
@@ -29,87 +32,86 @@
 #include "adc_channels.h"
 #include "adc_core.h"
 
-
 /////
-// Private functions
-
-/**
- * ADC 1 OwnTech's specific configuration.
- */
-static void _adc_configure_adc_1()
-{
-    // Set regular sequence length
-    LL_ADC_REG_SetSequencerLength(ADC1, adc_channels_get_channels_count(1)-1);
-
-    // TO TEST
-    // Set discontinuous mode: first event triggers first channel conversion,
-    // next event will convert the next channel in the sequence, and so on
-    // until all channels are converted, then restart from fisrt channel.
-    // RM: 21.4.20
-    //LL_ADC_REG_SetSequencerDiscont(ADC1, LL_ADC_REG_SEQ_DISCONT_1RANK);
-
-    // Enable dma and circular mode
-    LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
-
-    /////
-    // Set trigger source: only for ADC 1 as we operate in dual mode
-    // (ADC 2 triggered by ADC 1)
-
-    // Enable external trigger on hrtim_adc_trg1
-    LL_ADC_REG_SetTriggerEdge(ADC1, LL_ADC_REG_TRIG_EXT_RISING);
-
-    // RM Table 163. adc_ext_trg21 hrtim_adc_trg1 EXTSEL = 0x10101
-    LL_ADC_REG_SetTriggerSource(ADC1, LL_ADC_REG_TRIG_EXT_HRTIM_TRG1);
-}
-
-/**
- * ADC 2 OwnTech's specific configuration.
- */
-static void _adc_configure_adc_2()
-{
-    // Set regular sequence length
-    LL_ADC_REG_SetSequencerLength(ADC2, adc_channels_get_channels_count(2)-1);
-
-    // TO TEST
-    // Set discontinuous mode: first event triggers first channel conversion,
-    // next event will convert the next channel in the sequence, and so on
-    // until all channels are converted, then restart from fisrt channel.
-    // RM: 21.4.20
-    //LL_ADC_REG_SetSequencerDiscont(ADC2, LL_ADC_REG_SEQ_DISCONT_1RANK);
-
-    // Enable dma and circular mode
-    LL_ADC_REG_SetDMATransfer(ADC2, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
-}
+// Local variables
+static uint32_t adc_trigger_sources[3];
 
 
 /////
 // Public API
 
-/**
- * Configure ADC and DMA according to OwnTech
- * board requirements.
- */
 void adc_init()
 {
-    // Initialize ADC
-    adc_core_init();
-    adc_core_set_dual_mode();
+	for (int i = 0 ; i < 3 ; i++)
+	{
+		adc_trigger_sources[i] = 0;
+	}
 
-    // Initialize channels
-    adc_channels_init();
+	adc_core_init();
+	adc_channels_init();
+}
 
-    // Enable ADC
-    adc_core_enable(ADC1);
-    adc_core_enable(ADC2);
+void adc_set_dual_mode(uint8_t dual_mode)
+{
+	adc_core_set_dual_mode(dual_mode);
+}
 
-    // Perform post-enable ADC configuration
-    adc_channels_configure(ADC1);
-    adc_channels_configure(ADC2);
+void adc_configure_trigger_source(uint8_t adc_number, uint32_t trigger_source)
+{
+	// Only store configuration: it must be applied after ADC enable
+	if (adc_number < 3)
+		adc_trigger_sources[adc_number-1] = trigger_source;
+}
 
-    _adc_configure_adc_1();
-    _adc_configure_adc_2();
+int8_t adc_configure_adc_channels(uint8_t adc_number, char* channel_list[], uint8_t channel_count)
+{
+	return adc_channnels_configure_adc_channels(adc_number, channel_list, channel_count);
+}
 
-    // Finally, start ADCs
-    adc_core_start(ADC1);
-    adc_core_start(ADC2);
+void adc_start()
+{
+	uint8_t enabled_channels_count[3];
+
+	for (uint8_t i = 0 ; i < 3 ; i++)
+	{
+		enabled_channels_count[i] = adc_channels_get_enabled_channels_count(i+1);
+	}
+
+	/////
+	// Enable ADCs
+	for (uint8_t i = 0 ; i < 3 ; i++)
+	{
+		if (enabled_channels_count[i] > 0)
+			adc_core_enable(i+1);
+	}
+
+	/////
+	// Configure ADCs channels
+	for (uint8_t i = 0 ; i < 3 ; i++)
+	{
+		if (enabled_channels_count[i] > 0)
+			adc_channels_configure(i+1);
+	}
+
+	/////
+	// Configure ADCs
+	for (uint8_t i = 0 ; i < 3 ; i++)
+	{
+		if (enabled_channels_count[i] > 0)
+			adc_core_configure_dma_mode(i+1);
+	}
+
+	for (uint8_t i = 0 ; i < 3 ; i++)
+	{
+		if ( (enabled_channels_count[i] > 0) && (adc_trigger_sources[i] != 0) )
+			adc_core_configure_trigger_source(i+1, LL_ADC_REG_TRIG_EXT_RISING, adc_trigger_sources[i]);
+	}
+
+	/////
+	// Finally, start ADCs
+	for (uint8_t i = 0 ; i < 3 ; i++)
+	{
+		if (enabled_channels_count[i] > 0)
+			adc_core_start(i+1);
+	}
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.h b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.h
index a06013a7d91b3866b13a3d85aa58eb601abd160a..506061dfdb63f0383201af1ed1d4732e52d17047 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.h
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc.h
@@ -19,22 +19,79 @@
 
 /**
  * @author Clément Foucher <clement.foucher@laas.fr>
- * @brief  This is the main include for ADC configuration.
+ *
+ * @brief  This is an ad-hoc ADC driver for OwnTech's
+ * application. It supports differential channel setup
+ * unlike Zephyr's STM32 driver.
+ * It configures ADC 1 and ADC 2, using a common clock
+ * which is AHB clock with a prescaler division by 4.
+ * ADC 3 is also enabled independently.
+ *
+ * To use this driver, first call adc_init(), then call
+ * required configuration functions, then call adc_start().
  */
 
+
 #ifndef ADC_H_
 #define ADC_H_
 
+
+#include <stdint.h>
+
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 
 /**
- * @brief   Initializes ADC1 and ADC2 driver.
+ * @brief Initializes the ADCs. It must be
+ * called *before* any configuration is made.
  */
 void adc_init();
 
+/**
+ * ADC dual mode: enables ADC 1/ADC 2 synchronization.
+ * When ADC 1 acquisition is triggered, it simultaneously
+ * triggers an acquisition on ADC 2.
+ *
+ * @param dual_mode true to enable dual moode, false to
+ *        disable it. false by default.
+ */
+void adc_set_dual_mode(uint8_t dual_mode);
+
+/**
+ * Regsters the triger source for an ADC.
+ * It will be applied when ADC is started.
+ *
+ * @param adc_number Number of the ADC to configure
+ * @param triggger_source Source of the trigger as defined
+ *        in stm32gxx_ll_adc.h (LL_ADC_REG_TRIG_***)
+ */
+void adc_configure_trigger_source(uint8_t adc_number, uint32_t trigger_source);
+
+/**
+ * This function is used to configure the channels to be
+ * enabled on a given ADC.
+ *
+ * @param  adc_number Number of the ADC on which channel configuration is
+ *         to be done.
+ * @param  channel_list List of channels to configure. This is a list of
+ *         names as defined in the device tree (field `label`). The order
+ *         of the names in the array sets the acquisition ranks (order in
+ *         which the channels are acquired).
+ * @param  channel_count Number of channels defined in `channel_list`.
+ * @return 0 is everything went well,
+ *         ECHANNOTFOUND if at least one of the channels
+ *           is not available in the given ADC. Available channels are the
+ *           ones defined in the device tree.
+ */
+int8_t adc_configure_adc_channels(uint8_t adc_number, char* channel_list[], uint8_t channel_count);
+
+/**
+ * @brief Starts all configured ADCs.
+ */
+void adc_start();
 
 #ifdef __cplusplus
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.c b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.c
index 0e0f9357719ac8a52617a8f7916118b27034f416..ce458cc75d824ca646bde24e88793f3e9eb1c416 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.c
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.c
@@ -27,75 +27,161 @@
 
 // Stdlib
 #include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+// Zephyr
+#include <zephyr.h>
 
 // Current file header
-#include "adc_channels_private.h"
+#include "adc_channels.h"
 
 // OwnTech API
+#include "data_acquisition.h"
 #include "adc_helper.h"
-#include "adc_core.h"
+
+
+/////
+// Device-tree related defines
+
+typedef struct
+{
+	char*   name;
+	bool    is_differential;
+	uint8_t number;
+	char*   adc;
+} channel_prop_t;
+
+#define ADC_INPUTS_NODELABEL DT_NODELABEL(mychannels)
+
+// Channel properties
+#define CHANNEL_NAME(node_id)    DT_LABEL(node_id)
+#define CHANNEL_IS_DIFF(node_id) DT_PROP(node_id, differential)
+#define CHANNEL_NUMBER(node_id)  DT_PHA_BY_IDX(node_id, io_channels, 0, input)
+#define CHANNEL_ADC(node_id)     DT_PROP_BY_PHANDLE_IDX(node_id, io_channels, 0, label)
+
+#define CHANNEL_WRITE_PROP(node_id)                \
+	{                                              \
+		.name=CHANNEL_NAME(node_id),               \
+		.is_differential=CHANNEL_IS_DIFF(node_id), \
+		.number=CHANNEL_NUMBER(node_id),           \
+		.adc=CHANNEL_ADC(node_id)                  \
+	},
+
+// Channel count. This is very dirty!
+#define CHANNEL_COUNTER(node_id) +1
+#define CHANNEL_COUNT (DT_FOREACH_CHILD(ADC_INPUTS_NODELABEL, CHANNEL_COUNTER))
 
 
 /////
 // Variables
 
-static channel_prop_t channels_props[] =
+static channel_prop_t available_channels_props[] =
 {
-    DT_FOREACH_CHILD(ADC_INPUTS_NODELABEL, CHANNEL_WRITE_PROP)
+	DT_FOREACH_CHILD(ADC_INPUTS_NODELABEL, CHANNEL_WRITE_PROP)
 };
 
-static uint8_t channels_in_adc1_count;
-static uint8_t channels_in_adc2_count;
+// Number of available channels defined in device tree
+static uint8_t adc1_available_channels_count;
+static uint8_t adc2_available_channels_count;
+static uint8_t adc3_available_channels_count;
+
+// List of available channels as defined in device tree.
+// These are arrays (range 0 to adcX_available_channels_count-1)
+// of pointers to channel definition in available_channels_props array.
+static channel_prop_t** adc1_available_channels_list;
+static channel_prop_t** adc2_available_channels_list;
+static channel_prop_t** adc3_available_channels_list;
 
-static channel_prop_t** adc1_channels_list;
-static channel_prop_t** adc2_channels_list;
+// Number of enabled channels defined by the user configuration
+static uint8_t adc1_enabled_channels_count;
+static uint8_t adc2_enabled_channels_count;
+static uint8_t adc3_enabled_channels_count;
+
+// List of channels enabled by user configuration.
+// These are arrays (range 0 to adcX_enabled_channels_count-1)
+// of pointers to channel definition in available_channels_props array.
+static channel_prop_t** adc1_enabled_channels_list;
+static channel_prop_t** adc2_enabled_channels_list;
+static channel_prop_t** adc3_enabled_channels_list;
 
 
 /////
 // ADC channels private functions
 
 /**
- * Counts device-tree configured channels in each ADC.
+ * Builds list of device-tree defined channels for each ADC.
+ */
+static void _adc_channels_build_available_channels_lists()
+{
+	// Count total number of channels
+	adc1_available_channels_count = 0;
+	adc2_available_channels_count = 0;
+	adc3_available_channels_count = 0;
+
+	for (int i = 0 ; i < CHANNEL_COUNT ; i++)
+	{
+		uint8_t adc_number = _get_adc_number_by_name(available_channels_props[i].adc);
+		if (adc_number == 1)
+		{
+			adc1_available_channels_count++;
+		}
+		else if (adc_number == 2)
+		{
+			adc2_available_channels_count++;
+		}
+		else if (adc_number == 3)
+		{
+			adc3_available_channels_count++;
+		}
+	}
+
+	// Build a list of channels by ADC
+	adc1_available_channels_list = k_malloc(sizeof(channel_prop_t*) * adc1_available_channels_count);
+	adc2_available_channels_list = k_malloc(sizeof(channel_prop_t*) * adc2_available_channels_count);
+	adc3_available_channels_list = k_malloc(sizeof(channel_prop_t*) * adc3_available_channels_count);
+
+	int adc1_index = 0;
+	int adc2_index = 0;
+	int adc3_index = 0;
+	for (int i = 0 ; i < CHANNEL_COUNT ; i++)
+	{
+		uint8_t adc_number = _get_adc_number_by_name(available_channels_props[i].adc);
+		if (adc_number == 1)
+		{
+			adc1_available_channels_list[adc1_index] = &available_channels_props[i];
+			adc1_index++;
+		}
+		else if (adc_number == 2)
+		{
+			adc2_available_channels_list[adc2_index] = &available_channels_props[i];
+			adc2_index++;
+		}
+		else if (adc_number == 3)
+		{
+			adc3_available_channels_list[adc3_index] = &available_channels_props[i];
+			adc3_index++;
+		}
+	}
+}
+
+/**
+ * ADC differential channel set-up:
+ * Applies differential mode to specified channel.
+ * Refer to RM 21.4.7
  */
-static void _adc_channels_count()
-{
-    // Count total number of channels
-    channels_in_adc1_count = 0;
-    channels_in_adc2_count = 0;
-
-    for (int i = 0 ; i < CHANNEL_COUNT ; i++)
-    {
-        ADC_TypeDef* adc = _get_adc_by_name(channels_props[i].adc);
-        if (adc == ADC1)
-        {
-            channels_in_adc1_count++;
-        }
-        else if (adc == ADC2)
-        {
-            channels_in_adc2_count++;
-        }
-    }
-
-    // Build a list of channels by ADC
-    adc1_channels_list = malloc(sizeof(channel_prop_t*) * channels_in_adc1_count);
-    adc2_channels_list = malloc(sizeof(channel_prop_t*) * channels_in_adc2_count);
-
-    int adc1_index = 0;
-    int adc2_index = 0;
-    for (int i = 0 ; i < CHANNEL_COUNT ; i++)
-    {
-        ADC_TypeDef* adc = _get_adc_by_name(channels_props[i].adc);
-        if (adc == ADC1)
-        {
-            adc1_channels_list[adc1_index] = &channels_props[i];
-            adc1_index++;
-        }
-        else if (adc == ADC2)
-        {
-            adc2_channels_list[adc2_index] = &channels_props[i];
-            adc2_index++;
-        }
-    }
+static void _adc_channels_set_channel_differential(char* adc_name, uint8_t channel)
+{
+	ADC_TypeDef* adc = _get_adc_by_name(adc_name);
+
+	if (adc != NULL)
+	{
+		LL_ADC_SetChannelSingleDiff(adc,
+		                            __LL_ADC_DECIMAL_NB_TO_CHANNEL(channel),
+		                            LL_ADC_DIFFERENTIAL_ENDED
+		                           );
+	}
+
 }
 
 /**
@@ -104,148 +190,266 @@ static void _adc_channels_count()
  */
 static void _adc_channels_differential_setup()
 {
-    for (int i = 0; i < CHANNEL_COUNT; i++)
-    {
-        if (channels_props[i].is_differential)
-        {
-            ADC_TypeDef* adc = _get_adc_by_name(channels_props[i].adc);
-            if (adc != NULL)
-            {
-                adc_core_set_channel_differential(adc, channels_props[i].number);
-            }
-        }
-    }
+	for (int i = 0; i < CHANNEL_COUNT; i++)
+	{
+		if (available_channels_props[i].is_differential)
+		{
+			_adc_channels_set_channel_differential(available_channels_props[i].adc, available_channels_props[i].number);
+		}
+	}
 }
 
-/**
- * Internal path setup.
- * Currently done before ADC is enabled,
- * but is is correct? No hint in RM on this.
- */
-void _adc_channels_internal_path_setup()
-{
-    uint8_t vts  = 0;
-    uint8_t vbat = 0;
-    uint8_t vref = 0;
-
-    for (int i = 0 ; i < CHANNEL_COUNT ; i++)
-    {
-        if (channels_props[i].number == __LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_TEMPSENSOR_ADC1))
-            vts = 1;
-        if (channels_props[i].number == __LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_VBAT))
-            vbat = 1;
-        if (channels_props[i].number == __LL_ADC_CHANNEL_TO_DECIMAL_NB(LL_ADC_CHANNEL_VREFINT))
-            vref = 1;
-    }
-
-    adc_core_configure_internal_paths(vts, vbat, vref);
+static channel_prop_t** _adc_channels_get_enabled_channels_list(uint8_t adc_num)
+{
+	channel_prop_t** enabled_channels_list = NULL;
+	switch (adc_num)
+	{
+		case 1:
+			enabled_channels_list = adc1_enabled_channels_list;
+			break;
+		case 2:
+			enabled_channels_list = adc2_enabled_channels_list;
+			break;
+		case 3:
+			enabled_channels_list = adc3_enabled_channels_list;
+			break;
+	}
+	return enabled_channels_list;
+}
+
+static channel_prop_t** _adc_channels_get_available_channels_list(uint8_t adc_num)
+{
+	channel_prop_t** available_channels_list = NULL;
+	switch (adc_num)
+	{
+		case 1:
+			available_channels_list = adc1_available_channels_list;
+			break;
+		case 2:
+			available_channels_list = adc2_available_channels_list;
+			break;
+		case 3:
+			available_channels_list = adc3_available_channels_list;
+			break;
+	}
+	return available_channels_list;
+}
+
+static uint8_t _adc_channels_get_available_channels_count(uint8_t adc_num)
+{
+	uint8_t available_channels_count = 0;
+	switch (adc_num)
+	{
+		case 1:
+			available_channels_count = adc1_available_channels_count;
+			break;
+		case 2:
+			available_channels_count = adc2_available_channels_count;
+			break;
+		case 3:
+			available_channels_count = adc3_available_channels_count;
+			break;
+	}
+	return available_channels_count;
+}
+
+static uint8_t _adc_channels_get_enabled_channels_count(uint8_t adc_num)
+{
+	uint8_t enabled_channels_count = 0;
+	switch (adc_num)
+	{
+		case 1:
+			enabled_channels_count = adc1_enabled_channels_count;
+			break;
+		case 2:
+			enabled_channels_count = adc2_enabled_channels_count;
+			break;
+		case 3:
+			enabled_channels_count = adc3_enabled_channels_count;
+			break;
+	}
+	return enabled_channels_count;
+}
+
+static void _adc_channels_set_enabled_channels(uint8_t adc_num, channel_prop_t** enabled_channels, uint8_t enabled_channels_count)
+{
+	switch (adc_num)
+	{
+		case 1:
+			adc1_enabled_channels_list = enabled_channels;
+			adc1_enabled_channels_count = enabled_channels_count;
+			break;
+		case 2:
+			adc2_enabled_channels_list = enabled_channels;
+			adc2_enabled_channels_count = enabled_channels_count;
+			break;
+		case 3:
+			adc3_enabled_channels_list = enabled_channels;
+			adc3_enabled_channels_count = enabled_channels_count;
+			break;
+	}
+}
+
+static channel_prop_t* _adc_channels_get_available_channel_by_name(uint8_t adc_num, char* channel_name)
+{
+	channel_prop_t** current_adc_available_channels = _adc_channels_get_available_channels_list(adc_num);
+
+	for (int i = 0 ; i < _adc_channels_get_available_channels_count(adc_num) ; i++)
+	{
+		if (strcmp(current_adc_available_channels[i]->name, channel_name) == 0)
+		{
+			return current_adc_available_channels[i];
+		}
+	}
+
+	return NULL;
 }
 
 
 /////
-// ADC channls public functions
+// ADC channels public functions
 
-/**
- * Performs internal data structures initialization
- * and pre-ADC enable init.
- * Must be called before adc_core_enable()
- */
 void adc_channels_init()
 {
-    _adc_channels_count();
-    _adc_channels_differential_setup();
-    _adc_channels_internal_path_setup();
+	_adc_channels_build_available_channels_lists();
+	_adc_channels_differential_setup();
 }
 
-/**
- * ADC channel configuration.
- * Sets sequencer ranks and channels sampling time.
- */
-void adc_channels_configure(ADC_TypeDef* adc)
-{
-    uint8_t channel;
-    uint8_t rank = 1;
-    ADC_TypeDef* channel_adc;
-
-    for (int i = 0; i < CHANNEL_COUNT; i++)
-    {
-        channel = channels_props[i].number;
-        channel_adc = _get_adc_by_name(channels_props[i].adc);
-        // Regular sequencer
-        if (channel_adc == adc)
-        {
-            LL_ADC_REG_SetSequencerRanks(adc,
-                                         adc_decimal_nb_to_rank(rank),
-                                         __LL_ADC_DECIMAL_NB_TO_CHANNEL(channel)
-                                        );
-            rank++;
-
-            // Channels sampling time
-
-            /* 000: 2.5 ADC clock cycles
-             * 001: 6.5 ADC clock cycles
-             * 010: 12.5 ADC clock cycles
-             * 011: 24.5 ADC clock cycles
-             * 100: 47.5 ADC clock cycles
-             * 101: 92.5 ADC clock cycles
-             * 110: 247.5 ADC clock cycles
-             * 111: 640.5 ADC clock cycles
-             */
-            /* Vrefint minimum sampling time : 4us
-             */
-            /* Vts minimum sampling time : 5us
-             */
-            /* For 0b110:
-             * Tadc_clk = 1 / 42.5 MHz = 23.5 ns
-             * Tsar = 12.5 * Tadc_clk = 293.75 ns
-             * Tsmpl = 247.5 * Tadc_clk = 5816.25 ns
-             * Tconv = Tsmpl + Tsar = 6.11 us
-             * -> Fconv up to 163.6 KSPS for 1 channel per ADC
-             * Fconv up to 27.2 KSPS with the 6 channels actally
-             * used on the ADC1
-             *
-             * For 0b001 (ok for voltage):
-             * Tadc_clk = 1 / 42.5 MHz = 23.5 ns
-             * Tsar = 12.5 * Tadc_clk = 293.75 ns
-             * Tsmpl = 6.5 * Tadc_clk = 152.75 ns
-             * Tconv = Tsmpl + Tsar = 446.4 ns
-             * -> Fconv up to 2239 KSPS for 1 channel per ADC
-             * Fconv up to 373 KSPS with the 6 channels actally
-             * used on the ADC1
-             *
-             * For 0b101 (ok for current):
-             * Tadc_clk = 1 / 42.5 MHz = 23.5 ns
-             * Tsar = 12.5 * Tadc_clk = 293.75 ns
-             * Tsmpl = 92.5 * Tadc_clk = 2173.75 ns
-             * Tconv = Tsmpl + Tsar = 2.47 µs
-             * -> Fconv up to 404 KSPS for 1 channel per ADC
-             * Fconv up to 134 KSPS for 3 channels actally
-             * used on each ADC
-             */
-            LL_ADC_SetChannelSamplingTime(adc,
-                                          __LL_ADC_DECIMAL_NB_TO_CHANNEL(channel),
-                                          LL_ADC_SAMPLINGTIME_92CYCLES_5
-                                         );
-        }
-    }
+void adc_channels_configure(uint8_t adc_num)
+{
+	ADC_TypeDef* adc = _get_adc_by_number(adc_num);
+	uint8_t enabled_channels_in_this_adc = adc_channels_get_enabled_channels_count(adc_num);
+
+	channel_prop_t** enabled_channels_list = _adc_channels_get_enabled_channels_list(adc_num);
+
+	for (int rank = 0; rank < enabled_channels_in_this_adc; rank++)
+	{
+		uint8_t current_channel = enabled_channels_list[rank]->number;
+
+		// Set regular sequence
+		LL_ADC_REG_SetSequencerRanks(adc,
+		                             adc_decimal_nb_to_rank(rank+1), // Indexed from 1 => +1
+		                             __LL_ADC_DECIMAL_NB_TO_CHANNEL(current_channel)
+		                            );
+		// Set channels sampling time
+
+		/* 000: 2.5 ADC clock cycles
+		 * 001: 6.5 ADC clock cycles
+		 * 010: 12.5 ADC clock cycles
+		 * 011: 24.5 ADC clock cycles
+		 * 100: 47.5 ADC clock cycles
+		 * 101: 92.5 ADC clock cycles
+		 * 110: 247.5 ADC clock cycles
+		 * 111: 640.5 ADC clock cycles
+		 */
+		/* Vrefint minimum sampling time : 4us
+		 */
+		/* Vts minimum sampling time : 5us
+		 */
+		/* For 0b110:
+		 * Tadc_clk = 1 / 42.5 MHz = 23.5 ns
+		 * Tsar = 12.5 * Tadc_clk = 293.75 ns
+		 * Tsmpl = 247.5 * Tadc_clk = 5816.25 ns
+		 * Tconv = Tsmpl + Tsar = 6.11 us
+		 * -> Fconv up to 163.6 KSPS for 1 channel per ADC
+		 * Fconv up to 27.2 KSPS with the 6 channels actally
+		 * used on the ADC1
+		 *
+		 * For 0b001 (ok for voltage):
+		 * Tadc_clk = 1 / 42.5 MHz = 23.5 ns
+		 * Tsar = 12.5 * Tadc_clk = 293.75 ns
+		 * Tsmpl = 6.5 * Tadc_clk = 152.75 ns
+		 * Tconv = Tsmpl + Tsar = 446.4 ns
+		 * -> Fconv up to 2239 KSPS for 1 channel per ADC
+		 * Fconv up to 373 KSPS with the 6 channels actally
+		 * used on the ADC1
+		 *
+		 * For 0b101 (ok for current):
+		 * Tadc_clk = 1 / 42.5 MHz = 23.5 ns
+		 * Tsar = 12.5 * Tadc_clk = 293.75 ns
+		 * Tsmpl = 92.5 * Tadc_clk = 2173.75 ns
+		 * Tconv = Tsmpl + Tsar = 2.47 µs
+		 * -> Fconv up to 404 KSPS for 1 channel per ADC
+		 * Fconv up to 134 KSPS for 3 channels actally
+		 * used on each ADC
+		 */
+		LL_ADC_SetChannelSamplingTime(adc,
+		                              __LL_ADC_DECIMAL_NB_TO_CHANNEL(current_channel),
+		                              LL_ADC_SAMPLINGTIME_92CYCLES_5
+		                             );
+
+	}
+
+	// Set regular sequence length
+	LL_ADC_REG_SetSequencerLength(adc, (uint32_t)enabled_channels_in_this_adc-1);
+}
+
+int8_t adc_channnels_configure_adc_channels(uint8_t adc_num, char* channel_list[], uint8_t channel_count)
+{
+	uint8_t result = 0;
+
+	channel_prop_t** current_adc_enabled_channels_list = k_malloc(channel_count * sizeof(channel_prop_t*));
+
+	for (int i = 0 ; i < channel_count ; i++)
+	{
+		channel_prop_t* current_channel = _adc_channels_get_available_channel_by_name(adc_num, channel_list[i]);
+		if (current_channel == NULL)
+		{
+			result = ECHANNOTFOUND;
+			break;
+		}
+		else
+		{
+			current_adc_enabled_channels_list[i] = current_channel;
+		}
+	}
+
+	if (result == 0)
+	{
+		channel_prop_t** previous_adc_enabled_channels_list = _adc_channels_get_enabled_channels_list(adc_num);
+		if (previous_adc_enabled_channels_list != NULL)
+		{
+			k_free(previous_adc_enabled_channels_list);
+		}
+
+		_adc_channels_set_enabled_channels(adc_num, current_adc_enabled_channels_list, channel_count);
+	}
+	else
+	{
+		k_free(current_adc_enabled_channels_list);
+	}
+
+	return result;
 }
 
 char* adc_channels_get_channel_name(uint8_t adc_num, uint8_t channel_rank)
 {
-    if (adc_num == 1)
-        return adc1_channels_list[channel_rank]->name;
-    else if (adc_num == 2)
-        return adc2_channels_list[channel_rank]->name;
-    else
-        return NULL;
+	channel_prop_t** current_adc_enabled_channels_list = _adc_channels_get_enabled_channels_list(adc_num);
+
+	if ( (current_adc_enabled_channels_list != NULL) && (channel_rank < _adc_channels_get_enabled_channels_count(adc_num)) )
+	{
+		return current_adc_enabled_channels_list[channel_rank]->name;
+	}
+
+	return NULL;
 }
 
-uint8_t adc_channels_get_channels_count(uint8_t adc_num)
+uint8_t adc_channels_get_enabled_channels_count(uint8_t adc_num)
 {
-    if (adc_num == 1)
-        return channels_in_adc1_count;
-    else if (adc_num == 2)
-        return channels_in_adc2_count;
-    else
-        return 0xFF;
-}
\ No newline at end of file
+	uint8_t enabled_channels = 0;
+
+	switch (adc_num)
+	{
+		case 1:
+			enabled_channels = adc1_enabled_channels_count;
+			break;
+		case 2:
+			enabled_channels = adc2_enabled_channels_count;
+			break;
+		case 3:
+			enabled_channels = adc3_enabled_channels_count;
+			break;
+	}
+
+	return enabled_channels;
+}
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.h b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.h
index 72bc11f3d8054b891320787fdaf6df9551e7c3a3..90a5b550c6f97674dc10ac6f965da30484a1222b 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.h
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels.h
@@ -18,8 +18,6 @@
  */
 
 /**
- * @brief  This is the public include for adc_channels.h
- *
  * @author Clément Foucher <clement.foucher@laas.fr>
  */
 
@@ -27,20 +25,71 @@
 #define ADC_CHANNELS_H_
 
 
-// Zephyr
-#include <zephyr.h>
+#include <stdint.h>
 
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+/**
+ * Performs internal data structures initialization
+ * and pre-ADC enable init.
+ * Must be called before adc_core_enable()
+ */
 void adc_channels_init();
-void adc_channels_configure(ADC_TypeDef* adc);
 
+/**
+ * ADC channel configuration.
+ * For each channel enabled by the user, sets its sequencer rank and
+ * sampling time, then sets the ADC sequencer length.
+ *
+ * If must be called only after adc_channnels_configure_adc_channels
+ * has been called for this ADC, and ADC is enabled an not running.
+ *
+ * @param adc_num Number of the adc for which to configure channels.
+ */
+void adc_channels_configure(uint8_t adc_num);
+
+/**
+ * This function is used to configure the channels to be
+ * enabled on a given ADC.
+ *
+ * @param  adc_number Number of the ADC on which channel configuration is
+ *         to be done.
+ * @param  channel_list List of channels to configure. This is a list of
+ *         names as defined in the device tree (field `label`). The order
+ *         of the names in the array sets the acquisition ranks (order in
+ *         which the channels are acquired).
+ * @param  channel_count Number of channels defined in `channel_list`.
+ * @return 0 is everything went well,
+ *         ECHANNOTFOUND if at least one of the channels
+ *           is not available in the given ADC. Available channels are the
+ *           ones defined in the device tree.
+ */
+int8_t adc_channnels_configure_adc_channels(uint8_t adc_num, char* channel_list[], uint8_t channel_count);
+
+/**
+ * This function returns the name of an enabled channel.
+ *
+ * This function must onle be called after
+ * adc_channnels_configure_adc_channels has been called.
+ *
+ * @param  adc_number Number of the ADC
+ * @param  channel_rank Rank of the ADC channel to query.
+ *         Rank ranges from 0 to (number of enabled channels)-1
+ * @return Name of the channel as defined in the device tree, or
+ *         NULL if channel configuration has not been made or
+ *         channel_rank is over (number of enabled channels)-1.
+ */
 char* adc_channels_get_channel_name(uint8_t adc_num, uint8_t channel_rank);
 
-uint8_t adc_channels_get_channels_count(uint8_t adc_num);
+/**
+ * Get the number of enabled channels for an ADC.
+ * @param  adc_num Number of the ADC
+ * @return Number of enabled channels in this ADC.
+ */
+uint8_t adc_channels_get_enabled_channels_count(uint8_t adc_num);
 
 
 #ifdef __cplusplus
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels_private.h b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels_private.h
deleted file mode 100644
index 20ae8dc05de071a704bdd7318b2aaca23a0536a5..0000000000000000000000000000000000000000
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_channels_private.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2021 LAAS-CNRS
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGLPV2.1
- */
-
-/**
- * @author Clément Foucher <clement.foucher@laas.fr>
- * @brief  This is the private include file for adc_channels.c.
- * It should only be included in C files from the current folder.
- */
-
-#ifndef ADC_CHANNELS_PRIVATE_H_
-#define ADC_CHANNELS_PRIVATE_H_
-
-
-// Zephyr
-#include <zephyr.h>
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-typedef struct
-{
-    char*   name;
-    bool    is_differential;
-    uint8_t number;
-    char*   adc;
-} channel_prop_t;
-
-#define ADC_INPUTS_NODELABEL DT_NODELABEL(mychannels)
-
-// Channel properties
-#define CHANNEL_NAME(node_id)    DT_LABEL(node_id)
-#define CHANNEL_IS_DIFF(node_id) DT_PROP(node_id, differential)
-#define CHANNEL_NUMBER(node_id)  DT_PHA_BY_IDX(node_id, io_channels, 0, input)
-#define CHANNEL_ADC(node_id)     DT_PROP_BY_PHANDLE_IDX(node_id, io_channels, 0, label)
-
-#define CHANNEL_WRITE_PROP(node_id)                \
-    {                                              \
-        .name=CHANNEL_NAME(node_id),               \
-        .is_differential=CHANNEL_IS_DIFF(node_id), \
-        .number=CHANNEL_NUMBER(node_id),           \
-        .adc=CHANNEL_ADC(node_id)                  \
-    },
-
-// Channel count
-#define CHANNEL_COUNTER(node_id) +1
-#define CHANNEL_COUNT DT_FOREACH_CHILD(ADC_INPUTS_NODELABEL, CHANNEL_COUNTER)
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // ADC_CHANNELS_PRIVATE_H_
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.c b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.c
index 7336c3f94539f08f81c03c77a555535f85e6ecd2..b9935bf656975900be04399e0d91388ba6e361ef 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.c
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.c
@@ -19,35 +19,21 @@
 
 /**
  * @author Clément Foucher <clement.foucher@laas.fr>
- *
- * @brief  This is an ad-hoc ADC driver for OwnTech's
- * application. It supports differential channel setup
- * unlike Zephyr's STM32 driver.
- * It configures ADC 1 and ADC 2, using a common clock
- * which is AHB clock with a prescaler division by 4.
- *
- * ** Summary **
- *
- * To use this driver, first call adc_init(), then call
- * required configuration functions, then call adc_enable(),
- * and finally adc_start().
- *
- * ** Call order **
- *
- * All configuration functions provided by this driver must
- * be called (if required) before calling adc_enable().
- * If addtionnal configuration not provided by this driver
- * is required, this must be done after calling adc_enable(),
- * but before calling adc_start().
  */
 
 
-// Current file header
-#include "adc_core.h"
+// Stdlib
+#include <stdint.h>
+
+// Zephyr
+#include <zephyr.h>
 
 // STM32 LL
 #include <stm32g4xx_ll_bus.h>
 
+// Owntech API
+#include "adc_helper.h"
+
 
 /////
 // Private functions
@@ -56,124 +42,110 @@
  * ADC wake-up.
  * Refer to RM 21.4.6
  */
-static void _adc_core_wakeup(ADC_TypeDef* adc)
+static void _adc_core_wakeup(uint8_t adc_num)
 {
-    // Disable deep power down
-    LL_ADC_DisableDeepPowerDown(adc);
+	ADC_TypeDef* adc = _get_adc_by_number(adc_num);
+
+	// Disable deep power down
+	LL_ADC_DisableDeepPowerDown(adc);
 
-    // Enable internal regulator
-    LL_ADC_EnableInternalRegulator(adc);
+	// Enable internal regulator
+	LL_ADC_EnableInternalRegulator(adc);
 
-    // Wait for ADC voltage regulator start-up time (20us for g474)
-    // See also constant LL_ADC_DELAY_INTERNAL_REGUL_STAB_US
-    k_busy_wait(30);
+	// Wait for ADC voltage regulator start-up time
+	k_busy_wait(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US);
 }
 
 /**
  * ADC calibration.
  * Refer to RM 21.4.8
  */
-static void _adc_core_calibrate(ADC_TypeDef* adc)
+static void _adc_core_calibrate(uint8_t adc_num)
 {
-    // Single ended calibration
-    LL_ADC_StartCalibration(adc, LL_ADC_SINGLE_ENDED);
-    while ( LL_ADC_IsCalibrationOnGoing(adc) ) { /* Wait */ }
+	ADC_TypeDef* adc = _get_adc_by_number(adc_num);
 
-    // Seems to require an additionnal delay between calibrations
-    // TODO: undocumented???
-    k_busy_wait(10);
+	// Single ended calibration
+	LL_ADC_StartCalibration(adc, LL_ADC_SINGLE_ENDED);
+	while ( LL_ADC_IsCalibrationOnGoing(adc) ) { /* Wait */ }
 
-    // Differential ended calibration
-    LL_ADC_StartCalibration(adc, LL_ADC_DIFFERENTIAL_ENDED);
-    while ( LL_ADC_IsCalibrationOnGoing(adc) ) { /* Wait */ }
+	// Seems to require an additionnal delay between calibrations
+	// TODO: undocumented???
+	k_busy_wait(10);
+
+	// Differential ended calibration
+	LL_ADC_StartCalibration(adc, LL_ADC_DIFFERENTIAL_ENDED);
+	while ( LL_ADC_IsCalibrationOnGoing(adc) ) { /* Wait */ }
 }
 
 
 /////
 // Public API
 
-/**
- * ADC differential channel set-up:
- * Applies differential mode to specified channel.
- * Must be done *before* enabling ADC.
- * Refer to RM 21.4.7
- */
-void adc_core_set_channel_differential(ADC_TypeDef* adc, uint8_t channel)
+void adc_core_set_dual_mode(uint8_t dual_mode)
 {
-    LL_ADC_SetChannelSingleDiff(adc,
-                                __LL_ADC_DECIMAL_NB_TO_CHANNEL(channel),
-                                LL_ADC_DIFFERENTIAL_ENDED
-                               );
+	if (dual_mode != 0)
+	{
+		LL_ADC_SetMultimode(ADC12_COMMON, LL_ADC_MULTI_DUAL_REG_SIMULT);
+	}
+	else
+	{
+		LL_ADC_SetMultimode(ADC12_COMMON, LL_ADC_MULTI_INDEPENDENT);
+	}
 }
 
-/**
- * ADC internal paths configuration.
- * Enables VTS, VBAT and VREF.
- * Refer to RM 21.4.31 to 21.4.33
- */
-void adc_core_configure_internal_paths(uint8_t vts, uint8_t vbat, uint8_t vref)
+void adc_core_enable(uint8_t adc_num)
 {
-    uint32_t path = LL_ADC_PATH_INTERNAL_NONE;
-    if (vts == 1)
-        path |= LL_ADC_PATH_INTERNAL_TEMPSENSOR;
-    if (vbat == 1)
-        path |= LL_ADC_PATH_INTERNAL_VBAT;
-    if (vref == 1)
-        path |= LL_ADC_PATH_INTERNAL_VREFINT;
-
-    LL_ADC_SetCommonPathInternalCh(ADC12_COMMON, path);
-}
+	ADC_TypeDef* adc = _get_adc_by_number(adc_num);
 
-/**
- * ADC dual mode: enables ADC 1/ADC 2 synchronization.
- * When ADC 1 acquisition is triggered, it simultaneously
- * triggers an acquisition on ADC 2.
- * Refer to RM 21.4.30
- */
-void adc_core_set_dual_mode()
-{
-    LL_ADC_SetMultimode(ADC12_COMMON, LL_ADC_MULTI_DUAL_REG_SIMULT);
+	// Enable ADC and wait for it to be ready
+	LL_ADC_ClearFlag_ADRDY(adc);
+	LL_ADC_Enable(adc);
+	while (LL_ADC_IsActiveFlag_ADRDY(adc) == 0) { /* Wait */ }
 }
 
-/**
- * ADC enable.
- * Refer to RM 21.4.9
- */
-void adc_core_enable(ADC_TypeDef* adc)
+void adc_core_start(uint8_t adc_num)
 {
-    // Enable ADC and wait for it to be ready
-    LL_ADC_ClearFlag_ADRDY(adc);
-    LL_ADC_Enable(adc);
-    while (LL_ADC_IsActiveFlag_ADRDY(adc) == 0) { /* Wait */ }
+	ADC_TypeDef* adc = _get_adc_by_number(adc_num);
+
+	LL_ADC_REG_StartConversion(adc);
 }
 
-/**
- * ADC start.
- * Refer to RM 21.4.15
- */
-void adc_core_start(ADC_TypeDef* adc)
+void adc_core_configure_dma_mode(uint8_t adc_num)
 {
-    LL_ADC_REG_StartConversion(adc);
+	ADC_TypeDef* adc = _get_adc_by_number(adc_num);
+
+	LL_ADC_REG_SetDMATransfer(adc, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
 }
 
-/**
- * ADC initialization procedure for
- * both ADC 1 and ADC 2.
- */
-void adc_core_init()
+void adc_core_configure_trigger_source(uint8_t adc_num, uint32_t ExternalTriggerEdge, uint32_t TriggerSource)
 {
-    // Enable ADC1/2 clock
-    LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC12);
+	ADC_TypeDef* adc = _get_adc_by_number(adc_num);
 
-    // Wake-up ADCs
-    _adc_core_wakeup(ADC1);
-    _adc_core_wakeup(ADC2);
+	// Set trigger edge
+	LL_ADC_REG_SetTriggerEdge(adc, ExternalTriggerEdge);
 
-    // Set common clock
-    // Refer to RM 21.4.3 and 21.7.2
-    LL_ADC_SetCommonClock(ADC12_COMMON, LL_ADC_CLOCK_SYNC_PCLK_DIV4);
+	// Set trigger source
+	LL_ADC_REG_SetTriggerSource(adc, TriggerSource);
+}
 
-    // Calibrate ADCs
-    _adc_core_calibrate(ADC1);
-    _adc_core_calibrate(ADC2);
+void adc_core_init()
+{
+	// Enable ADCs clocks
+	LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC12);
+	LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC345);
+
+	// Wake-up ADCs
+	_adc_core_wakeup(1);
+	_adc_core_wakeup(2);
+	_adc_core_wakeup(3);
+
+	// Set common clock between ADC 1 and ADC 2
+	// Refer to RM 21.4.3 and 21.7.2
+	LL_ADC_SetCommonClock(ADC12_COMMON, LL_ADC_CLOCK_SYNC_PCLK_DIV4);
+	LL_ADC_SetCommonClock(ADC345_COMMON, LL_ADC_CLOCK_SYNC_PCLK_DIV4);
+
+	// Calibrate ADCs
+	_adc_core_calibrate(1);
+	_adc_core_calibrate(2);
+	_adc_core_calibrate(3);
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.h b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.h
index 98adc3278d053dd5fdef43343251cff74b508caa..d1c5da2955a80e6932248ff31bcdcb881e6f649f 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.h
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_core.h
@@ -19,32 +19,77 @@
 
 /**
  * @author Clément Foucher <clement.foucher@laas.fr>
- * @brief  This is the public include for adc_cores.h
- * It should only be included in C files from the current folder.
  */
 
 #ifndef ADC_CORE_H_
 #define ADC_CORE_H_
 
 
-// Zephyr
-#include <zephyr.h>
+#include <stdint.h>
 
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-
+/////
 // Init, enable, start
+
+/**
+ * ADC initialization procedure for
+ * ADC 1, ADC 2 and ADC 3.
+ */
 void adc_core_init();
-void adc_core_enable(ADC_TypeDef* adc);
-void adc_core_start(ADC_TypeDef* adc);
 
+/**
+ * ADC enable.
+ * Refer to RM 21.4.9
+ *
+ * @param adc_num Number of the ADC to enable.
+ */
+void adc_core_enable(uint8_t adc_num);
+
+/**
+ * ADC start.
+ * Refer to RM 21.4.15
+ *
+ * @param adc_num Number of the ADC to start.
+ */
+void adc_core_start(uint8_t adc_num);
+
+/////
 // Configuration functions
-void adc_core_set_channel_differential(ADC_TypeDef* adc, uint8_t channel);
-void adc_core_configure_internal_paths(uint8_t vts, uint8_t vbat, uint8_t vref);
-void adc_core_set_dual_mode();
+
+/**
+ * ADC dual mode: enables ADC 1/ADC 2 synchronization.
+ * When ADC 1 acquisition is triggered, it simultaneously
+ * triggers an acquisition on ADC 2.
+ *
+ * Refer to RM 21.4.30
+ *
+ * @param dual_mode true to enable dual moode, false to
+ *        disable it. false by default.
+ */
+void adc_core_set_dual_mode(uint8_t dual_mode);
+
+/**
+ * ADC DMA mode configuration.
+ * Enables DMA and circular mode on an ADC.
+ *
+ * @param adc_num Number of the ADC on which to enable DMA.
+ */
+void adc_core_configure_dma_mode(uint8_t adc_num);
+
+/**
+ * Defines the trigger source for an ADC.
+ *
+ * @param adc_num Number of the ADC to configure.
+ * @param ExternalTriggerEdge Edge of the trigger as defined
+ *        in stm32gxx_ll_adc.h (LL_ADC_REG_TRIG_***).
+ * @param trigger_source Source of the trigger as defined
+ *        in stm32gxx_ll_adc.h (LL_ADC_REG_TRIG_***).
+ */
+void adc_core_configure_trigger_source(uint8_t adc_num, uint32_t ExternalTriggerEdge, uint32_t TriggerSource);
 
 
 #ifdef __cplusplus
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.c b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.c
index 22a48d586e1a0b7dda29389bc05762ef39d4fb0e..1b06cb1c806f79ee311aca73643913d04f29f847 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.c
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.c
@@ -24,9 +24,10 @@
 
 // Stdlib
 #include <string.h>
+#include <stdint.h>
 
-// Current file header
-#include "adc_helper.h"
+// Zephyr
+#include <zephyr.h>
 
 
 /**
@@ -34,14 +35,44 @@
  */
 ADC_TypeDef* _get_adc_by_name(char* name)
 {
-    ADC_TypeDef* adc = NULL;
+	ADC_TypeDef* adc = NULL;
 
-    if  ( strcmp(name, "ADC_1") == 0)
-        adc = ADC1;
-    else if  ( strcmp(name, "ADC_2") == 0)
-        adc = ADC2;
+	if ( strcmp(name, "ADC_1") == 0)
+		adc = ADC1;
+	else if ( strcmp(name, "ADC_2") == 0)
+		adc = ADC2;
+	else if ( strcmp(name, "ADC_3") == 0)
+		adc = ADC3;
 
-    return adc;
+	return adc;
+}
+
+ADC_TypeDef* _get_adc_by_number(uint8_t adc_number)
+{
+	ADC_TypeDef* adc = NULL;
+
+	if (adc_number == 1)
+		adc = ADC1;
+	else if (adc_number == 2)
+		adc = ADC2;
+	else if (adc_number == 3)
+		adc = ADC3;
+
+	return adc;
+}
+
+uint8_t _get_adc_number_by_name(char* name)
+{
+	uint8_t adc_number = 0;
+
+	if ( strcmp(name, "ADC_1") == 0)
+		adc_number = 1;
+	else if ( strcmp(name, "ADC_2") == 0)
+		adc_number = 2;
+	else if ( strcmp(name, "ADC_3") == 0)
+		adc_number = 3;
+
+	return adc_number;
 }
 
 /**
@@ -51,59 +82,59 @@ ADC_TypeDef* _get_adc_by_name(char* name)
  */
 uint32_t adc_decimal_nb_to_rank(uint8_t decimal_rank)
 {
-    uint32_t ll_rank;
-    switch (decimal_rank)
-    {
-        case 1:
-            ll_rank = LL_ADC_REG_RANK_1;
-            break;
-        case 2:
-            ll_rank = LL_ADC_REG_RANK_2;
-            break;
-        case 3:
-            ll_rank = LL_ADC_REG_RANK_3;
-            break;
-        case 4:
-            ll_rank = LL_ADC_REG_RANK_4;
-            break;
-        case 5:
-            ll_rank = LL_ADC_REG_RANK_5;
-            break;
-        case 6:
-            ll_rank = LL_ADC_REG_RANK_6;
-            break;
-        case 7:
-            ll_rank = LL_ADC_REG_RANK_7;
-            break;
-        case 8:
-            ll_rank = LL_ADC_REG_RANK_8;
-            break;
-        case 9:
-            ll_rank = LL_ADC_REG_RANK_9;
-            break;
-        case 10:
-            ll_rank = LL_ADC_REG_RANK_10;
-            break;
-        case 11:
-            ll_rank = LL_ADC_REG_RANK_11;
-            break;
-        case 12:
-            ll_rank = LL_ADC_REG_RANK_12;
-            break;
-        case 13:
-            ll_rank = LL_ADC_REG_RANK_13;
-            break;
-        case 14:
-            ll_rank = LL_ADC_REG_RANK_14;
-            break;
-        case 15:
-            ll_rank = LL_ADC_REG_RANK_15;
-            break;
-        case 16:
-            ll_rank = LL_ADC_REG_RANK_16;
-            break;
-        default:
-            ll_rank = 0;
-    }
-    return ll_rank;
+	uint32_t ll_rank;
+	switch (decimal_rank)
+	{
+		case 1:
+			ll_rank = LL_ADC_REG_RANK_1;
+			break;
+		case 2:
+			ll_rank = LL_ADC_REG_RANK_2;
+			break;
+		case 3:
+			ll_rank = LL_ADC_REG_RANK_3;
+			break;
+		case 4:
+			ll_rank = LL_ADC_REG_RANK_4;
+			break;
+		case 5:
+			ll_rank = LL_ADC_REG_RANK_5;
+			break;
+		case 6:
+			ll_rank = LL_ADC_REG_RANK_6;
+			break;
+		case 7:
+			ll_rank = LL_ADC_REG_RANK_7;
+			break;
+		case 8:
+			ll_rank = LL_ADC_REG_RANK_8;
+			break;
+		case 9:
+			ll_rank = LL_ADC_REG_RANK_9;
+			break;
+		case 10:
+			ll_rank = LL_ADC_REG_RANK_10;
+			break;
+		case 11:
+			ll_rank = LL_ADC_REG_RANK_11;
+			break;
+		case 12:
+			ll_rank = LL_ADC_REG_RANK_12;
+			break;
+		case 13:
+			ll_rank = LL_ADC_REG_RANK_13;
+			break;
+		case 14:
+			ll_rank = LL_ADC_REG_RANK_14;
+			break;
+		case 15:
+			ll_rank = LL_ADC_REG_RANK_15;
+			break;
+		case 16:
+			ll_rank = LL_ADC_REG_RANK_16;
+			break;
+		default:
+			ll_rank = 0;
+	}
+	return ll_rank;
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.h b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.h
index f1990442d1ef5222c76359eb297851cc91370221..efdc71365fe6144ba57f2e63530b44416afd84f2 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.h
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/adc/adc_helper.h
@@ -19,15 +19,13 @@
 
 /**
  * @author Clément Foucher <clement.foucher@laas.fr>
- * @brief  This is the public include for adc_helper.h
- * It should only be included in C files from the current folder.
  */
 
 #ifndef ADC_HELPER_H_
 #define ADC_HELPER_H_
 
 
-// Zephyr
+#include <stdint.h>
 #include <zephyr.h>
 
 
@@ -37,6 +35,8 @@ extern "C" {
 
 
 ADC_TypeDef* _get_adc_by_name(char* name);
+ADC_TypeDef* _get_adc_by_number(uint8_t adc_number);
+uint8_t _get_adc_number_by_name(char* name);
 uint32_t adc_decimal_nb_to_rank(uint8_t decimal_rank);
 
 
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/data_acquisition.c b/zephyr/modules/owntech_data_acquisition/zephyr/data_acquisition.c
index 13fe0876dc75ae7ec108298ceea6866dc01fdbbd..d4cd294b67de84065ce7a8c4db220d51bae4006f 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/data_acquisition.c
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/data_acquisition.c
@@ -22,52 +22,217 @@
  */
 
 
-/////
-// OwnTech Power API includes
+// Stdlib
+#include <string.h>
+#include <stdint.h>
 
-#include "timer.h"
+// OwnTech Power API
 #include "dma/dma.h"
 #include "adc/adc.h"
+#include "adc/adc_channels.h"
 #include "data_dispatch/data_dispatch.h"
 
+#include "data_acquisition.h"
+
 
 /////
-// Timer
+// Local types
 
-#define TIMER7_LABEL DT_PROP(DT_NODELABEL(timers7), label)
-static const struct device* timer7;
+typedef struct
+{
+	uint8_t adc_number;
+	uint8_t channel_rank;
+} channel_assignment_t;
 
 
 /////
-// Private functions
+// Local variables
+
+static uint8_t data_acquisition_initialized = 0;
+static uint8_t data_acquisition_started = 0;
+
+static channel_assignment_t v1_low_assignement = {0};
+static channel_assignment_t v2_low_assignement = {0};
+static channel_assignment_t v_high_assignement = {0};
+static channel_assignment_t i1_low_assignement = {0};
+static channel_assignment_t i2_low_assignement = {0};
+static channel_assignment_t i_high_assignement = {0};
+static channel_assignment_t temp_sensor_assignement = {0};
 
 
 /////
 // Public API
 
-void data_acquisition_init()
+int8_t data_acquisition_init()
+{
+	if (data_acquisition_initialized == 0)
+	{
+		adc_init();
+		data_acquisition_initialized = 1;
+		return 0;
+	}
+	else
+	{
+		return EALREADYINIT;
+	}
+}
+
+int8_t data_acquisition_set_adc12_dual_mode(uint8_t dual_mode)
 {
-	/////
-	// Initialize peripherals
+	if ( (data_acquisition_initialized == 1) && (data_acquisition_started == 0) )
+	{
+		adc_set_dual_mode(dual_mode);
+		return 0;
+	}
+	else if (data_acquisition_initialized == 0)
+	{
+		return EUNITITIALIZED;
+	}
+	else
+	{
+		return EALREADYSTARTED;
+	}
+}
 
-	// ADC
-	adc_init();
+int8_t data_acquisition_configure_adc_channels(uint8_t adc_number, char* channel_list[], uint8_t channel_count)
+{
+	if ( (data_acquisition_initialized == 1) && (data_acquisition_started == 0) )
+	{
+		int8_t result = adc_configure_adc_channels(adc_number, channel_list, channel_count);
+		if (result == 0)
+		{
+			for (int i = 0 ; i < channel_count ; i++)
+			{
+				if (strcmp(channel_list[i], "V1_LOW") == 0)
+				{
+					v1_low_assignement.adc_number = adc_number;
+					v1_low_assignement.channel_rank = i;
+				}
+				else if (strcmp(channel_list[i], "V2_LOW") == 0)
+				{
+					v2_low_assignement.adc_number = adc_number;
+					v2_low_assignement.channel_rank = i;
+				}
+				else if (strcmp(channel_list[i], "V_HIGH") == 0)
+				{
+					v_high_assignement.adc_number = adc_number;
+					v_high_assignement.channel_rank = i;
+				}
+				else if (strcmp(channel_list[i], "I1_LOW") == 0)
+				{
+					i1_low_assignement.adc_number = adc_number;
+					i1_low_assignement.channel_rank = i;
+				}
+				else if (strcmp(channel_list[i], "I2_LOW") == 0)
+				{
+					i2_low_assignement.adc_number = adc_number;
+					i2_low_assignement.channel_rank = i;
+				}
+				else if (strcmp(channel_list[i], "I_HIGH") == 0)
+				{
+					i_high_assignement.adc_number = adc_number;
+					i_high_assignement.channel_rank = i;
+				}
+				else if (strcmp(channel_list[i], "TEMP_SENSOR") == 0)
+				{
+					temp_sensor_assignement.adc_number = adc_number;
+					temp_sensor_assignement.channel_rank = i;
+				}
+			}
+		}
+		return result;
+	}
+	else if (data_acquisition_initialized == 0)
+	{
+		return EUNITITIALIZED;
+	}
+	else
+	{
+		return EALREADYSTARTED;
+	}
+}
 
-	// DMA
-	dma_init();
+int8_t data_acquisition_configure_adc_trigger_source(uint8_t adc_number, uint32_t trigger_source)
+{
+	if ( (data_acquisition_initialized == 1) && (data_acquisition_started == 0) )
+	{
+		adc_configure_trigger_source(adc_number, trigger_source);
+		return 0;
+	}
+	else if (data_acquisition_initialized == 0)
+	{
+		return EUNITITIALIZED;
+	}
+	else
+	{
+		return EALREADYSTARTED;
+	}
+}
 
-	/////
-	// Initialize data dispatch
-	data_dispatch_init();
 
-	/////
-	// Configure timer for background data dispatch
-	timer7 = device_get_binding(TIMER7_LABEL);
-	struct timer_config_t timer_cfg =
+int8_t data_acquisition_start()
+{
+	if ( (data_acquisition_initialized == 1) && (data_acquisition_started == 0) )
 	{
-		.timer_enable_irq = 1,
-		.timer_callback   = data_dispatch_do_dispatch
-	};
-	timer_config(timer7, &timer_cfg);
-	timer_start(timer7, 25);
+		// DMAs
+		dma_configure_and_start();
+
+		// Initialize data dispatch
+		data_dispatch_init();
+
+		// Launch ADC conversion
+		adc_start();
+
+		data_acquisition_started = 1;
+
+		return 0;
+	}
+	else if (data_acquisition_initialized == 0)
+	{
+		return EUNITITIALIZED;
+	}
+	else
+	{
+		return EALREADYSTARTED;
+	}
+}
+
+char* data_acquisition_get_channel_name(uint8_t adc_number, uint8_t channel_rank)
+{
+	return adc_channels_get_channel_name(adc_number, channel_rank);
+}
+
+uint16_t* data_acquisition_get_v1_low_values(uint32_t* number_of_values_acquired)
+{
+	return data_dispatch_get_acquired_values(v1_low_assignement.adc_number, v1_low_assignement.channel_rank, number_of_values_acquired);
+}
+
+uint16_t* data_acquisition_get_v2_low_values(uint32_t* number_of_values_acquired)
+{
+	return data_dispatch_get_acquired_values(v2_low_assignement.adc_number, v2_low_assignement.channel_rank, number_of_values_acquired);
+}
+
+uint16_t* data_acquisition_get_v_high_values(uint32_t* number_of_values_acquired)
+{
+	return data_dispatch_get_acquired_values(v_high_assignement.adc_number, v_high_assignement.channel_rank, number_of_values_acquired);
+}
+
+uint16_t* data_acquisition_get_i1_low_values(uint32_t* number_of_values_acquired)
+{
+	return data_dispatch_get_acquired_values(i1_low_assignement.adc_number, i1_low_assignement.channel_rank, number_of_values_acquired);
+}
+
+uint16_t* data_acquisition_get_i2_low_values(uint32_t* number_of_values_acquired)
+{
+	return data_dispatch_get_acquired_values(i2_low_assignement.adc_number, i2_low_assignement.channel_rank, number_of_values_acquired);
+}
+
+uint16_t* data_acquisition_get_i_high_values(uint32_t* number_of_values_acquired)
+{
+	return data_dispatch_get_acquired_values(i_high_assignement.adc_number, i_high_assignement.channel_rank, number_of_values_acquired);
+}
+
+uint16_t* data_acquisition_get_temp_sensor_values(uint32_t* number_of_values_acquired)
+{
+	return data_dispatch_get_acquired_values(temp_sensor_assignement.adc_number, temp_sensor_assignement.channel_rank, number_of_values_acquired);
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/data_acquisition.h b/zephyr/modules/owntech_data_acquisition/zephyr/data_acquisition.h
deleted file mode 100644
index 5aff6d03d50ecddf5cac53c92f1b70779a3b982d..0000000000000000000000000000000000000000
--- a/zephyr/modules/owntech_data_acquisition/zephyr/data_acquisition.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2021 LAAS-CNRS
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGLPV2.1
- */
-
-/**
- * @author Clément Foucher <clement.foucher@laas.fr>
- */
-
-#ifndef DATA_ACQUISITION_H_
-#define DATA_ACQUISITION_H_
-
-// Indirect public headers
-#include "adc/adc_channels.h"
-#include "data_dispatch/data_dispatch.h"
-#include "dma/dma.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-// Current file public function
-void data_acquisition_init();
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // DATA_ACQUISITION_H_
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.c b/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.c
index 2f67b8f1e8d28ba27dcd0a1b8db181175ba87d59..89c4404d3d0359c14b8aa3c2c4407bea8a2dcc71 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.c
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.c
@@ -31,7 +31,6 @@
 #include <zephyr.h>
 
 // OwnTech API
-#include "../dma/dma.h"
 #include "../adc/adc_channels.h"
 
 
@@ -40,84 +39,56 @@
 
 #define CHANNELS_BUFFERS_SIZE 32
 
-// Number of channels in each ADC
-static uint8_t channels_in_adc1_count;
-static uint8_t channels_in_adc2_count;
+// Number of channels in each ADC (cell i is ADC number i+1)
+static uint8_t enabled_channels_count[3];
 
-// Number of readings a DMA buffer can handle
-static size_t dma1_buffer_size;
-static size_t dma2_buffer_size;
-
-// Number of readings a *half-buffer* can handle
-static size_t dma1_half_buffer_size;
-static size_t dma2_half_buffer_size;
-
-// Number of *per-channel* readings a *half-buffer* can handle
-static uint32_t dma1_half_buffer_capacity;
-static uint32_t dma2_half_buffer_capacity;
-
-/////
-// DMA buffers (in)
-
-static uint16_t* dma1_buffer = NULL;
-static uint16_t* dma2_buffer = NULL;
-
-static uint32_t dma1_next_read_index = 0;
-static uint32_t dma2_next_read_index = 0;
 
 /////
-// Per-channel buffers (out)
+// Per-channel buffers
 
 // Bidimentionnal arrays: each of these variables is an array
 // of per-channel arrays holding the readings
 static uint16_t** adc1_channels_buffers = NULL;
 static uint16_t** adc2_channels_buffers = NULL;
 
-// Arrays to store the latest and next indexes in each channel array
+// Arrays to store the read and write indexes in each channel array
 static uint32_t* adc1_next_read_indexes  = NULL;
 static uint32_t* adc2_next_read_indexes  = NULL;
 static uint32_t* adc1_next_write_indexes = NULL;
 static uint32_t* adc2_next_write_indexes = NULL;
 
-/////
-// Meta-data
-static uint32_t readings_missed_in_adc1 = 0;
-static uint32_t readings_missed_in_adc2 = 0;
-
+static uint16_t** adc1_user_buffers = NULL;
+static uint16_t** adc2_user_buffers = NULL;
 
 /////
 // Private functions
 
-void _increment_circular_index(uint32_t* buffer_index, uint32_t buffer_size)
+__STATIC_INLINE void _increment_circular_index(uint32_t* buffer_index, uint32_t buffer_size)
 {
-    (*buffer_index) =  ( (*buffer_index) < (buffer_size-1) ) ? ( (*buffer_index) + 1 ) : 0;
+	(*buffer_index) =  ( (*buffer_index) < (buffer_size-1) ) ? ( (*buffer_index) + 1 ) : 0;
 }
 
-void _circular_buffer_copy(uint16_t* src_buffer,                 uint16_t* dest_buffer,
-                           uint32_t* src_buffer_next_read_index, uint32_t* dest_buffer_next_write_index,
-                           uint32_t  src_buffer_size,            uint32_t  dest_buffer_size
-                          )
+__STATIC_INLINE void _circular_buffer_copy(uint16_t* src_buffer,                 uint16_t* dest_buffer,
+                                           uint32_t  src_buffer_next_read_index, uint32_t  dest_buffer_next_write_index
+                                          )
 {
-    dest_buffer[*dest_buffer_next_write_index] = src_buffer[*src_buffer_next_read_index];
-
-    _increment_circular_index(src_buffer_next_read_index,   src_buffer_size);
-    _increment_circular_index(dest_buffer_next_write_index, dest_buffer_size);
+	dest_buffer[dest_buffer_next_write_index] = src_buffer[src_buffer_next_read_index];
 }
 
-uint32_t _get_values_available_count_in_buffer(uint32_t next_read_index, uint32_t next_write_index, uint32_t buffer_size)
+static uint32_t _get_values_available_count_in_buffer(uint32_t next_read_index, uint32_t next_write_index, uint32_t buffer_size)
 {
-    if (next_read_index == next_write_index)
-        return 0;
+	if (next_read_index == next_write_index)
+		return 0;
 
-    uint32_t i_next_write_index = (next_write_index < next_read_index) ? (next_write_index + buffer_size) : next_write_index;
-    return i_next_write_index - next_read_index;
+	uint32_t i_next_write_index = (next_write_index < next_read_index) ? (next_write_index + buffer_size) : next_write_index;
+	return i_next_write_index - next_read_index;
 }
 
-uint16_t _get_next_value_from_buffer(uint16_t* buffer, uint32_t* next_read_index, uint32_t buffer_size)
+static uint16_t _get_next_value_from_buffer(uint16_t* buffer, uint32_t* next_read_index, uint32_t buffer_size)
 {
-    uint16_t value = buffer[*next_read_index];
-    _increment_circular_index(next_read_index, buffer_size);
-    return value;
+	uint16_t value = buffer[*next_read_index];
+	_increment_circular_index(next_read_index, buffer_size);
+	return value;
 }
 
 
@@ -126,175 +97,112 @@ uint16_t _get_next_value_from_buffer(uint16_t* buffer, uint32_t* next_read_index
 
 void data_dispatch_init()
 {
-    dma1_buffer = dma_get_dma1_buffer();
-    dma2_buffer = dma_get_dma2_buffer();
-
-    dma1_buffer_size = dma_get_dma1_buffer_size();
-    dma2_buffer_size = dma_get_dma2_buffer_size();
-
-    dma1_half_buffer_size = dma1_buffer_size/2;
-    dma2_half_buffer_size = dma2_buffer_size/2;
-
-    channels_in_adc1_count = adc_channels_get_channels_count(1);
-    channels_in_adc2_count = adc_channels_get_channels_count(2);
-
-    dma1_half_buffer_capacity = dma1_half_buffer_size/channels_in_adc1_count;
-    dma2_half_buffer_capacity = dma2_half_buffer_size/channels_in_adc2_count;
-
-    adc1_channels_buffers   = malloc(sizeof(uint16_t*) * channels_in_adc1_count);
-    adc1_next_read_indexes  = malloc(sizeof(uint32_t)  * channels_in_adc1_count);
-    adc1_next_write_indexes = malloc(sizeof(uint32_t)  * channels_in_adc1_count);
-    for (int i = 0 ; i < channels_in_adc1_count ; i++)
-    {
-        adc1_channels_buffers[i]   = malloc(sizeof(uint16_t) * CHANNELS_BUFFERS_SIZE);
-        adc1_next_read_indexes[i]  = 0;
-        adc1_next_write_indexes[i] = 0;
-    }
-
-    adc2_channels_buffers   = malloc(sizeof(uint16_t*) * channels_in_adc2_count);
-    adc2_next_read_indexes  = malloc(sizeof(uint32_t)  * channels_in_adc2_count);
-    adc2_next_write_indexes = malloc(sizeof(uint32_t)  * channels_in_adc2_count);
-    for (int i = 0 ; i < channels_in_adc2_count ; i++)
-    {
-        adc2_channels_buffers[i]   = malloc(sizeof(uint16_t) * CHANNELS_BUFFERS_SIZE);
-        adc2_next_read_indexes[i]  = 0;
-        adc2_next_write_indexes[i] = 0;
-    }
+	enabled_channels_count[0] = adc_channels_get_enabled_channels_count(1);
+	if (enabled_channels_count[0] > 0)
+	{
+		adc1_channels_buffers   = k_malloc(sizeof(uint16_t*) * enabled_channels_count[0]);
+		adc1_next_read_indexes  = k_malloc(sizeof(uint32_t)  * enabled_channels_count[0]);
+		adc1_next_write_indexes = k_malloc(sizeof(uint32_t)  * enabled_channels_count[0]);
+		adc1_user_buffers       = k_malloc(sizeof(uint16_t*) * enabled_channels_count[0]);
+		for (int i = 0 ; i < enabled_channels_count[0] ; i++)
+		{
+			adc1_channels_buffers[i]   = k_malloc(sizeof(uint16_t) * CHANNELS_BUFFERS_SIZE);
+			adc1_next_read_indexes[i]  = 0;
+			adc1_next_write_indexes[i] = 0;
+			adc1_user_buffers[i]       = k_malloc(sizeof(uint16_t) * CHANNELS_BUFFERS_SIZE);
+		}
+	}
+
+	enabled_channels_count[1] = adc_channels_get_enabled_channels_count(2);
+	if (enabled_channels_count[1] > 0)
+	{
+		adc2_channels_buffers   = k_malloc(sizeof(uint16_t*) * enabled_channels_count[1]);
+		adc2_next_read_indexes  = k_malloc(sizeof(uint32_t)  * enabled_channels_count[1]);
+		adc2_next_write_indexes = k_malloc(sizeof(uint32_t)  * enabled_channels_count[1]);
+		adc2_user_buffers       = k_malloc(sizeof(uint16_t*) * enabled_channels_count[1]);
+		for (int i = 0 ; i < enabled_channels_count[1] ; i++)
+		{
+			adc2_channels_buffers[i]   = k_malloc(sizeof(uint16_t) * CHANNELS_BUFFERS_SIZE);
+			adc2_next_read_indexes[i]  = 0;
+			adc2_next_write_indexes[i] = 0;
+			adc2_user_buffers[i]       = k_malloc(sizeof(uint16_t) * CHANNELS_BUFFERS_SIZE);
+		}
+	}
 }
 
-/**
- * This function gets the values from ADC buffers
- * and place them in arrays depending on their origin.
- */
-void data_dispatch_do_dispatch()
+void data_dispatch_do_dispatch(uint8_t adc_num, uint16_t* dma_buffer)
 {
-    // DMA 1
-    uint32_t _dma1_count = atomic_set(&dma1_readings_count, 0);
-
-    if (_dma1_count > 1)
-    {
-        uint32_t readings_missed = _dma1_count - 1;
-        readings_missed_in_adc1 += readings_missed*dma1_half_buffer_capacity;
-        // Catch up to the latest written half-buffer
-        if ( (readings_missed % 2) == 1)
-        {
-            dma1_next_read_index = (dma1_next_read_index + dma1_half_buffer_size) % dma1_buffer_size;
-        }
-    }
-
-    if (_dma1_count >= 1)
-    {
-        for (int readings_count = 0 ; readings_count < dma1_half_buffer_capacity ; readings_count++)
-        {
-            for (int channel_count = 0 ; channel_count < channels_in_adc1_count ; channel_count++)
-            {
-                _circular_buffer_copy(dma1_buffer,           adc1_channels_buffers[channel_count],
-                                      &dma1_next_read_index, &adc1_next_write_indexes[channel_count],
-                                      dma1_buffer_size,      CHANNELS_BUFFERS_SIZE
-                                     );
-            }
-        }
-    }
-
-    // DMA 2
-    uint32_t _dma2_count = atomic_set(&dma2_readings_count, 0);
-
-    if (_dma2_count > 1)
-    {
-        uint32_t readings_missed = _dma2_count - 1;
-        readings_missed_in_adc2 += readings_missed*dma2_half_buffer_capacity;
-        // Catch up to the latest written half-buffer
-        if ( (readings_missed % 2) == 1)
-        {
-            dma2_next_read_index = (dma2_next_read_index + dma2_half_buffer_size) % dma2_buffer_size;
-        }
-    }
-
-    if (_dma2_count >= 1)
-    {
-        for (int readings_count = 0 ; readings_count < dma2_half_buffer_capacity ; readings_count++)
-        {
-            for (int channel_count = 0 ; channel_count < channels_in_adc2_count ; channel_count++)
-            {
-                _circular_buffer_copy(dma2_buffer,           adc2_channels_buffers[channel_count],
-                                      &dma2_next_read_index, &adc2_next_write_indexes[channel_count],
-                                      dma2_buffer_size,      CHANNELS_BUFFERS_SIZE
-                                     );
-            }
-        }
-    }
+	if (adc_num == 1)
+	{
+		for (int current_channel = 0 ; current_channel < enabled_channels_count[0] ; current_channel++)
+		{
+			_circular_buffer_copy(dma_buffer,      adc1_channels_buffers[current_channel],
+			                      current_channel, adc1_next_write_indexes[current_channel]
+			                     );
+
+			_increment_circular_index(&adc1_next_write_indexes[current_channel], CHANNELS_BUFFERS_SIZE);
+		}
+	}
+	else if (adc_num == 2)
+	{
+		for (int current_channel = 0 ; current_channel < enabled_channels_count[1] ; current_channel++)
+		{
+			_circular_buffer_copy(dma_buffer,      adc2_channels_buffers[current_channel],
+			                      current_channel, adc2_next_write_indexes[current_channel]
+			                     );
+
+			_increment_circular_index(&adc2_next_write_indexes[current_channel], CHANNELS_BUFFERS_SIZE);
+		}
+	}
 }
 
 
 /////
 // Accessors
 
-char* data_dispatch_get_adc1_channel_name(uint8_t channel_rank)
-{
-    return adc_channels_get_channel_name(1, channel_rank);
-}
-
-char* data_dispatch_get_adc2_channel_name(uint8_t channel_rank)
-{
-    return adc_channels_get_channel_name(2, channel_rank);
-}
-
-uint32_t data_dispatch_get_values_available_in_adc1_channel(uint8_t channel_rank)
-{
-    return _get_values_available_count_in_buffer(adc1_next_read_indexes[channel_rank],
-                                                 adc1_next_write_indexes[channel_rank],
-                                                 CHANNELS_BUFFERS_SIZE
-                                                );
-}
-
-uint32_t data_dispatch_get_values_available_in_adc2_channel(uint8_t channel_rank)
-{
-    return _get_values_available_count_in_buffer(adc2_next_read_indexes[channel_rank],
-                                                 adc2_next_write_indexes[channel_rank],
-                                                 CHANNELS_BUFFERS_SIZE
-                                                );
-}
-
-uint16_t data_dispatch_get_next_value_from_adc1_channel(uint8_t channel_rank)
-{
-    return _get_next_value_from_buffer(adc1_channels_buffers[channel_rank],
-                                       &adc1_next_read_indexes[channel_rank],
-                                       CHANNELS_BUFFERS_SIZE
-                                      );
-}
-
-uint16_t data_dispatch_get_next_value_from_adc2_channel(uint8_t channel_rank)
-{
-    return _get_next_value_from_buffer(adc2_channels_buffers[channel_rank],
-                                       &adc2_next_read_indexes[channel_rank],
-                                       CHANNELS_BUFFERS_SIZE
-                                      );
-}
-
-uint32_t data_dispatch_how_many_adc1_reading_been_lost()
-{
-    if (readings_missed_in_adc1 > 0)
-    {
-        uint32_t error_count = readings_missed_in_adc1;
-        readings_missed_in_adc1 = 0;
-        return error_count;
-    }
-    else
-    {
-        return 0;
-    }
-}
-
-uint32_t data_dispatch_how_many_adc2_reading_been_lost()
+uint16_t* data_dispatch_get_acquired_values(uint8_t adc_number, uint8_t channel_rank, uint32_t* number_of_values_acquired)
 {
-    if (readings_missed_in_adc2 > 0)
-    {
-        uint32_t error_count = readings_missed_in_adc2;
-        readings_missed_in_adc2 = 0;
-        return error_count;
-    }
-    else
-    {
-        return 0;
-    }
+	if (adc_number == 1)
+	{
+		// Get number of available values
+		uint32_t number_of_values = _get_values_available_count_in_buffer(adc1_next_read_indexes[channel_rank],
+		                                                                  adc1_next_write_indexes[channel_rank],
+		                                                                  CHANNELS_BUFFERS_SIZE
+		                                                                 );
+
+		for (uint32_t i = 0 ; i < number_of_values ; i++)
+		{
+			adc1_user_buffers[channel_rank][i] = _get_next_value_from_buffer(adc1_channels_buffers[channel_rank],
+			                                                                &adc1_next_read_indexes[channel_rank],
+			                                                                CHANNELS_BUFFERS_SIZE
+			                                                               );
+		}
+
+		*number_of_values_acquired = number_of_values;
+		return adc1_user_buffers[channel_rank];
+	}
+	else if (adc_number == 2)
+	{
+		// Get number of available values
+		uint32_t number_of_values = _get_values_available_count_in_buffer(adc2_next_read_indexes[channel_rank],
+		                                                                  adc2_next_write_indexes[channel_rank],
+		                                                                  CHANNELS_BUFFERS_SIZE
+		                                                                 );
+
+		for (uint32_t i = 0 ; i < number_of_values ; i++)
+		{
+			adc2_user_buffers[channel_rank][i] = _get_next_value_from_buffer(adc2_channels_buffers[channel_rank],
+			                                                                 &adc2_next_read_indexes[channel_rank],
+			                                                                CHANNELS_BUFFERS_SIZE
+			                                                                );
+		}
+
+		*number_of_values_acquired = number_of_values;
+		return adc2_user_buffers[channel_rank];
+	}
+	else
+	{
+		*number_of_values_acquired = 0;
+		return NULL;
+	}
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.h b/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.h
index 7a0bde076bd2c96a859fd8cb5f176c0fc5a0b236..4fce83103813de1ba84be3e11eaea0f16f099618 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.h
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/data_dispatch/data_dispatch.h
@@ -28,7 +28,6 @@
 #define DATA_DISPATCH_H_
 
 
-// Stdlib
 #include <stdint.h>
 
 
@@ -41,27 +40,10 @@ extern "C" {
 void data_dispatch_init();
 
 // Dispatch function: gets the readings and store them in per-channel arrays
-void data_dispatch_do_dispatch();
-
-// Get the name of a specific channel
-char* data_dispatch_get_adc1_channel_name(uint8_t channel_rank);
-char* data_dispatch_get_adc2_channel_name(uint8_t channel_rank);
-
-// Get number of readings available for a specific channel
-uint32_t data_dispatch_get_values_available_in_adc1_channel(uint8_t channel_rank);
-uint32_t data_dispatch_get_values_available_in_adc2_channel(uint8_t channel_rank);
-
-// Pop next value from the buffer of a specific channel
-// WARNING: calling one of these function when values_available is 0
-// will break the behavior by desynchronizing the buffer indexes
-uint16_t data_dispatch_get_next_value_from_adc1_channel(uint8_t channel_rank);
-uint16_t data_dispatch_get_next_value_from_adc2_channel(uint8_t channel_rank);
-
-// Error function: tells how many values have been missed since
-// last call of the function (error count is reset at each call)
-uint32_t data_dispatch_how_many_adc1_reading_been_lost();
-uint32_t data_dispatch_how_many_adc2_reading_been_lost();
+void data_dispatch_do_dispatch(uint8_t adc_num, uint16_t* dma_buffer);
 
+// Obtain buffer for a specific channel
+uint16_t* data_dispatch_get_acquired_values(uint8_t adc_number, uint8_t channel_rank, uint32_t* number_of_values_acquired);
 
 #ifdef __cplusplus
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.c b/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.c
index b2e48c96dd86ddb46748885c2bf261682ac5ba43..041a572f1ace49a0e98a05ca3636a2d7b322b79f 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.c
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.c
@@ -18,181 +18,149 @@
  */
 
 /**
- * @brief  DMA configuration for OwnTech application
+ * @brief  DMA configuration for OwnTech application.
+ * One DMA channel is assigned per ADC. For each ADC, the DMA
+ * has a buffer sized 2*(number of enabled channels in ADC),
+ * which is subdivised in two half-buffers, so that when
+ * the DMA is filling one half-buffer, the other one
+ * is available to data dispatch.
+ * DMA 1 is used for all acquisitions, with channel i
+ * acquiring values from ADC i.
  *
  * @author Clément Foucher <clement.foucher@laas.fr>
  */
 
+
 // Stdlib
-#include <stdlib.h>
+#include <stdint.h>
 
 // Zephyr
+#include <zephyr.h>
 #include <drivers/dma.h>
 
 // STM32
 #include <stm32g4xx_ll_dma.h>
 
 // OwnTech API
+#include "../data_dispatch/data_dispatch.h"
 #include "../adc/adc_channels.h"
 
-// Current file header
-#include "dma.h"
-
 
 /////
 // DT definitions
 #define DMA1_NODELABEL DT_NODELABEL(dma1)
 #define DMA1_LABEL     DT_PROP(DMA1_NODELABEL, label)
 
-#define DMA2_NODELABEL DT_NODELABEL(dma2)
-#define DMA2_LABEL     DT_PROP(DMA2_NODELABEL, label)
-
-// This is the size of the buffer, in terms of readings
-// *per channel*. E.g. if 3 channels in an ADC,
-// the size of the buffer will be 3*DMA_BUFFER_SIZE
-// This value *must be* a multiple of 2 to allow
-// half-transfer interrupts correct handling.
-#define DMA_BUFFER_SIZE 2
+const struct device* _dma1;
 
 /////
-// Variables
-atomic_t dma1_readings_count = ATOMIC_INIT(0);
-atomic_t dma2_readings_count = ATOMIC_INIT(0);
-
-static uint16_t* dma1_buffer = NULL;
-static uint16_t* dma2_buffer = NULL;
-
-static size_t buffer1_size;
-static size_t buffer2_size;
+// Arrays of buffers:
+// half_buffer_*[i][] is an array whose size matches
+// the number of enabled channels in ADC(i+1).
+static uint16_t* half_buffer_1[3];
+static uint16_t* half_buffer_2[3];
 
 
 /////
-// DMA callbacks
-// These callbacks are called twice per buffer filling:
-// when buffer is half-filled and when buffer is filled.
-
-void _dma1_callback(const struct device* dev, void* user_data, uint32_t channel, int status)
-{
-    atomic_inc(&dma1_readings_count);
-}
+// Private API
 
-void _dma2_callback(const struct device* dev, void* user_data, uint32_t channel, int status)
-{
-    atomic_inc(&dma2_readings_count);
-}
-
-
-/////
-// DMA inits
-
-static void _dma1_init()
+/**
+ * DMA callback
+ * This callback is called twice per buffer filling:
+ * when buffer is half-filled and when buffer is filled.
+ */
+static void _dma_callback(const struct device* dev, void* user_data, uint32_t channel, int status)
 {
-    // Prepare buffers
-    buffer1_size = adc_channels_get_channels_count(1) * DMA_BUFFER_SIZE;
-    dma1_buffer = malloc(sizeof(uint16_t) * buffer1_size);
-
-    // Configure DMA
-    struct dma_block_config dma1_block_config = {0};
-    struct dma_config       dma1_config       = {0};
-
-    dma1_block_config.source_address   = (uint32_t)(&(ADC1->DR)); // Source: ADC 1
-    dma1_block_config.dest_address     = (uint32_t)dma1_buffer;   // Dest: array in mem
-    dma1_block_config.block_size       = 2 * buffer1_size;        // Buffer size in bytes
-    dma1_block_config.source_addr_adj  = DMA_ADDR_ADJ_NO_CHANGE;  // Source: no increment in DMA register
-    dma1_block_config.dest_addr_adj    = DMA_ADDR_ADJ_INCREMENT;  // Dest: increment in memory
-    dma1_block_config.source_reload_en = 1;                       // Reload initial address after block
-    dma1_block_config.dest_reload_en   = 1;                       // Reload initial address after block
-
-    dma1_config.dma_slot            = LL_DMAMUX_REQ_ADC1;   // Source: ADC 1
-    dma1_config.channel_direction   = PERIPHERAL_TO_MEMORY; // From periph to mem
-    dma1_config.source_data_size    = 2;                    // 2 bytes
-    dma1_config.dest_data_size      = 2;                    // 2 bytes
-    dma1_config.source_burst_length = 1;                    // No burst
-    dma1_config.dest_burst_length   = 1;                    // No burst
-    dma1_config.block_count         = 1;                    // 1 block
-    dma1_config.head_block          = &dma1_block_config;   // Above block config
-    dma1_config.dma_callback        = _dma1_callback;       // Callback
-
-    const struct device* _dma1 = device_get_binding(DMA1_LABEL);
-    dma_config(_dma1, 1, &dma1_config);
-    // Half-transfer interrupt is not handled by Zephyr driver:
-    // enable it manually.
-    LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1);
+	static uint8_t current_half_buffer[3] = {0};
+
+	if (current_half_buffer[channel] == 0)
+	{
+		data_dispatch_do_dispatch(channel+1, half_buffer_1[channel]);
+		current_half_buffer[channel] = 1;
+	}
+	else
+	{
+		data_dispatch_do_dispatch(channel+1, half_buffer_2[channel]);
+		current_half_buffer[channel] = 0;
+	}
 }
 
-static void _dma2_init()
+/**
+ * DMA init function for one channel
+ */
+static void _dma_channel_init(uint8_t adc_num, uint32_t source_address, uint32_t source_trigger)
 {
-    // Prepare buffers
-    buffer2_size = adc_channels_get_channels_count(2) * DMA_BUFFER_SIZE;
-    dma2_buffer = malloc(sizeof(uint16_t) * buffer2_size);
-
-    // Configure DMA
-    struct dma_block_config dma2_block_config = {0};
-    struct dma_config       dma2_config       = {0};
-
-    dma2_block_config.source_address   = (uint32_t)(&(ADC2->DR)); // Source: ADC 2
-    dma2_block_config.dest_address     = (uint32_t)dma2_buffer;   // Dest: array in mem
-    dma2_block_config.block_size       = 2 * buffer2_size;        // Buffer size in bytes
-    dma2_block_config.source_addr_adj  = DMA_ADDR_ADJ_NO_CHANGE;  // Source: no increment in DMA register
-    dma2_block_config.dest_addr_adj    = DMA_ADDR_ADJ_INCREMENT;  // Dest: increment in memory
-    dma2_block_config.source_reload_en = 1;                       // Reload initial address after block
-    dma2_block_config.dest_reload_en   = 1;                       // Reload initial address after block
-
-    dma2_config.dma_slot            = LL_DMAMUX_REQ_ADC2;   // Source: ADC 2
-    dma2_config.channel_direction   = PERIPHERAL_TO_MEMORY; // From periph to mem
-    dma2_config.source_data_size    = 2;                    // 2 bytes
-    dma2_config.dest_data_size      = 2;                    // 2 bytes
-    dma2_config.source_burst_length = 1;                    // No burst
-    dma2_config.dest_burst_length   = 1;                    // No burst
-    dma2_config.block_count         = 1;                    // 1 block
-    dma2_config.head_block          = &dma2_block_config;   // Above block config
-    dma2_config.dma_callback        = _dma2_callback;       // Callback
-
-    const struct device* _dma2 = device_get_binding(DMA2_LABEL);
-    dma_config(_dma2, 1, &dma2_config);
-    // Half-transfer interrupt is not handled by Zephyr driver:
-    // enable it manually.
-    LL_DMA_EnableIT_HT(DMA2, LL_DMA_CHANNEL_1);
+	// Prepare buffers
+	uint8_t enabled_channels = adc_channels_get_enabled_channels_count(adc_num);
+	size_t dma_buffer_size = enabled_channels * sizeof(uint16_t) * 2;
+	uint8_t adc_index = adc_num - 1;
+
+	uint16_t* dma_buffer = k_malloc(dma_buffer_size);
+	half_buffer_1[adc_index] = dma_buffer;
+	half_buffer_2[adc_index] = dma_buffer + enabled_channels;
+
+	// Configure DMA
+	struct dma_block_config dma_block_config_s = {0};
+	struct dma_config       dma_config_s       = {0};
+
+	dma_block_config_s.source_address   = source_address;         // Source: ADC DR register
+	dma_block_config_s.dest_address     = (uint32_t)dma_buffer;   // Dest: buffer in memory
+	dma_block_config_s.block_size       = dma_buffer_size;        // Buffer size in bytes
+	dma_block_config_s.source_addr_adj  = DMA_ADDR_ADJ_NO_CHANGE; // Source: no increment in ADC register
+	dma_block_config_s.dest_addr_adj    = DMA_ADDR_ADJ_INCREMENT; // Dest: increment in memory
+	dma_block_config_s.source_reload_en = 1;                      // Reload initial address after block; Enables Half-transfer interrupt
+	dma_block_config_s.dest_reload_en   = 1;                      // Reload initial address after block
+
+	dma_config_s.dma_slot            = source_trigger;       // Source: triggered from ADC
+	dma_config_s.channel_direction   = PERIPHERAL_TO_MEMORY; // From periph to mem
+	dma_config_s.source_data_size    = 2;                    // 2 bytes
+	dma_config_s.dest_data_size      = 2;                    // 2 bytes
+	dma_config_s.source_burst_length = 1;                    // No burst
+	dma_config_s.dest_burst_length   = 1;                    // No burst
+	dma_config_s.block_count         = 1;                    // 1 block
+	dma_config_s.head_block          = &dma_block_config_s;  // Above block config
+	dma_config_s.dma_callback        = _dma_callback;        // Callback
+
+	dma_config(_dma1, adc_num, &dma_config_s);
 }
 
 
 /////
 // Public API
 
-/**
- * DMA initialization procedure.
- */
-void dma_init()
+void dma_configure_and_start()
 {
-    __ASSERT( (DMA_BUFFER_SIZE%2 == 0), "WARNING: DMA_BUFFER_SIZE macro must be an even value.");
-
-    // DMA1
-    _dma1_init();
-    const struct device* _dma1 = device_get_binding(DMA1_LABEL);
-    dma_start(_dma1, 1);
-
-    // DMA2
-    _dma2_init();
-    const struct device* _dma2 = device_get_binding(DMA2_LABEL);
-    dma_start(_dma2, 1);
+	_dma1 = device_get_binding(DMA1_LABEL);
+
+	// ADC 1
+	if (adc_channels_get_enabled_channels_count(1) > 0)
+	{
+		_dma_channel_init(1, (uint32_t)(&(ADC1->DR)), LL_DMAMUX_REQ_ADC1);
+		dma_start(_dma1, 1);
+	}
+
+	// ADC 2
+	if (adc_channels_get_enabled_channels_count(2) > 0)
+	{
+		_dma_channel_init(2, (uint32_t)(&(ADC2->DR)), LL_DMAMUX_REQ_ADC2);
+		dma_start(_dma1, 2);
+	}
+
+	// ADC 3
+	if (adc_channels_get_enabled_channels_count(3) > 0)
+	{
+		_dma_channel_init(3, (uint32_t)(&(ADC3->DR)), LL_DMAMUX_REQ_ADC3);
+		dma_start(_dma1, 3);
+	}
 }
 
+
 uint16_t* dma_get_dma1_buffer()
 {
-    return dma1_buffer;
+	return half_buffer_1[0];
 }
 
 uint16_t* dma_get_dma2_buffer()
 {
-    return dma2_buffer;
-}
-
-size_t dma_get_dma1_buffer_size()
-{
-    return buffer1_size;
-}
-
-size_t dma_get_dma2_buffer_size()
-{
-    return buffer2_size;
+	return half_buffer_1[1];
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.h b/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.h
index 321160290ade277f184413e14a7506403b6fc0cc..70e6c7d03c477316a55d0ab8046f57e5f2124c9b 100644
--- a/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.h
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/dma/dma.h
@@ -25,8 +25,7 @@
 #define DMA_H_
 
 
-// Zephyr
-#include <zephyr.h>
+#include <stdint.h>
 
 
 #ifdef __cplusplus
@@ -34,26 +33,20 @@ extern "C" {
 #endif
 
 
-// Public functions
-void dma_init();
+/**
+ * This function configures and starts
+ * the DMA. It must only be called after
+ * all the ADCs configuration has been
+ * carried out, as it uses its channels
+ * configuration to determine the size
+ * of the buffers.
+ */
+void dma_configure_and_start();
 
+// For debug purpose
 uint16_t* dma_get_dma1_buffer();
 uint16_t* dma_get_dma2_buffer();
 
-size_t dma_get_dma1_buffer_size();
-size_t dma_get_dma2_buffer_size();
-
-// Public variables: these variables handle
-// the number of readings stored in the
-// buffer at a specific time. They are
-// incremented by 1 by the DMA transfer
-// interrupts and can be decremented by
-// a task reading the buffers.
-// Each unit in these counts represent
-// a half-buffer amount of readings.
-extern atomic_t dma1_readings_count;
-extern atomic_t dma2_readings_count;
-
 
 #ifdef __cplusplus
 }
diff --git a/zephyr/modules/owntech_data_acquisition/zephyr/public_include/data_acquisition.h b/zephyr/modules/owntech_data_acquisition/zephyr/public_include/data_acquisition.h
new file mode 100644
index 0000000000000000000000000000000000000000..e9287984caf20f8b867826cbdf2cc27bd10e66c1
--- /dev/null
+++ b/zephyr/modules/owntech_data_acquisition/zephyr/public_include/data_acquisition.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2021 LAAS-CNRS
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGLPV2.1
+ */
+
+/**
+ * @author Clément Foucher <clement.foucher@laas.fr>
+ */
+
+#ifndef DATA_ACQUISITION_H_
+#define DATA_ACQUISITION_H_
+
+
+#include <stdint.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/////
+// Error codes
+
+#define ECHANNOTFOUND   -1
+#define EUNITITIALIZED  -2
+#define ESTARTED        -3
+#define EALREADYSTARTED -4
+#define EALREADYINIT    -5
+
+
+/////
+// Configuration API
+
+/**
+ * This function initializes the Data Acquisition module.
+ * It must be called prior to any other function in this module.
+ * @return 0 if initialization went well,
+ *         EALREADYINIT is already initialized.
+ */
+int8_t data_acquisition_init();
+
+/**
+ * Use this function to set ADC 1 and ADC 2 in dual mode.
+ *
+ * This function must be called AFTER data_acquisition_init()
+ * and BEFORE data_acquisition_start().
+ *
+ * @param  dual_mode Status of the dual mode: true to enable,
+ *         false to disable. false by default.
+ * @return 0 is everything went well,
+ *         EUNITITIALIZED if the module has not been initialized,
+ *         EALREADYSTARTED if the module has already been started.
+ */
+int8_t data_acquisition_set_adc12_dual_mode(uint8_t dual_mode);
+
+/**
+ * This function is used to configure the channels to be
+ * enabled on a given ADC.
+ *
+ * This function must be called AFTER data_acquisition_init()
+ * and BEFORE data_acquisition_start().
+  *
+ * @param  adc_number Number of the ADC on which channel configuration is
+ *         to be done.
+ * @param  channel_list List of channels to configure. This is a list of
+ *         names as defined in the device tree (field `label`). The order
+ *         of the names in the array sets the acquisition ranks (order in
+ *         which the channels are acquired).
+ * @param  channel_count Number of channels defined in `channel_list`.
+ * @return 0 is everything went well,
+ *         ECHANNOTFOUND if at least one of the channels
+ *           is not available in the given ADC. Available channels are the
+ *           ones defined in the device tree.
+ *         EUNITITIALIZED if the module has not been initialized,
+ *         EALREADYSTARTED if the module has already been started.
+ */
+int8_t data_acquisition_configure_adc_channels(uint8_t adc_number, char* channel_list[], uint8_t channel_count);
+
+/**
+ * This function is used to configure the trigger source of an ADC.
+ *
+ * This function must be called AFTER data_acquisition_init()
+ * and BEFORE data_acquisition_start().
+ *
+ * TODO: Use an enumeration instead of LL constants for source.
+ *
+ * @param  adc_number Number of the ADC to configure
+ * @param  trigger_source Source of the trigger as defined
+ *         in stm32gxx_ll_adc.h (LL_ADC_REG_TRIG_***)
+ * @return 0 is everything went well,
+ *         EUNITITIALIZED if the module has not been initialized,
+ *         EALREADYSTARTED if the module has already been started.
+ */
+int8_t data_acquisition_configure_adc_trigger_source(uint8_t adc_number, uint32_t trigger_source);
+
+/**
+ * This functions starts the acquisition chain. It must be called
+ * after all module configuration has been carried out. No
+ * configuration change is allowed after module has been started.
+ *
+ * @return 0 is everything went well,
+ *         EUNITITIALIZED if the module has not been initialized,
+ *         EALREADYSTARTED if the module has already been started.
+ */
+int8_t data_acquisition_start();
+
+
+/////
+// Accessor API
+
+/**
+ * This function returns the name of an enabled channel.
+ *
+ * This function must be called AFTER data_acquisition_init()
+ * and AFTER data_acquisition_configure_adc_channels() function
+ * has been called for this channel.
+ *
+ * @param  adc_number Number of the ADC
+ * @param  channel_rank Rank of the ADC channel to query.
+ *         Rank ranges from 0 to (number of enabled channels)-1
+ * @return Name of the channel as defined in the device tree, or
+ *         NULL if channel configuration has not been made or
+ *         channel_rank is over (number of enabled channels)-1.
+ */
+char* data_acquisition_get_channel_name(uint8_t adc_number, uint8_t channel_rank);
+
+/**
+ * Function to access the acquired data for each channel.
+ * Each function provides a buffer in which all data that
+ * have been acquired since last call are stored. The count
+ * of these values is returned as an output parameter: the
+ * user has to define a variable and pass a pointer to this
+ * variable as the parameter of the function. The variable
+ * will be updated with the number of values that are available
+ * in the buffer.
+ *
+ * NOTE 1: When calling one of these functions, it invalidates
+ *         the buffer returned by a previous call to the same function.
+ * NOTE 2: All function buffers are independent.
+ *
+ * @param  number_of_values_acquired Pass a pointer to an uint32_t variable.
+ *         This variable will be updated with the number of values that
+ *         have been acquired for this channel.
+ * @return Pointer to a buffer in which the acquired values are stored.
+ *         If number_of_values_acquired is 0, do not try to access the
+ *         buffer as it may be NULL.
+ */
+uint16_t* data_acquisition_get_v1_low_values(uint32_t* number_of_values_acquired);
+uint16_t* data_acquisition_get_v2_low_values(uint32_t* number_of_values_acquired);
+uint16_t* data_acquisition_get_v_high_values(uint32_t* number_of_values_acquired);
+uint16_t* data_acquisition_get_i1_low_values(uint32_t* number_of_values_acquired);
+uint16_t* data_acquisition_get_i2_low_values(uint32_t* number_of_values_acquired);
+uint16_t* data_acquisition_get_i_high_values(uint32_t* number_of_values_acquired);
+uint16_t* data_acquisition_get_temp_sensor_values(uint32_t* number_of_values_acquired);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // DATA_ACQUISITION_H_
diff --git a/zephyr/nucleo_g474re.overlay b/zephyr/nucleo_g474re.overlay
index 04b45dd36af941884d76a260ddc216e496bf4edc..780a9fce859cd5216c6600641671091f5a53e6e1 100644
--- a/zephyr/nucleo_g474re.overlay
+++ b/zephyr/nucleo_g474re.overlay
@@ -16,6 +16,13 @@
 	status = "okay";
 };
 
+/*
+&adc3 {
+	pinctrl-0 = <&adc3_in1_pb1 >;
+	status = "okay";
+};
+*/
+
 /*******/
 /* SPI */
 /*******/
@@ -77,10 +84,6 @@
 	status = "okay";
 };
 
-&dma2 {
-	status = "okay";
-};
-
 &dmamux1 {
 	status = "okay";
 };
diff --git a/zephyr/prj.conf b/zephyr/prj.conf
index aff1b3e1faa651a268243c7f639dbbc790973107..4165d53541877708f1cd2204de9cae25b43e49a3 100644
--- a/zephyr/prj.conf
+++ b/zephyr/prj.conf
@@ -1,7 +1,6 @@
 # Main application configuration (overrides board-specific settings)
 
-# only very small heap necessary for malloc in printf statements with %f
-CONFIG_HEAP_MEM_POOL_SIZE=256
+CONFIG_HEAP_MEM_POOL_SIZE=2048
 
 CONFIG_CPLUSPLUS=y
 
@@ -28,7 +27,7 @@ CONFIG_HWINFO=y
 # Buses
 CONFIG_GPIO=y
 CONFIG_SERIAL=y
-CONFIG_I2C=y
+#CONFIG_SPI=y
 
 # Console
 CONFIG_CONSOLE_SUBSYS=y