1. Hardware Understanding
From the manual, we can know that STM32C0 provides the same excellent ADC support as always:
On the Nucleo-C031C6 development board, the CN8 area is compatible with the Arduino Uno R3 interface and can be used directly as an ADC pin:
Specific corresponding location on the development board:
2. Corresponding configuration in zephyr
In zephyr/boards/arm/nucleo_c031c6/nucleo_c031c6.dts, there is the following configuration:
This defines the basic configuration information of adc1.
In the adc1 channel, you can use pa0, pa1, and pa4 to perform ADC peripheral measurements.
In addition, in zephyr/samples/drivers/adc/boards/nucleo_c031c6.overlay, the configuration of specific channels is provided:
/ {
zephyr,user {
/* adjust channel number according to pinmux in board.dts */
io-channels = <&adc1 0>, <&adc1 1>, <&adc1 4>;
};
};
&adc1 {
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
channel[url=home.php?mod=space&uid=490]@1[/url] {
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
channel[url=home.php?mod=space&uid=28485]@4[/url] {
reg = <4>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};
The regs of channel@0, channel@1, and channel@4 corresponding to the above adc1 correspond to adc1_in0_pa0, adc1_in1_pa1, and adc1_in4_pa4 in dts.
In addition, the resolution in the configuration corresponds to the actual resolution to be used. The available values are shown in the reference manual as follows:
3. Analog Light Sensor
I have a simulated ambient light sensor from DFRobot:
The corresponding pins are as follows:
This analog sensor can use +3~5V DC, which can be borrowed from the 3.3V, GND, and PA0 on the development board:
4. Code Writing
Refer to zephyr/samples/drivers/adc/src/main.c and write the following code:
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \
!DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels)
#error "No suitable devicetree overlay specified"
#endif
#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
ADC_DT_SPEC_GET_BY_IDX(node_id, idx),
/* Data of ADC io-channels specified in devicetree. */
static const struct adc_dt_spec adc_channels[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels,
DT_SPEC_AND_COMMA)
};
uint8_t val_to_light(int32_t buf)
{
float temp = 0;
temp = 100*((float) buf/4096);
return (uint8_t)temp;
}
int main(void)
{
int err;
uint32_t count = 0;
uint16_t buf;
struct adc_sequence sequence = {
.buffer = &buf,
/* buffer size in bytes, not number of samples */
.buffer_size = sizeof(buf),
};
/* Configure channels individually prior to sampling. */
for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
if (!adc_is_ready_dt(&adc_channels[i])) {
printk("ADC controller device %s not ready\n", adc_channels[i].dev->name);
return 0;
}
err = adc_channel_setup_dt(&adc_channels[i]);
if (err < 0) {
printk("Could not setup channel #%d (%d)\n", i, err);
return 0;
}
}
struct adc_dt_spec adc_channel = adc_channels[0];
while (1) {
printk("ADC reading[%u]:\n", count++);
int32_t val_mv;
uint8_t light;
printk("- %s, channel %d: ",
adc_channel.dev->name,
adc_channel.channel_id);
(void)adc_sequence_init_dt(&adc_channel, &sequence);
err = adc_read_dt(&adc_channel, &sequence);
if (err < 0) {
printk("Could not read (%d)\n", err);
continue;
}
/*
* If using differential mode, the 16 bit value
* in the ADC sample buffer should be a signed 2's
* complement value.
*/
if (adc_channel.channel_cfg.differential) {
val_mv = (int32_t)((int16_t)buf);
} else {
val_mv = (int32_t)buf;
}
printk("%"PRId32, val_mv);
light = val_to_light(val_mv);
err = adc_raw_to_millivolts_dt(&adc_channel,
&val_mv);
/* conversion to mV may not be supported, skip if not */
if (err < 0) {
printk(" (value in mV not available)\n");
} else {
printk(" = %"PRId32" mV", val_mv);
}
printk(", Light is %"PRId8"%%\n\n", light);
k_sleep(K_MSEC(1000));
}
return 0;
}
In the above code, zephyr_user / io_channels corresponds to the adc1 channels configured in zephyr/samples/drivers/adc/boards/nucleo_c031c6.overlay.
Then, use adc_is_ready_dt() and adc_channel_setup_dt() to check and configure the device, initialize data reading through adc_sequence_init_dt(), and use adc_read_dt() to read data into sequence.
sequence.buffer corresponds to the buf variable, which is the original value read. Then val_to_light() is used to convert the original value to the brightness percentage. The value range is determined by the resolution in the configuration. The 4096 in val_to_light() is also set according to the resolution, and 2^12 corresponds to 4096.
Finally, adc_raw_to_millivolts_dt() can be called to convert the raw value to a mV value.
5. Output results
After compiling and burning to the development board, you can read the output.
When the photodiode of the simulated light sensor is completely covered, the output is as follows:
Normal placement, the output is as follows:
The weather is rather gloomy today, so the brightness is low.
Turn on the light, or shine a flashlight from your phone, and the output is as follows:
The above results are all for 12-bit resolution, and the corresponding maximum original value is 2^12=4096, and the voltage is 3300mV.
If you change the resolution to 8 bits:
uint8_t val_to_light(int32_t buf)
{
float temp = 0;
temp = 100*((float) buf/256);
return (uint8_t)temp;
}
The normal output is as follows:
VI. Conclusion
The above content is the basic usage of ADC function.
In actual use, the next step of processing can be performed according to the light intensity value. For example, if the light intensity is too low, the light is automatically turned on to provide fill light.
Other similar analog smoke sensors, analog humidity sensors, etc. can measure and process data in a similar manner.
VII. References