zephyr实现ADC方式有那些?
在 Zephyr RTOS 中,使用 ADC 主要有三种官方示例所展示的标准方式,它们可以满足从简单到复杂的各类采集需求。
下面的表格可以帮你快速了解它们的区别:
| 特性 | 方式一: ADC with devicetree | 方式二: ADC sequence sample | 方式三: Generic ADC stream |
|---|---|---|---|
| 核心思路 | 通过设备树 (devicetree) 静态配置通道,简化代码 | 在代码中动态配置通道,进行单次或多次序列采样 | 使用流式API (Stream API),持续、高效地采集数据 |
| 配置方式 | 设备树(.dts/.overlay) 中定义io-channels和通道参数 | C 代码中填充adc_channel_cfg结构体 | 设备树中为 ADC 设备添加adc0别名 |
| 典型场景 | 推荐方式,通道固定、配置不变的工程 | 需要动态调整通道或采样顺序的场景 | 需要高频率、持续采样,如音频、传感器数据流 |
| 上手难度 | 低 | 中 | 高 |
📝 方式一:ADC with devicetree(推荐)
这是 Zephyr最推荐的使用方式,通过设备树将硬件配置与代码逻辑分离,使代码更简洁、可移植性更强。你的vbat.c正是采用了这种方式。
核心步骤:
设备树配置:在板级或应用 Overlay 文件中,于
zephyr,user节点下通过io-channels属性指定 ADC 通道。通道的具体参数(如增益、参考电压)也在设备树中配置。代码实现:
使用
ADC_DT_SPEC_GET_BY_IDX等宏,自动从设备树生成通道规格数组adc_dt_spec。调用
adc_channel_setup_dt()和adc_sequence_init_dt()进行初始化。调用
adc_read_dt()触发读取。
优点:代码与硬件解耦,修改通道或参数时无需改动 C 代码,只需更新设备树文件。
🎯 方式二:ADC sequence sample
此方式更侧重于在C 代码中灵活控制采样序列,适用于需要动态配置通道或采样顺序的场景。
核心步骤:
设备获取:直接通过
DEVICE_DT_GET()或device_get_binding()获取 ADC 设备实例。通道配置:在代码中填充
adc_channel_cfg结构体,并调用adc_channel_setup()进行配置。定义序列:填充
adc_sequence结构体,指定缓冲区、通道等。可通过adc_sequence_options设置采样间隔等高级选项。执行读取:调用
adc_read()触发采样。
🌊 方式三:Generic ADC stream
这是为需要持续、高速数据流的场景(如音频采集)设计的高效 API。
核心步骤:
设备树别名:需要为 ADC 设备设置
adc0别名。流式读取:使用专门的 Stream API 来管理和处理持续涌入的 ADC 数据。
除了以上三种主要方式,Zephyr 的 ADC 驱动还提供了一些高级特性:
异步读取:支持不阻塞线程的异步读取,完成后通过回调函数通知。
连续采样模式:通过配置
adc_sequence_options的extra_samplings等字段,可在一次请求中完成多次采样。内部温度传感器:部分 SoC 支持读取内部温度传感器,可与普通 ADC 采样功能共存。
DMA 支持:许多 ADC 驱动底层实现了 DMA 支持,可高效传输大量数据。
💎 总结与建议
对于大多数嵌入式应用,强烈推荐使用“方式一:ADC with devicetree”。这是 Zephyr 的现代开发范式,能最大程度提升代码的可移植性和可维护性。
下面是方式一参考程序:
/*
* Copyright (c) 2020 Libre Solar Technologies GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#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_FOR_INPUTS(node_id, prop, idx) \
COND_CODE_1(DT_PHA_HAS_CELL_AT_IDX(node_id, prop, idx, input), \
(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_FOR_INPUTS)
};
int main(void)
{
int err;
uint32_t count = 0;
uint32_t buf = 0;
struct adc_sequence sequence = {
.buffer = &buf,
/* buffer size in bytes, not number of samples */
.buffer_size = sizeof(buf),
#if CONFIG_SAMPLE_ADC_CALIBRATE_REQUIRED
.calibrate = true,
#endif
};
/* 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;
}
}
#ifndef CONFIG_COVERAGE
while (1) {
#else
for (int k = 0; k < 10; k++) {
#endif
printk("ADC reading[%u]:\n", count++);
for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
int32_t val_mv;
/*
* Clear buffer before reading. This ensures the upper 16-bits will be zero
* when the adc uses a 16-bit buffer size.
*/
buf = 0;
printk("- %s, channel %d: ",
adc_channels[i].dev->name,
adc_channels[i].channel_id);
(void)adc_sequence_init_dt(&adc_channels[i], &sequence);
err = adc_read_dt(&adc_channels[i], &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_channels[i].channel_cfg.differential) {
val_mv = (int32_t)((int16_t)buf);
} else {
val_mv = (int32_t)buf;
}
printk("%"PRId32, val_mv);
err = adc_raw_to_millivolts_dt(&adc_channels[i],
&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\n", val_mv);
}
}
k_sleep(K_MSEC(1000));
}
return 0;
}
