Browse Source
ADC emulator is designed to be used in tests on native_posix board. It supports 1-16 bit resolution range and all GAINs from enum adc_gain. Reference voltages and number of emulated channels are set through dts. Using special API from drivers/adc/adc_emul.h it is possible to set constant voltage value returned by given ADC channel or set custom function which allows to simulate complex output. Also reference voltages can be changed in runtime using the API. The CL also includes: - Add adc definitions of ADC emulator in tests/drivers/adc/adc_api/src/test_adc.c for supporting test suites. - Add test for ADC emulator API in tests/drivers/adc/adc_emul/ Signed-off-by: Tomasz Michalec <tm@semihalf.com>pull/34974/head
19 changed files with 1505 additions and 0 deletions
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
# Copyright 2021 Google LLC |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
config ADC_EMUL |
||||
bool "ADC emulator" |
||||
help |
||||
Enable the ADC emulator driver. This is a fake driver in that it |
||||
does not talk to real hardware. It prenteds to be actual ADC. It |
||||
is used for testing higher-level API for ADC devices. |
||||
|
||||
if ADC_EMUL |
||||
|
||||
config ADC_EMUL_ACQUISITION_THREAD_STACK_SIZE |
||||
int "Stack size for the ADC data acquisition thread" |
||||
default 512 |
||||
help |
||||
Size of the stack used for the internal data acquisition |
||||
thread. Increasing size may be required when value function for |
||||
emulated ADC require a lot of memory. |
||||
|
||||
config ADC_EMUL_ACQUISITION_THREAD_PRIO |
||||
int "Priority for the ADC data acquisition thread" |
||||
default 0 |
||||
help |
||||
Priority level for the internal ADC data acquisition thread. |
||||
|
||||
endif # ADC_EMUL |
@ -0,0 +1,580 @@
@@ -0,0 +1,580 @@
|
||||
/**
|
||||
* @file |
||||
* |
||||
* @brief Emulated ADC driver |
||||
*/ |
||||
|
||||
/*
|
||||
* Copyright 2021 Google LLC |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#define DT_DRV_COMPAT zephyr_adc_emul |
||||
|
||||
#include <drivers/adc.h> |
||||
#include <drivers/adc/adc_emul.h> |
||||
#include <kernel.h> |
||||
#include <logging/log.h> |
||||
#include <sys/byteorder.h> |
||||
#include <sys/util.h> |
||||
#include <zephyr.h> |
||||
|
||||
LOG_MODULE_REGISTER(adc_emul, CONFIG_ADC_LOG_LEVEL); |
||||
|
||||
#define ADC_CONTEXT_USES_KERNEL_TIMER |
||||
#include "adc_context.h" |
||||
|
||||
#define ADC_EMUL_MAX_RESOLUTION 16 |
||||
|
||||
typedef uint16_t adc_emul_res_t; |
||||
|
||||
enum adc_emul_input_source { |
||||
ADC_EMUL_CONST_VALUE, |
||||
ADC_EMUL_CUSTOM_FUNC, |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Channel of emulated ADC config |
||||
* |
||||
* This structure contains configuration of one channel of emualted ADC. |
||||
*/ |
||||
struct adc_emul_chan_cfg { |
||||
/** Pointer to function used to obtain input mV */ |
||||
adc_emul_value_func func; |
||||
/** Pointer to data that are passed to @a func on call */ |
||||
void *func_data; |
||||
/** Constant mV input value */ |
||||
uint32_t const_value; |
||||
/** Gain used on output value */ |
||||
enum adc_gain gain; |
||||
/** Reference source */ |
||||
enum adc_reference ref; |
||||
/** Input source which is used to obtain input value */ |
||||
enum adc_emul_input_source input; |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Emulated ADC config |
||||
* |
||||
* This structure contains constant data for given instance of emulated ADC. |
||||
*/ |
||||
struct adc_emul_config { |
||||
/** Number of supported channels */ |
||||
uint8_t num_channels; |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Emulated ADC data |
||||
* |
||||
* This structure contains data structures used by a emulated ADC. |
||||
*/ |
||||
struct adc_emul_data { |
||||
/** Structure that handle state of ongoing read operation */ |
||||
struct adc_context ctx; |
||||
/** Pointer to ADC emulator own device structure */ |
||||
const struct device *dev; |
||||
/** Pointer to memory where next sample will be written */ |
||||
uint16_t *buf; |
||||
/** Pointer to where will be data stored in case of repeated sampling */ |
||||
uint16_t *repeat_buf; |
||||
/** Mask with channels that will be sampled */ |
||||
uint32_t channels; |
||||
/** Mask created from requested resolution in read operation */ |
||||
uint16_t res_mask; |
||||
/** Reference voltage for ADC_REF_VDD_1 source */ |
||||
uint16_t ref_vdd; |
||||
/** Reference voltage for ADC_REF_EXTERNAL0 source */ |
||||
uint16_t ref_ext0; |
||||
/** Reference voltage for ADC_REF_EXTERNAL1 source */ |
||||
uint16_t ref_ext1; |
||||
/** Reference voltage for ADC_REF_INTERNAL source */ |
||||
uint16_t ref_int; |
||||
/** Array of each channel configuration */ |
||||
struct adc_emul_chan_cfg *chan_cfg; |
||||
/** Structure used for acquisition thread */ |
||||
struct k_thread thread; |
||||
/** Semaphore used to control acquisiton thread */ |
||||
struct k_sem sem; |
||||
/** Mutex used to control access to channels config and ref voltages */ |
||||
struct k_mutex cfg_mtx; |
||||
|
||||
/** Stack for acquisition thread */ |
||||
K_KERNEL_STACK_MEMBER(stack, |
||||
CONFIG_ADC_EMUL_ACQUISITION_THREAD_STACK_SIZE); |
||||
}; |
||||
|
||||
int adc_emul_const_value_set(const struct device *dev, unsigned int chan, |
||||
uint32_t value) |
||||
{ |
||||
const struct adc_emul_config *config = dev->config; |
||||
struct adc_emul_data *data = dev->data; |
||||
struct adc_emul_chan_cfg *chan_cfg; |
||||
|
||||
if (chan >= config->num_channels) { |
||||
LOG_ERR("unsupported channel %d", chan); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
chan_cfg = &data->chan_cfg[chan]; |
||||
|
||||
k_mutex_lock(&data->cfg_mtx, K_FOREVER); |
||||
|
||||
chan_cfg->input = ADC_EMUL_CONST_VALUE; |
||||
chan_cfg->const_value = value; |
||||
|
||||
k_mutex_unlock(&data->cfg_mtx); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int adc_emul_value_func_set(const struct device *dev, unsigned int chan, |
||||
adc_emul_value_func func, void *func_data) |
||||
{ |
||||
const struct adc_emul_config *config = dev->config; |
||||
struct adc_emul_data *data = dev->data; |
||||
struct adc_emul_chan_cfg *chan_cfg; |
||||
|
||||
if (chan >= config->num_channels) { |
||||
LOG_ERR("unsupported channel %d", chan); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
chan_cfg = &data->chan_cfg[chan]; |
||||
|
||||
k_mutex_lock(&data->cfg_mtx, K_FOREVER); |
||||
|
||||
chan_cfg->func = func; |
||||
chan_cfg->func_data = func_data; |
||||
chan_cfg->input = ADC_EMUL_CUSTOM_FUNC; |
||||
|
||||
k_mutex_unlock(&data->cfg_mtx); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int adc_emul_ref_voltage_set(const struct device *dev, enum adc_reference ref, |
||||
uint16_t value) |
||||
{ |
||||
struct adc_driver_api *api = (struct adc_driver_api *)dev->api; |
||||
struct adc_emul_data *data = dev->data; |
||||
int err = 0; |
||||
|
||||
k_mutex_lock(&data->cfg_mtx, K_FOREVER); |
||||
|
||||
switch (ref) { |
||||
case ADC_REF_VDD_1: |
||||
data->ref_vdd = value; |
||||
break; |
||||
case ADC_REF_INTERNAL: |
||||
data->ref_int = value; |
||||
api->ref_internal = value; |
||||
break; |
||||
case ADC_REF_EXTERNAL0: |
||||
data->ref_ext0 = value; |
||||
break; |
||||
case ADC_REF_EXTERNAL1: |
||||
data->ref_ext1 = value; |
||||
break; |
||||
default: |
||||
err = -EINVAL; |
||||
} |
||||
|
||||
k_mutex_unlock(&data->cfg_mtx); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Convert @p ref to reference voltage value in mV |
||||
* |
||||
* @param data Internal data of ADC emulator |
||||
* @param ref Select which reference source should be used |
||||
* |
||||
* @return Reference voltage in mV |
||||
* @return 0 on error |
||||
*/ |
||||
static uint16_t adc_emul_get_ref_voltage(struct adc_emul_data *data, |
||||
enum adc_reference ref) |
||||
{ |
||||
uint16_t voltage; |
||||
|
||||
k_mutex_lock(&data->cfg_mtx, K_FOREVER); |
||||
|
||||
switch (ref) { |
||||
case ADC_REF_VDD_1: |
||||
voltage = data->ref_vdd; |
||||
break; |
||||
case ADC_REF_VDD_1_2: |
||||
voltage = data->ref_vdd / 2; |
||||
break; |
||||
case ADC_REF_VDD_1_3: |
||||
voltage = data->ref_vdd / 3; |
||||
break; |
||||
case ADC_REF_VDD_1_4: |
||||
voltage = data->ref_vdd / 4; |
||||
break; |
||||
case ADC_REF_INTERNAL: |
||||
voltage = data->ref_int; |
||||
break; |
||||
case ADC_REF_EXTERNAL0: |
||||
voltage = data->ref_ext0; |
||||
break; |
||||
case ADC_REF_EXTERNAL1: |
||||
voltage = data->ref_ext1; |
||||
break; |
||||
default: |
||||
voltage = 0; |
||||
} |
||||
|
||||
k_mutex_unlock(&data->cfg_mtx); |
||||
|
||||
return voltage; |
||||
} |
||||
|
||||
static int adc_emul_channel_setup(const struct device *dev, |
||||
const struct adc_channel_cfg *channel_cfg) |
||||
{ |
||||
const struct adc_emul_config *config = dev->config; |
||||
struct adc_emul_chan_cfg *emul_chan_cfg; |
||||
struct adc_emul_data *data = dev->data; |
||||
|
||||
if (channel_cfg->channel_id >= config->num_channels) { |
||||
LOG_ERR("unsupported channel id '%d'", channel_cfg->channel_id); |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
if (adc_emul_get_ref_voltage(data, channel_cfg->reference) == 0) { |
||||
LOG_ERR("unsupported channel reference '%d'", |
||||
channel_cfg->reference); |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
if (channel_cfg->differential) { |
||||
LOG_ERR("unsupported differential mode"); |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
emul_chan_cfg = &data->chan_cfg[channel_cfg->channel_id]; |
||||
|
||||
k_mutex_lock(&data->cfg_mtx, K_FOREVER); |
||||
|
||||
emul_chan_cfg->gain = channel_cfg->gain; |
||||
emul_chan_cfg->ref = channel_cfg->reference; |
||||
|
||||
k_mutex_unlock(&data->cfg_mtx); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Check if buffer in @p sequence is big enough to hold all ADC samples |
||||
* |
||||
* @param dev ADC emulator device |
||||
* @param sequence ADC sequence description |
||||
* |
||||
* @return 0 on success |
||||
* @return -ENOMEM if buffer is not big enough |
||||
*/ |
||||
static int adc_emul_check_buffer_size(const struct device *dev, |
||||
const struct adc_sequence *sequence) |
||||
{ |
||||
const struct adc_emul_config *config = dev->config; |
||||
uint8_t channels = 0; |
||||
size_t needed; |
||||
uint32_t mask; |
||||
|
||||
for (mask = BIT(config->num_channels - 1); mask != 0; mask >>= 1) { |
||||
if (mask & sequence->channels) { |
||||
channels++; |
||||
} |
||||
} |
||||
|
||||
needed = channels * sizeof(adc_emul_res_t); |
||||
if (sequence->options) { |
||||
needed *= (1 + sequence->options->extra_samplings); |
||||
} |
||||
|
||||
if (sequence->buffer_size < needed) { |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Start processing read request |
||||
* |
||||
* @param dev ADC emulator device |
||||
* @param sequence ADC sequence description |
||||
* |
||||
* @return 0 on success |
||||
* @return -ENOTSUP if requested resolution or channel is out side of supported |
||||
* range |
||||
* @return -ENOMEM if buffer is not big enough |
||||
* (see @ref adc_emul_check_buffer_size) |
||||
* @return other error code returned by adc_context_wait_for_completion |
||||
*/ |
||||
static int adc_emul_start_read(const struct device *dev, |
||||
const struct adc_sequence *sequence) |
||||
{ |
||||
const struct adc_emul_config *config = dev->config; |
||||
struct adc_emul_data *data = dev->data; |
||||
int err; |
||||
|
||||
if (sequence->resolution > ADC_EMUL_MAX_RESOLUTION || |
||||
sequence->resolution == 0) { |
||||
LOG_ERR("unsupported resolution %d", sequence->resolution); |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
if (find_msb_set(sequence->channels) > config->num_channels) { |
||||
LOG_ERR("unsupported channels in mask: 0x%08x", |
||||
sequence->channels); |
||||
return -ENOTSUP; |
||||
} |
||||
|
||||
err = adc_emul_check_buffer_size(dev, sequence); |
||||
if (err) { |
||||
LOG_ERR("buffer size too small"); |
||||
return err; |
||||
} |
||||
|
||||
data->res_mask = BIT_MASK(sequence->resolution); |
||||
data->buf = sequence->buffer; |
||||
adc_context_start_read(&data->ctx, sequence); |
||||
|
||||
return adc_context_wait_for_completion(&data->ctx); |
||||
} |
||||
|
||||
static int adc_emul_read_async(const struct device *dev, |
||||
const struct adc_sequence *sequence, |
||||
struct k_poll_signal *async) |
||||
{ |
||||
struct adc_emul_data *data = dev->data; |
||||
int err; |
||||
|
||||
adc_context_lock(&data->ctx, async ? true : false, async); |
||||
err = adc_emul_start_read(dev, sequence); |
||||
adc_context_release(&data->ctx, err); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
static int adc_emul_read(const struct device *dev, |
||||
const struct adc_sequence *sequence) |
||||
{ |
||||
return adc_emul_read_async(dev, sequence, NULL); |
||||
} |
||||
|
||||
static void adc_context_start_sampling(struct adc_context *ctx) |
||||
{ |
||||
struct adc_emul_data *data = CONTAINER_OF(ctx, struct adc_emul_data, |
||||
ctx); |
||||
|
||||
data->channels = ctx->sequence.channels; |
||||
data->repeat_buf = data->buf; |
||||
|
||||
k_sem_give(&data->sem); |
||||
} |
||||
|
||||
static void adc_context_update_buffer_pointer(struct adc_context *ctx, |
||||
bool repeat_sampling) |
||||
{ |
||||
struct adc_emul_data *data = CONTAINER_OF(ctx, struct adc_emul_data, |
||||
ctx); |
||||
|
||||
if (repeat_sampling) { |
||||
data->buf = data->repeat_buf; |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief Convert input voltage of ADC @p chan to raw output value |
||||
* |
||||
* @param data Internal data of ADC emulator |
||||
* @param chan ADC channel to sample |
||||
* @param result Raw output value |
||||
* |
||||
* @return 0 on success |
||||
* @return -EINVAL if failed to get reference voltage or unknown input is |
||||
* selected |
||||
* @return other error code returned by custom function |
||||
*/ |
||||
static int adc_emul_get_chan_value(struct adc_emul_data *data, |
||||
unsigned int chan, |
||||
adc_emul_res_t *result) |
||||
{ |
||||
struct adc_emul_chan_cfg *chan_cfg = &data->chan_cfg[chan]; |
||||
uint32_t input_mV; |
||||
uint32_t ref_v; |
||||
uint64_t temp; /* Temporary 64 bit value prevent overflows */ |
||||
int err = 0; |
||||
|
||||
k_mutex_lock(&data->cfg_mtx, K_FOREVER); |
||||
|
||||
/* Get input voltage */ |
||||
switch (chan_cfg->input) { |
||||
case ADC_EMUL_CONST_VALUE: |
||||
input_mV = chan_cfg->const_value; |
||||
break; |
||||
|
||||
case ADC_EMUL_CUSTOM_FUNC: |
||||
err = chan_cfg->func(data->dev, chan, chan_cfg->func_data, |
||||
&input_mV); |
||||
if (err) { |
||||
LOG_ERR("failed to read channel %d (err %d)", |
||||
chan, err); |
||||
goto out; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
LOG_ERR("unknown input source %d", chan_cfg->input); |
||||
err = -EINVAL; |
||||
goto out; |
||||
} |
||||
|
||||
/* Get reference voltage and apply inverted gain */ |
||||
ref_v = adc_emul_get_ref_voltage(data, chan_cfg->ref); |
||||
err = adc_gain_invert(chan_cfg->gain, &ref_v); |
||||
if (ref_v == 0 || err) { |
||||
LOG_ERR("failed to get ref voltage (channel %d)", chan); |
||||
err = -EINVAL; |
||||
goto out; |
||||
} |
||||
|
||||
/* Calculate output value */ |
||||
temp = (uint64_t)input_mV * data->res_mask / ref_v; |
||||
|
||||
/* If output value is greater than resolution, it has to be trimmed */ |
||||
if (temp > data->res_mask) { |
||||
temp = data->res_mask; |
||||
} |
||||
|
||||
*result = temp; |
||||
|
||||
out: |
||||
k_mutex_unlock(&data->cfg_mtx); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Main function of thread which is used to collect samples from |
||||
* emulated ADC. When adc_context_start_sampling give semaphore, |
||||
* for each requested channel value function is called. Returned |
||||
* mV value is converted to output using reference voltage, gain |
||||
* and requested resolution. |
||||
* |
||||
* @param data Internal data of ADC emulator |
||||
* |
||||
* @return This thread should not end |
||||
*/ |
||||
static void adc_emul_acquisition_thread(struct adc_emul_data *data) |
||||
{ |
||||
int err; |
||||
|
||||
while (true) { |
||||
k_sem_take(&data->sem, K_FOREVER); |
||||
|
||||
err = 0; |
||||
|
||||
while (data->channels) { |
||||
adc_emul_res_t result = 0; |
||||
unsigned int chan = find_lsb_set(data->channels) - 1; |
||||
|
||||
LOG_DBG("reading channel %d", chan); |
||||
|
||||
err = adc_emul_get_chan_value(data, chan, &result); |
||||
if (err) { |
||||
adc_context_complete(&data->ctx, err); |
||||
break; |
||||
} |
||||
|
||||
LOG_DBG("read channel %d, result = %d", chan, result); |
||||
|
||||
*data->buf++ = result; |
||||
WRITE_BIT(data->channels, chan, 0); |
||||
} |
||||
|
||||
if (!err) { |
||||
adc_context_on_sampling_done(&data->ctx, data->dev); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief Function called on init for each ADC emulator device. It setups all |
||||
* channels to return constant 0 mV and create acquisition thread. |
||||
* |
||||
* @param dev ADC emulator device |
||||
* |
||||
* @return 0 on success |
||||
*/ |
||||
static int adc_emul_init(const struct device *dev) |
||||
{ |
||||
const struct adc_emul_config *config = dev->config; |
||||
struct adc_emul_data *data = dev->data; |
||||
int chan; |
||||
|
||||
data->dev = dev; |
||||
|
||||
k_sem_init(&data->sem, 0, 1); |
||||
k_mutex_init(&data->cfg_mtx); |
||||
|
||||
for (chan = 0; chan < config->num_channels; chan++) { |
||||
struct adc_emul_chan_cfg *chan_cfg = &data->chan_cfg[chan]; |
||||
|
||||
chan_cfg->func = NULL; |
||||
chan_cfg->func_data = NULL; |
||||
chan_cfg->input = ADC_EMUL_CONST_VALUE; |
||||
chan_cfg->const_value = 0; |
||||
} |
||||
|
||||
k_thread_create(&data->thread, data->stack, |
||||
CONFIG_ADC_EMUL_ACQUISITION_THREAD_STACK_SIZE, |
||||
(k_thread_entry_t)adc_emul_acquisition_thread, |
||||
data, NULL, NULL, |
||||
CONFIG_ADC_EMUL_ACQUISITION_THREAD_PRIO, |
||||
0, K_NO_WAIT); |
||||
|
||||
adc_context_unlock_unconditionally(&data->ctx); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
#define ADC_EMUL_INIT(_num) \ |
||||
static struct adc_driver_api adc_emul_api_##_num = { \ |
||||
.channel_setup = adc_emul_channel_setup, \ |
||||
.read = adc_emul_read, \ |
||||
.ref_internal = DT_INST_PROP(_num, ref_internal_mv), \ |
||||
IF_ENABLED(CONFIG_ADC_ASYNC, \ |
||||
(.read_async = adc_emul_read_async,)) \ |
||||
}; \ |
||||
\ |
||||
static struct adc_emul_chan_cfg \ |
||||
adc_emul_ch_cfg_##_num[DT_INST_PROP(_num, nchannels)]; \ |
||||
\ |
||||
static const struct adc_emul_config adc_emul_config_##_num = { \ |
||||
.num_channels = DT_INST_PROP(_num, nchannels), \ |
||||
}; \ |
||||
\ |
||||
static struct adc_emul_data adc_emul_data_##_num = { \ |
||||
ADC_CONTEXT_INIT_TIMER(adc_emul_data_##_num, ctx), \ |
||||
ADC_CONTEXT_INIT_LOCK(adc_emul_data_##_num, ctx), \ |
||||
ADC_CONTEXT_INIT_SYNC(adc_emul_data_##_num, ctx), \ |
||||
.chan_cfg = adc_emul_ch_cfg_##_num, \ |
||||
.ref_vdd = DT_INST_PROP(_num, ref_vdd_mv), \ |
||||
.ref_ext0 = DT_INST_PROP(_num, ref_external0_mv), \ |
||||
.ref_ext1 = DT_INST_PROP(_num, ref_external1_mv), \ |
||||
.ref_int = DT_INST_PROP(_num, ref_internal_mv), \ |
||||
}; \ |
||||
\ |
||||
DEVICE_DT_INST_DEFINE(_num, adc_emul_init, NULL, \ |
||||
&adc_emul_data_##_num, \ |
||||
&adc_emul_config_##_num, POST_KERNEL, \ |
||||
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
||||
&adc_emul_api_##_num) |
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(ADC_EMUL_INIT); |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
# Copyright 2021 Google LLC |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
description: Zephyr ADC Emulator |
||||
|
||||
compatible: "zephyr,adc-emul" |
||||
|
||||
include: adc-controller.yaml |
||||
|
||||
properties: |
||||
nchannels: |
||||
type: int |
||||
required: true |
||||
description: Number of emulated ADC channels. Should be in 1-32 range. |
||||
|
||||
ref-internal-mv: |
||||
type: int |
||||
required: false |
||||
default: 0 |
||||
description: |
||||
Internal reference voltage in mV. If not provided or set to zero, |
||||
channel setup with ADC_REF_INTERNAL will fail. |
||||
|
||||
ref-vdd-mv: |
||||
type: int |
||||
required: false |
||||
default: 0 |
||||
description: |
||||
VDD reference voltage in mV. If not provided or set to zero, |
||||
channel setup with ADC_REF_VDD_X will fail. |
||||
|
||||
ref-external0-mv: |
||||
type: int |
||||
required: false |
||||
default: 0 |
||||
description: |
||||
External 0 reference voltage in mV. If not provided or set to zero, |
||||
channel setup with ADC_REF_EXTERNAL0 will fail. |
||||
|
||||
ref-external1-mv: |
||||
type: int |
||||
required: false |
||||
default: 0 |
||||
description: |
||||
External 1 reference voltage in mV. If not provided or set to zero, |
||||
channel setup with ADC_REF_EXTERNAL1 will fail. |
@ -0,0 +1,110 @@
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @file |
||||
* |
||||
* @brief Backend API for emulated ADC |
||||
*/ |
||||
|
||||
/*
|
||||
* Copyright 2021 Google LLC |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
#ifndef ZEPHYR_INCLUDE_DRIVERS_ADC_ADC_EMUL_H_ |
||||
#define ZEPHYR_INCLUDE_DRIVERS_ADC_ADC_EMUL_H_ |
||||
|
||||
#include <zephyr/types.h> |
||||
#include <drivers/adc.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief Emulated ADC backend API |
||||
* @defgroup adc_emul Emulated ADC |
||||
* @ingroup adc_interface |
||||
* @{ |
||||
* |
||||
* Behaviour of emulated ADC is application-defined. As-such, each |
||||
* application may |
||||
* |
||||
* - define a Device Tree overlay file to indicate the number of ADC |
||||
* controllers as well as the number of channels for each controller |
||||
* - set default reference voltages in Device Tree or using |
||||
* @ref adc_emul_ref_voltage_set |
||||
* - asynchronously call @ref adc_emul_const_value_set in order to set |
||||
* constant mV value on emulated ADC input |
||||
* - asynchronously call @ref adc_emul_value_func_set in order to assign |
||||
* function which will be used to obtain voltage on emulated ADC input |
||||
* |
||||
* An example of an appropriate Device Tree overlay file is in |
||||
* tests/drivers/adc/adc_api/boards/native_posix.overlay |
||||
* |
||||
* An example of using emulated ADC backend API is in the file |
||||
* tests/drivers/adc/adc_emul/src/main.c |
||||
*/ |
||||
|
||||
/**
|
||||
* @brief Type definition of the function which is used to obtain ADC |
||||
* mV input values |
||||
* |
||||
* @param dev Pointer to the device structure for the driver instance |
||||
* @param chan ADC channel to sample |
||||
* @param data User data which was passed on @ref adc_emul_value_func_set |
||||
* @param result The result value which will be set as input for ADC @p chan |
||||
* |
||||
* @return 0 on success |
||||
* @return other as error code which ends ADC context |
||||
*/ |
||||
typedef int (*adc_emul_value_func)(const struct device *dev, unsigned int chan, |
||||
void *data, uint32_t *result); |
||||
|
||||
/**
|
||||
* @brief Set constant mV value input for emulated ADC @p chan |
||||
* |
||||
* @param dev The emulated ADC device |
||||
* @param chan The channel of ADC which input is assigned |
||||
* @param value New voltage in mV to assign to @p chan input |
||||
* |
||||
* @return 0 on success |
||||
* @return -EINVAL if an invalid argument is provided |
||||
*/ |
||||
int adc_emul_const_value_set(const struct device *dev, unsigned int chan, |
||||
uint32_t value); |
||||
|
||||
/**
|
||||
* @brief Set function used to obtain voltage for input of emulated |
||||
* ADC @p chan |
||||
* |
||||
* @param dev The emulated ADC device |
||||
* @param chan The channel of ADC to which @p func is assigned |
||||
* @param func New function to assign to @p chan |
||||
* @param data Pointer to data passed to @p func on call |
||||
* |
||||
* @return 0 on success |
||||
* @return -EINVAL if an invalid argument is provided |
||||
*/ |
||||
int adc_emul_value_func_set(const struct device *dev, unsigned int chan, |
||||
adc_emul_value_func func, void *data); |
||||
|
||||
/**
|
||||
* @brief Set reference voltage |
||||
* |
||||
* @param dev The emulated ADC device |
||||
* @param ref Reference config which is changed |
||||
* @param value New reference voltage in mV |
||||
* |
||||
* @return 0 on success |
||||
* @return -EINVAL if an invalid argument is provided |
||||
*/ |
||||
int adc_emul_ref_voltage_set(const struct device *dev, enum adc_reference ref, |
||||
uint16_t value); |
||||
/**
|
||||
* @} |
||||
*/ |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* ZEPHYR_INCLUDE_DRIVERS_ADC_ADC_EMUL_H_ */ |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
# Copyright 2021 Google LLC |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
CONFIG_ADC_EMUL=y |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
/* |
||||
* Copyright 2021 Google LLC |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
/ { |
||||
adc0: adc { |
||||
compatible = "zephyr,adc-emul"; |
||||
nchannels = <2>; |
||||
ref-internal-mv = <3300>; |
||||
ref-external1-mv = <5000>; |
||||
#io-channel-cells = <1>; |
||||
label = "ADC_0"; |
||||
status = "okay"; |
||||
}; |
||||
}; |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
# Copyright 2021 Google LLC |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
CONFIG_ADC_EMUL=y |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
/* |
||||
* Copyright 2021 Google LLC |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
#include "native_posix.overlay" |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
# Copyright 2021 Google LLC |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
cmake_minimum_required(VERSION 3.13.1) |
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) |
||||
project(adc_emul) |
||||
|
||||
FILE(GLOB app_sources src/*.c) |
||||
target_sources(app PRIVATE ${app_sources}) |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
/* |
||||
* Copyright 2021 Google LLC |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
/ { |
||||
adc_emulator_test_dev { |
||||
compatible = "zephyr,adc-emul"; |
||||
nchannels = <2>; |
||||
ref-internal-mv = <3300>; |
||||
ref-external1-mv = <5000>; |
||||
#io-channel-cells = <1>; |
||||
label = "ADC_0"; |
||||
status = "okay"; |
||||
}; |
||||
}; |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
CONFIG_ZTEST=y |
||||
|
||||
CONFIG_ADC=y |
||||
CONFIG_ADC_LOG_LEVEL_INF=y |
||||
CONFIG_HEAP_MEM_POOL_SIZE=1024 |
||||
CONFIG_TEST_USERSPACE=y |
||||
CONFIG_ADC_EMUL=y |
@ -0,0 +1,656 @@
@@ -0,0 +1,656 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
#include <drivers/adc.h> |
||||
#include <drivers/adc/adc_emul.h> |
||||
#include <zephyr.h> |
||||
#include <ztest.h> |
||||
|
||||
#define ADC_DEVICE_NAME DT_LABEL(DT_INST(0, zephyr_adc_emul)) |
||||
#define ADC_REF_INTERNAL_MV DT_PROP(DT_INST(0, zephyr_adc_emul), ref_internal_mv) |
||||
#define ADC_REF_EXTERNAL1_MV DT_PROP(DT_INST(0, zephyr_adc_emul), ref_external1_mv) |
||||
#define ADC_RESOLUTION 14 |
||||
#define ADC_ACQUISITION_TIME ADC_ACQ_TIME_DEFAULT |
||||
#define ADC_1ST_CHANNEL_ID 0 |
||||
#define ADC_2ND_CHANNEL_ID 1 |
||||
|
||||
#define INVALID_ADC_VALUE SHRT_MIN |
||||
/* Raw to milivolt conversion doesn't handle rounding */ |
||||
#define MV_OUTPUT_EPS 2 |
||||
#define SEQUENCE_STEP 100 |
||||
|
||||
#define BUFFER_SIZE 6 |
||||
static ZTEST_BMEM int16_t m_sample_buffer[BUFFER_SIZE]; |
||||
|
||||
/**
|
||||
* @brief Get ADC emulated device |
||||
* |
||||
* @return pointer to ADC device |
||||
*/ |
||||
const struct device *get_adc_device(void) |
||||
{ |
||||
const struct device *adc_dev = device_get_binding(ADC_DEVICE_NAME); |
||||
|
||||
zassert_not_null(adc_dev, "Cannot get ADC device"); |
||||
|
||||
return adc_dev; |
||||
} |
||||
|
||||
/**
|
||||
* @brief Setup channel with specific reference and gain |
||||
* |
||||
* @param adc_dev Pointer to ADC device |
||||
* @param ref ADC reference voltage source |
||||
* @param gain Gain applied to ADC @p channel |
||||
* @param channel ADC channel which is being setup |
||||
* |
||||
* @return none |
||||
*/ |
||||
static void channel_setup(const struct device *adc_dev, enum adc_reference ref, |
||||
enum adc_gain gain, int channel) |
||||
{ |
||||
int ret; |
||||
struct adc_channel_cfg channel_cfg = { |
||||
.gain = gain, |
||||
.reference = ref, |
||||
.acquisition_time = ADC_ACQUISITION_TIME, |
||||
.channel_id = channel, |
||||
}; |
||||
|
||||
ret = adc_channel_setup(adc_dev, &channel_cfg); |
||||
zassert_ok(ret, "Setting up of the %d channel failed with code %d", |
||||
channel, ret); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Check if samples for specific channel are correct. It can check |
||||
* samples with arithmetic sequence with common difference. |
||||
* |
||||
* @param expected_count Number of samples that are expected to be set |
||||
* @param start_mv_value Voltage in mV that was set on input at the beginning |
||||
* of sampling |
||||
* @param step Common difference in arithmetic sequence which describes how |
||||
* samples were generated |
||||
* @param num_channels Number of channels that were sampled |
||||
* @param channel_id ADC channel from which samples are checked |
||||
* @param ref_mv Reference voltage in mV |
||||
* @param gain Gain applied to ADC @p channel_id |
||||
* |
||||
* @return none |
||||
*/ |
||||
static void check_samples(int expected_count, int32_t start_mv_value, int step, |
||||
int num_channels, int channel_id, int32_t ref_mv, |
||||
enum adc_gain gain) |
||||
{ |
||||
int32_t output, expected; |
||||
int i, ret; |
||||
|
||||
for (i = channel_id; i < expected_count; i += num_channels) { |
||||
expected = start_mv_value + i / num_channels * step; |
||||
output = m_sample_buffer[i]; |
||||
ret = adc_raw_to_millivolts(ref_mv, gain, ADC_RESOLUTION, |
||||
&output); |
||||
zassert_ok(ret, "adc_raw_to_millivolts() failed with code %d", |
||||
ret); |
||||
zassert_within(expected, output, MV_OUTPUT_EPS, |
||||
"%u != %u [%u] should has set value", |
||||
expected, output, i); |
||||
} |
||||
|
||||
} |
||||
|
||||
/**
|
||||
* @brief Check if any values in buffer were set after expected samples. |
||||
* |
||||
* @param expected_count Number of samples that are expected to be set |
||||
* |
||||
* @return none |
||||
*/ |
||||
static void check_empty_samples(int expected_count) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = expected_count; i < BUFFER_SIZE; i++) |
||||
zassert_equal(INVALID_ADC_VALUE, m_sample_buffer[i], |
||||
"[%u] should be empty", i); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Run adc_read for given channels and collect specified number of |
||||
* samples. |
||||
* |
||||
* @param adc_dev Pointer to ADC device |
||||
* @param channel_mask Mask of channels that will be sampled |
||||
* @param samples Number of requested samples for each channel |
||||
* |
||||
* @return none |
||||
*/ |
||||
static void start_adc_read(const struct device *adc_dev, uint32_t channel_mask, |
||||
int samples) |
||||
{ |
||||
int ret; |
||||
const struct adc_sequence_options *options_ptr; |
||||
|
||||
const struct adc_sequence_options options = { |
||||
.extra_samplings = samples - 1, |
||||
}; |
||||
|
||||
if (samples > 1) { |
||||
options_ptr = &options; |
||||
} else { |
||||
options_ptr = NULL; |
||||
} |
||||
|
||||
const struct adc_sequence sequence = { |
||||
.options = options_ptr, |
||||
.channels = channel_mask, |
||||
.buffer = m_sample_buffer, |
||||
.buffer_size = sizeof(m_sample_buffer), |
||||
.resolution = ADC_RESOLUTION, |
||||
}; |
||||
|
||||
ret = adc_read(adc_dev, &sequence); |
||||
zassert_ok(ret, "adc_read() failed with code %d", ret); |
||||
} |
||||
|
||||
/** @brief Data for handle_seq function */ |
||||
struct handle_seq_params { |
||||
/** Current input value in mV */ |
||||
unsigned int value; |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Simple custom function to set as value input function for emulated |
||||
* ADC channel. It returns arithmetic sequence with SEQUENCE_STEP |
||||
* as common difference, starting from param value. |
||||
* |
||||
* @param dev Pointer to ADC device |
||||
* @param channel ADC channel for which input value is requested |
||||
* @param data Pointer to function parameters. It has to be |
||||
* struct handle_seq_params type |
||||
* @param result Pointer where input value should be stored |
||||
* |
||||
* @return 0 on success |
||||
* @return -EINVAL when current input value equals 0 |
||||
*/ |
||||
static int handle_seq(const struct device *dev, unsigned int channel, |
||||
void *data, uint32_t *result) |
||||
{ |
||||
struct handle_seq_params *param = data; |
||||
|
||||
if (param->value == 0) { |
||||
return -EINVAL; |
||||
} |
||||
|
||||
*result = param->value; |
||||
param->value += SEQUENCE_STEP; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/** @brief Test setting one channel with constant output. */ |
||||
static void test_adc_emul_single_value(void) |
||||
{ |
||||
const uint16_t input_mv = 1500; |
||||
const int samples = 4; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples, input_mv, 0 /* step */, 1 /* channels */, |
||||
0 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples); |
||||
} |
||||
|
||||
/** @brief Test setting two channels with different constant output */ |
||||
static void test_adc_emul_single_value_2ch(void) |
||||
{ |
||||
const uint16_t input1_mv = 3000; |
||||
const uint16_t input2_mv = 2000; |
||||
const int samples = 3; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input1_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | |
||||
BIT(ADC_2ND_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, |
||||
0 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1); |
||||
check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, |
||||
1 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
} |
||||
|
||||
/** @brief Test setting one channel with custom function. */ |
||||
static void test_adc_emul_custom_function(void) |
||||
{ |
||||
struct handle_seq_params channel1_param; |
||||
const uint16_t input_mv = 1500; |
||||
const int samples = 4; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
channel1_param.value = input_mv; |
||||
|
||||
ret = adc_emul_value_func_set(adc_dev, ADC_1ST_CHANNEL_ID, |
||||
handle_seq, &channel1_param); |
||||
zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples, input_mv, SEQUENCE_STEP, 1 /* channels */, |
||||
0 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Test setting two channels with custom function and different |
||||
* params. |
||||
*/ |
||||
static void test_adc_emul_custom_function_2ch(void) |
||||
{ |
||||
struct handle_seq_params channel1_param; |
||||
struct handle_seq_params channel2_param; |
||||
const uint16_t input1_mv = 1500; |
||||
const uint16_t input2_mv = 1000; |
||||
const int samples = 3; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
channel1_param.value = input1_mv; |
||||
channel2_param.value = input2_mv; |
||||
|
||||
ret = adc_emul_value_func_set(adc_dev, ADC_1ST_CHANNEL_ID, |
||||
handle_seq, &channel1_param); |
||||
zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_value_func_set(adc_dev, ADC_2ND_CHANNEL_ID, |
||||
handle_seq, &channel2_param); |
||||
zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | |
||||
BIT(ADC_2ND_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input1_mv, SEQUENCE_STEP, |
||||
2 /* channels */, 0 /* first channel data */, |
||||
ADC_REF_INTERNAL_MV, ADC_GAIN_1); |
||||
check_samples(samples * 2, input2_mv, SEQUENCE_STEP, |
||||
2 /* channels */, 1 /* first channel data */, |
||||
ADC_REF_INTERNAL_MV, ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Test setting two channels, one with custom function and |
||||
* one with constant value. |
||||
*/ |
||||
static void test_adc_emul_custom_function_and_value(void) |
||||
{ |
||||
struct handle_seq_params channel1_param; |
||||
const uint16_t input1_mv = 1500; |
||||
const uint16_t input2_mv = 1000; |
||||
const int samples = 3; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
channel1_param.value = input1_mv; |
||||
|
||||
ret = adc_emul_value_func_set(adc_dev, ADC_1ST_CHANNEL_ID, |
||||
handle_seq, &channel1_param); |
||||
zassert_ok(ret, "adc_emul_value_func_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | |
||||
BIT(ADC_2ND_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input1_mv, SEQUENCE_STEP, |
||||
2 /* channels */, 0 /* first channel data */, |
||||
ADC_REF_INTERNAL_MV, ADC_GAIN_1); |
||||
check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, |
||||
1 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
} |
||||
|
||||
/** @brief Test few different settings of gain argument. */ |
||||
static void test_adc_emul_gain(void) |
||||
{ |
||||
const uint16_t input_mv = 1000; |
||||
uint32_t channel_mask; |
||||
const int samples = 3; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1_6, |
||||
ADC_1ST_CHANNEL_ID); |
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_3, |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
channel_mask = BIT(ADC_1ST_CHANNEL_ID) | BIT(ADC_2ND_CHANNEL_ID); |
||||
|
||||
ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, channel_mask, samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, |
||||
0 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1_6); |
||||
check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, |
||||
1 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_3); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
|
||||
/* Change gain and re-run test */ |
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1_4, |
||||
ADC_1ST_CHANNEL_ID); |
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_2_3, |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, channel_mask, samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, |
||||
0 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1_4); |
||||
check_samples(samples * 2, input_mv, 0 /* step */, 2 /* channels */, |
||||
1 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_2_3); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Test behaviour on input higher than reference. Return value should be |
||||
* cropped to reference value and cannot exceed resolution requested in |
||||
* adc_read(). |
||||
*/ |
||||
static void test_adc_emul_input_higher_than_ref(void) |
||||
{ |
||||
const uint16_t input_mv = ADC_REF_INTERNAL_MV + 100; |
||||
const int samples = 4; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID), samples); |
||||
|
||||
/*
|
||||
* Check samples - returned value should max out on reference value. |
||||
* Raw value shoudn't exceed resolution. |
||||
*/ |
||||
check_samples(samples, ADC_REF_INTERNAL_MV, 0 /* step */, |
||||
1 /* channels */, 0 /* first channel data */, |
||||
ADC_REF_INTERNAL_MV, ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples); |
||||
|
||||
for (i = 0; i < samples; i++) |
||||
zassert_equal(BIT_MASK(ADC_RESOLUTION), m_sample_buffer[i], |
||||
"[%u] raw value isn't max value", i); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Test different reference sources and if error is reported when |
||||
* unconfigured reference source is requested. |
||||
*/ |
||||
static void test_adc_emul_reference(void) |
||||
{ |
||||
const uint16_t input1_mv = 4000; |
||||
const uint16_t input2_mv = 2000; |
||||
const int samples = 3; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_EXTERNAL1, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
|
||||
struct adc_channel_cfg channel_cfg = { |
||||
.gain = ADC_GAIN_1, |
||||
/* Reference value not setup in DTS */ |
||||
.reference = ADC_REF_EXTERNAL0, |
||||
.acquisition_time = ADC_ACQUISITION_TIME, |
||||
.channel_id = ADC_2ND_CHANNEL_ID, |
||||
}; |
||||
|
||||
ret = adc_channel_setup(adc_dev, &channel_cfg); |
||||
zassert_not_equal(ret, 0, |
||||
"Setting up of the %d channel shuldn't succeeded", |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input1_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | |
||||
BIT(ADC_2ND_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, |
||||
0 /* first channel data */, ADC_REF_EXTERNAL1_MV, |
||||
ADC_GAIN_1); |
||||
check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, |
||||
1 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
} |
||||
|
||||
/** @brief Test setting reference value. */ |
||||
static void test_adc_emul_ref_voltage_set(void) |
||||
{ |
||||
const uint16_t input1_mv = 4000; |
||||
const uint16_t input2_mv = 2000; |
||||
const uint16_t ref1_mv = 6000; |
||||
const uint16_t ref2_mv = 9000; |
||||
const int samples = 3; |
||||
int ret, i; |
||||
|
||||
for (i = 0; i < BUFFER_SIZE; ++i) { |
||||
m_sample_buffer[i] = INVALID_ADC_VALUE; |
||||
} |
||||
|
||||
/* Generic ADC setup */ |
||||
const struct device *adc_dev = get_adc_device(); |
||||
|
||||
channel_setup(adc_dev, ADC_REF_EXTERNAL1, ADC_GAIN_1, |
||||
ADC_1ST_CHANNEL_ID); |
||||
channel_setup(adc_dev, ADC_REF_INTERNAL, ADC_GAIN_1, |
||||
ADC_2ND_CHANNEL_ID); |
||||
|
||||
/* ADC emulator-specific setup */ |
||||
ret = adc_emul_const_value_set(adc_dev, ADC_1ST_CHANNEL_ID, input1_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_const_value_set(adc_dev, ADC_2ND_CHANNEL_ID, input2_mv); |
||||
zassert_ok(ret, "adc_emul_const_value_set() failed with code %d", ret); |
||||
|
||||
/* Change reference voltage */ |
||||
ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_EXTERNAL1, ref1_mv); |
||||
zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_INTERNAL, ref2_mv); |
||||
zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | |
||||
BIT(ADC_2ND_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, |
||||
0 /* first channel data */, ref1_mv, ADC_GAIN_1); |
||||
check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, |
||||
1 /* first channel data */, ref2_mv, ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
|
||||
/* Set previous reference voltage value */ |
||||
ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_EXTERNAL1, |
||||
ADC_REF_EXTERNAL1_MV); |
||||
zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); |
||||
|
||||
ret = adc_emul_ref_voltage_set(adc_dev, ADC_REF_INTERNAL, |
||||
ADC_REF_INTERNAL_MV); |
||||
zassert_ok(ret, "adc_emul_ref_voltage_set() failed with code %d", ret); |
||||
|
||||
/* Test sampling */ |
||||
start_adc_read(adc_dev, BIT(ADC_1ST_CHANNEL_ID) | |
||||
BIT(ADC_2ND_CHANNEL_ID), samples); |
||||
|
||||
/* Check samples */ |
||||
check_samples(samples * 2, input1_mv, 0 /* step */, 2 /* channels */, |
||||
0 /* first channel data */, ADC_REF_EXTERNAL1_MV, |
||||
ADC_GAIN_1); |
||||
check_samples(samples * 2, input2_mv, 0 /* step */, 2 /* channels */, |
||||
1 /* first channel data */, ADC_REF_INTERNAL_MV, |
||||
ADC_GAIN_1); |
||||
|
||||
check_empty_samples(samples * 2); |
||||
} |
||||
|
||||
void test_main(void) |
||||
{ |
||||
k_object_access_grant(get_adc_device(), k_current_get()); |
||||
|
||||
ztest_test_suite(adc_basic_test, |
||||
ztest_user_unit_test(test_adc_emul_single_value), |
||||
ztest_user_unit_test(test_adc_emul_single_value_2ch), |
||||
ztest_user_unit_test(test_adc_emul_custom_function), |
||||
ztest_user_unit_test(test_adc_emul_custom_function_2ch), |
||||
ztest_user_unit_test(test_adc_emul_custom_function_and_value), |
||||
ztest_user_unit_test(test_adc_emul_gain), |
||||
ztest_user_unit_test(test_adc_emul_input_higher_than_ref), |
||||
ztest_user_unit_test(test_adc_emul_reference), |
||||
ztest_user_unit_test(test_adc_emul_ref_voltage_set)); |
||||
ztest_run_test_suite(adc_basic_test); |
||||
} |
Loading…
Reference in new issue