Skip to content

Commit f5ffd05

Browse files
ananglmbolivar-nordic
authored andcommitted
samples/drivers/adc: Rework to use channel configurations from DT
Allow specifying configuration of channels through child nodes of ADC controllers. This way analog inputs can be specified for channels in SoCs that require this and it is also possible, for example, to use different gain or reference selections for particular channels. This commit also removes a few former limitations of the sample: now it is possible to use more than 2 channels and each channel can use a different ADC controller. Also the ADC resolution to be used can be specified through devicetree. Signed-off-by: Andrzej Głąbek <[email protected]>
1 parent 3e0679f commit f5ffd05

File tree

3 files changed

+141
-95
lines changed

3 files changed

+141
-95
lines changed

samples/drivers/adc/README.rst

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,13 @@ Overview
88

99
This sample demonstrates how to use the ADC driver API.
1010

11-
Depending on the MCU type, it reads the ADC samples of one or two ADC channels
12-
and prints the readings to the console. If supported by the driver, the raw
13-
readings are converted to millivolts.
11+
Depending on the target board, it reads ADC samples from one or more channels
12+
and prints the readings on the console. If voltage of the used reference can
13+
be obtained, the raw readings are converted to millivolts.
1414

1515
The pins of the ADC channels are board-specific. Please refer to the board
1616
or MCU datasheet for further details.
1717

18-
.. note::
19-
20-
This sample does not work on Nordic platforms where there is a distinction
21-
between channel and analog input that requires additional configuration. See
22-
:zephyr_file:`samples/boards/nrf/battery` for an example of using the ADC
23-
infrastructure on Nordic hardware.
24-
25-
2618
Building and Running
2719
********************
2820

@@ -32,7 +24,15 @@ sure that the ADC is enabled (``status = "okay";``).
3224
In addition to that, this sample requires an ADC channel specified in the
3325
``io-channels`` property of the ``zephyr,user`` node. This is usually done with
3426
a devicetree overlay. The example overlay in the ``boards`` subdirectory for
35-
the :ref:`nucleo_l073rz_board` can be easily adjusted for other boards.
27+
the ``nucleo_l073rz`` board can be easily adjusted for other boards.
28+
29+
Configuration of channels (settings like gain, reference, or acquisition time)
30+
can be specified in devicetree, in ADC controller child nodes. Also the ADC
31+
resolution and oversampling setting to be used for particular channels can
32+
be specified there. See :zephyr_file:`boards/nrf52840dk_nrf52840.overlay
33+
<samples/drivers/adc/boards/nrf52840dk_nrf52840.overlay>` for an example of
34+
such setup. If these parameters are not specified in devicetree, default values,
35+
supposed to be supported by most ADCs, are used instead.
3636

3737
Building and Running for ST Nucleo L073RZ
3838
=========================================

samples/drivers/adc/sample.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ tests:
99
harness: console
1010
timeout: 10
1111
harness_config:
12-
type: one_line
12+
type: multi_line
1313
regex:
14-
- "ADC reading: (.*)"
14+
- "ADC reading:"
15+
- " - .+, channel \\d+: \\d+"

samples/drivers/adc/src/main.c

Lines changed: 126 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -13,114 +13,159 @@
1313
#error "No suitable devicetree overlay specified"
1414
#endif
1515

16-
#define ADC_NUM_CHANNELS DT_PROP_LEN(DT_PATH(zephyr_user), io_channels)
16+
#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
17+
ADC_DT_SPEC_GET_BY_IDX(node_id, idx),
1718

18-
#if ADC_NUM_CHANNELS > 2
19-
#error "Currently only 1 or 2 channels supported in this sample"
20-
#endif
19+
/* Data of ADC io-channels specified in devicetree. */
20+
static const struct adc_dt_spec adc_channels[] = {
21+
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels,
22+
DT_SPEC_AND_COMMA)
23+
};
2124

22-
#if ADC_NUM_CHANNELS == 2 && !DT_SAME_NODE( \
23-
DT_PHANDLE_BY_IDX(DT_PATH(zephyr_user), io_channels, 0), \
24-
DT_PHANDLE_BY_IDX(DT_PATH(zephyr_user), io_channels, 1))
25-
#error "Channels have to use the same ADC."
26-
#endif
25+
#define LABEL_AND_COMMA(node_id, prop, idx) \
26+
DT_LABEL(DT_IO_CHANNELS_CTLR_BY_IDX(node_id, idx)),
2727

