Commit 5c85d374 authored by Clément Foucher's avatar Clément Foucher
Browse files

Complete rewrite of the Data Acquisition module.

- Use various DMA 1 channels for ADC data copy instead of ADC 1 and ADC 2;
- Remove asynchronous data dispatch task in favor of a dispatch within the DMA interrupt;
- Clarify ADC functions purpose;
- Add error codes for Data Acquisition functions;
- Properly commented module headers;
- Made some ADC configuration available to user;
- Allow fine channel configuration for each adc;
- Per-channel data acquisition functions return buffers instead of FIFOs.
parent ede8588f
......@@ -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
......
/ {
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";
};
*/
};
};
if(CONFIG_OWNTECH_DATA_ACQUISITION)
zephyr_include_directories(.)
zephyr_include_directories(./public_include)
zephyr_library()
zephyr_library_sources(
......
......@@ -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);
}
}
......@@ -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
}
......
......@@ -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;