一、硬件了解
从手册可以得知,STM32C0上提供了一如既往的优秀的ADC支持:
在Nucleo-C031C6开发板上,兼容Arduino Uno R3接口的CN8区域,可以直接作为ADC引脚使用:
具体对应开发板上的位置:
二、zephyr中对应的配置
在zephyr/boards/arm/nucleo_c031c6/nucleo_c031c6.dts中,有如下的配置:
这个定义了adc1的基础配置信息。
可以在adc1的通道中,使用pa0、pa1、pa4进行ADC外设测量。
另外,在 zephyr/samples/drivers/adc/boards/nucleo_c031c6.overlay中,提供了具体channel的配置:
/ {
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>;
};
};
上述adc1对应的channel@0、channel@1、channel@4的reg对应dts中adc1_in0_pa0、adc1_in1_pa1、adc1_in4_pa4
另外,配置中的resolution,对应实际要使用的分辨率,可用值见参考手册如下:
三、模拟光线传感器
我手头有一个DFRobot的模拟环境光线传感器:
对应的引脚如下:
这个模拟传感器可以使用+3~5V DC,可以借到开发板上的3.3V 、GND、PA0使用:
四、代码编写
参考 zephyr/samples/drivers/adc/src/main.c,编写如下的代码:
#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;
}
上述代码中,通过 zephyr_user / io_channels 对应 zephyr/samples/drivers/adc/boards/nucleo_c031c6.overlay中配置的adc1 channels。
然后,使用adc_is_ready_dt()、adc_channel_setup_dt()来检查设备和配置设备,再通过adc_sequence_init_dt()来进行数据读取初始化,并使用adc_read_dt()读取数据到sequence。
sequence.buffer对应buf变量,即为读取的原始值,再使用val_to_light()转换原始值到亮度百分比。该值根据配置中的resolution来确定范围。val_to_light()中的4096,也根据resolution来进行设置,2^12对应为4096。
最后,可以调用 adc_raw_to_millivolts_dt()将原始值转换为mV值。
五、输出结果
编译烧录到开发板以后,就可以读取输出了。
当把模拟光线传感器的光敏二极管完全罩住后,输出如下:
正常放置,输出如下:
今天天气比较阴沉,所以亮度较低。
打开灯,或者用手机手电筒照射,输出如下:
上述结果,都为12位分辨率的情况,对应的原始值最大位2^12=4096,电压为3300mV。
如果将分辨率修改位8位:
uint8_t val_to_light(int32_t buf)
{
float temp = 0;
temp = 100*((float) buf/256);
return (uint8_t)temp;
}
则正常情况下的输出如下:
六、总结
上述内容为ADC功能的基础使用。
实际使用中,可以根据光线强度值来进行下一步的处理,例如如果光线强度过低,则自动开灯补光。
其他类似模拟烟雾传感器、模拟湿度传感器等,都可以采用类似方式测量数据和进行处理。
七、参考资料