28-
#define ADC_NODE DT_PHANDLE(DT_PATH(zephyr_user), io_channels)
28+
/* Labels of ADC controllers referenced by the above io-channels. */
29+
static const char *const adc_labels[] = {
30+
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels,
31+
LABEL_AND_COMMA)
32+
};
2933

30-
/* Common settings supported by most ADCs */
34+
/*
35+
* Common settings supported by most ADCs.
36+
* If for a given channel a configuration is available in devicetree, values
37+
* for gain, reference, and acquisition time are taken from there instead of
38+
* those below. Also resolution can be optionally specified together with
39+
* the channel configuration in devicetree. If it is, that value is used
40+
* instead of the default one below.
41+
*/
3142
#define ADC_RESOLUTION 12
3243
#define ADC_GAIN ADC_GAIN_1
3344
#define ADC_REFERENCE ADC_REF_INTERNAL
3445
#define ADC_ACQUISITION_TIME ADC_ACQ_TIME_DEFAULT
3546

36-
#ifdef CONFIG_ADC_NRFX_SAADC
37-
#define ADC_INPUT_POS_OFFSET SAADC_CH_PSELP_PSELP_AnalogInput0
38-
#else
39-
#define ADC_INPUT_POS_OFFSET 0
40-
#endif
41-
42-
/* Get the numbers of up to two channels */
43-
static uint8_t channel_ids[ADC_NUM_CHANNELS] = {
44-
DT_IO_CHANNELS_INPUT_BY_IDX(DT_PATH(zephyr_user), 0),
45-
#if ADC_NUM_CHANNELS == 2
46-
DT_IO_CHANNELS_INPUT_BY_IDX(DT_PATH(zephyr_user), 1)
47-
#endif
48-
};
47+
static void configure_channel(const struct adc_dt_spec *dt_spec,
48+
uint16_t *vref_mv)
49+
{
50+
/* If a configuration for the channel is specified in devicetree,
51+
* use it, otherwise use default settings that should be suitable
52+
* for most ADCs.
53+
*/
54+
if (dt_spec->channel_cfg_dt_node_exists) {
55+
adc_channel_setup(dt_spec->dev, &dt_spec->channel_cfg);
4956

50-
static int16_t sample_buffer[ADC_NUM_CHANNELS];
57+
/* For the internal reference, use the voltage value returned
58+
* by the dedicated API function. For others, use the value
59+
* from devicetree if available.
60+
*/
61+
if (dt_spec->channel_cfg.reference == ADC_REF_INTERNAL) {
62+
*vref_mv = adc_ref_internal(dt_spec->dev);
63+
} else if (dt_spec->vref_mv > 0) {
64+
*vref_mv = dt_spec->vref_mv;
65+
}
66+
} else {
67+
struct adc_channel_cfg channel_cfg = {
68+
.channel_id = dt_spec->channel_id,
69+
.gain = ADC_GAIN,
70+
.reference = ADC_REFERENCE,
71+
.acquisition_time = ADC_ACQUISITION_TIME,
72+
};
5173

52-
struct adc_channel_cfg channel_cfg = {
53-
.gain = ADC_GAIN,
54-
.reference = ADC_REFERENCE,
55-
.acquisition_time = ADC_ACQUISITION_TIME,
56-
/* channel ID will be overwritten below */
57-
.channel_id = 0,
58-
.differential = 0
59-
};
74+
adc_channel_setup(dt_spec->dev, &channel_cfg);
6075

61-
struct adc_sequence sequence = {
62-
/* individual channels will be added below */
63-
.channels = 0,
64-
.buffer = sample_buffer,
65-
/* buffer size in bytes, not number of samples */
66-
.buffer_size = sizeof(sample_buffer),
67-
.resolution = ADC_RESOLUTION,
68-
};
76+
*vref_mv = adc_ref_internal(dt_spec->dev);
77+
}
78+
}
6979

70-
void main(void)
80+
static void prepare_sequence(struct adc_sequence *sequence,
81+
const struct adc_dt_spec *dt_spec)
7182
{
72-
int err;
73-
const struct device *dev_adc = DEVICE_DT_GET(ADC_NODE);
83+
sequence->channels = BIT(dt_spec->channel_id);
84+
sequence->resolution = ADC_RESOLUTION;
85+
sequence->oversampling = 0;
7486

75-
if (!device_is_ready(dev_adc)) {
76-
printk("ADC device not found\n");
77-
return;
87+
if (dt_spec->channel_cfg_dt_node_exists) {
88+
if (dt_spec->resolution) {
89+
sequence->resolution = dt_spec->resolution;
90+
}
91+
if (dt_spec->oversampling) {
92+
sequence->oversampling = dt_spec->oversampling;
93+
}
7894
}
95+
}
7996

80-
/*
81-
* Configure channels individually prior to sampling
82-
*/
83-
for (uint8_t i = 0; i < ADC_NUM_CHANNELS; i++) {
84-
channel_cfg.channel_id = channel_ids[i];
85-
#ifdef CONFIG_ADC_CONFIGURABLE_INPUTS
86-
channel_cfg.input_positive = ADC_INPUT_POS_OFFSET + channel_ids[i];
87-
#endif
97+
static void print_millivolts(int32_t value,
98+
uint16_t vref_mv,
99+
uint8_t resolution,
100+
const struct adc_dt_spec *dt_spec)
101+
{
102+
enum adc_gain gain = ADC_GAIN;
88103

89-
adc_channel_setup(dev_adc, &channel_cfg);
104+
if (dt_spec->channel_cfg_dt_node_exists) {
105+
gain = dt_spec->channel_cfg.gain;
90106

91-
sequence.channels |= BIT(channel_ids[i]);
107+
/*
108+
* For differential channels, one bit less needs to be specified
109+
* for resolution to achieve correct conversion.
110+
*/
111+
if (dt_spec->channel_cfg.differential) {
112+
resolution -= 1;
113+
}
92114
}
93115

94-
int32_t adc_vref = adc_ref_internal(dev_adc);
116+
adc_raw_to_millivolts(vref_mv, gain, resolution, &value);
117+
printk(" = %d mV", value);
118+
}
95119

96-
while (1) {
97-
/*
98-
* Read sequence of channels (fails if not supported by MCU)
99-
*/
100-
err = adc_read(dev_adc, &sequence);
101-
if (err != 0) {
102-
printk("ADC reading failed with error %d.\n", err);
120+
void main(void)
121+
{
122+
int err;
123+
int16_t sample_buffer[1];
124+
struct adc_sequence sequence = {
125+
.buffer = sample_buffer,
126+
/* buffer size in bytes, not number of samples */
127+
.buffer_size = sizeof(sample_buffer),
128+
};
129+
uint16_t vref_mv[ARRAY_SIZE(adc_channels)] = { 0 };
130+
131+
/* Configure channels individually prior to sampling. */
132+
for (uint8_t i = 0; i < ARRAY_SIZE(adc_channels); i++) {
133+
if (!device_is_ready(adc_channels[i].dev)) {
134+
printk("ADC device not found\n");
103135
return;
104136
}
105137

106-
printk("ADC reading:");
107-
for (uint8_t i = 0; i < ADC_NUM_CHANNELS; i++) {
108-
int32_t raw_value = sample_buffer[i];
109-
110-
printk(" %d", raw_value);
111-
if (adc_vref > 0) {
112-
/*
113-
* Convert raw reading to millivolts if driver
114-
* supports reading of ADC reference voltage
115-
*/
116-
int32_t mv_value = raw_value;
117-
118-
adc_raw_to_millivolts(adc_vref, ADC_GAIN,
119-
ADC_RESOLUTION, &mv_value);
120-
printk(" = %d mV ", mv_value);
138+
configure_channel(&adc_channels[i], &vref_mv[i]);
139+
}
140+
141+
while (1) {
142+
printk("ADC reading:\n");
143+
for (uint8_t i = 0; i < ARRAY_SIZE(adc_channels); i++) {
144+
printk(" - %s, channel %d: ",
145+
adc_labels[i], adc_channels[i].channel_id);
146+
147+
prepare_sequence(&sequence, &adc_channels[i]);
148+
149+
err = adc_read(adc_channels[i].dev, &sequence);
150+
if (err < 0) {
151+
printk("error %d\n", err);
152+
continue;
153+
} else {
154+
printk("%d", sample_buffer[0]);
155+
}
156+
157+
/*
158+
* Convert raw reading to millivolts if the reference
159+
* voltage is known.
160+
*/
161+
if (vref_mv[i] > 0) {
162+
print_millivolts(sample_buffer[0],
163+
vref_mv[i],
164+
sequence.resolution,
165+
&adc_channels[i]);
121166
}
167+
printk("\n");
122168
}
123-
printk("\n");
124169

125170
k_sleep(K_MSEC(1000));
126171
}

0 commit comments

Comments
 (0)