跳到主要内容

ADC

1. 简介

  • Omni3576 开发板上提供了两类 AD 接口,分别是温度传感器 (TS-ADC) 和逐次逼近型 ADC (SAR-ADC)。
    • TS-ADC(温度传感器):支持 6 个通道。
    • SAR-ADC(逐次逼近型 ADC):支持 8 通道的单端 12 位 ADC,最高转换速率为 1MSPS,A/D 转换器的时钟频率为 20MHz。

2. IIO 子系统

IIO(Industrial I/O)子系统是 Linux 内核中的一个重要组成部分,专门用于处理各种工业和嵌入式应用中的模拟输入和输出设备。这些设备可以包括传感器、ADC(模数转换器)、DAC(数字模拟转换器)以及其他模拟信号处理设备。IIO 子系统的主要目标是提供一个通用的、统一的框架,以便在 Linux 中支持各种不同类型的模拟设备。

IIO 子系统通过 sysfs 文件系统和用户空间提供了统一的接口,允许用户轻松地访问和配置模拟设备。这意味着应用程序可以使用标准的 Linux 文件I/O操作来与传感器和模拟设备进行通信。

2.读取 ADC(Shell 脚本)

2.1 引脚分布

开发板都默认开启了 ADC 接口,测量范围为0V~1.8V。

2.2 设备目录

/sys/bus/iio/devices 目录中,包含了与系统中检测到的 IIO 设备相关的子目录。每个子目录通常对应一个特定的 IIO 设备,其名称可能包括设备类型和编号。如果您想查看系统存在的 IIO 设备,可以使用如下命令:

# ls /sys/bus/iio/devices/
iio:device0

2.3 设备属性

每个设备子目录中包含了一组属性文件,这些文件用于获取和配置 IIO 设备的各种参数和状态。例如,您可以通过读取这些文件来获取传感器的数据,配置采样频率,或者设置中断阈值。

  1. 查看设备的属性文件

    # ls /sys/bus/iio/devices/iio\:device0/
    of_node in_voltage_scale in_voltage0_raw power
    name trigger buffer subsystem
    uevent in_voltage1_raw dev scan_elements
  2. 属性文件

    在这个目录中,主要用到的文件是 in_voltage0_rawin_voltage1_rawin_voltage_scale

    • in_voltage0_raw 文件包含ADC输入通道0的原始电压值,通常以整数形式表示,可以使用 cat 命令来读取它,例如:

      # cat /sys/bus/iio/devices/iio\:device0/in_voltage0_raw
      1023
    • in_voltage1_raw 文件包含ADC输入通道1的原始电压值,通常以整数形式表示,可以使用 cat 命令来读取它,例如:

      # cat /sys/bus/iio/devices/iio\:device0/in_voltage1_raw
      1022
    • in_voltage_scale 文件包含了一个比例因子,可以将原始电压值转换为实际电压值。您可以使用 cat 命令来读取它,例如:

      # cat /sys/bus/iio/devices/iio\:device0/in_voltage_scale
      1.757812500

2.4 编写shell脚本获取电压值

要获得 ADC 通道0和通道1实际的电压值,您可以编写一个shell脚本分别读取原始电压值 in_voltage0_rawin_voltage1_raw 和比例因子 in_voltage_scale 的值,然后分别将两个原始电压值与比例因子相乘即可,如下所示:

  1. 使用vi编辑器打开shell脚本

    # vi adc.sh
  2. 编写shell脚本

    #!/bin/sh

    echo "Press Ctrl+C to quit"
    ADC_DIR="/sys/bus/iio/devices/iio:device0"

    while true
    do
    scale_value=$(cat "$ADC_DIR/in_voltage_scale")
    IN0_raw_value=$(cat "$ADC_DIR/in_voltage0_raw")
    IN1_raw_value=$(cat "$ADC_DIR/in_voltage1_raw")
    IN0_voltage=$(awk -v raw="$IN0_raw_value" -v scale="$scale_value" 'BEGIN { printf "%.6f\n", raw * scale / 1000 }')
    IN1_voltage=$(awk -v raw="$IN1_raw_value" -v scale="$scale_value" 'BEGIN { printf "%.6f\n", raw * scale / 1000 }')
    echo "IN0_Voltage: $IN0_voltage V,IN1_Voltage: $IN1_voltage V"
    sleep 1
    done
  3. 运行脚本

    # chmod 777 adc.sh
    # ./adc.sh
    Press Ctrl+C to quit
    IN0_Voltage: 1.796484 V,IN1_Voltage: 1.798242 V
    IN0_Voltage: 1.798242 V,IN1_Voltage: 1.798242 V

3.读取ADC(Python程序)

在前文中,我们演示了如何使用 shell 脚本获取实际的电压值。接下来,我们将使用 Python 程序实现电压值的获取。

3.1 完整代码

通过以下程序,可以实现获取 ADC 通道0和通道1的电压值。

import time

ADC_DIR = "/sys/bus/iio/devices/iio:device0"

def read_value(file_path):
with open(file_path, "r") as file:
return file.read().strip()

def main():
print("Press Ctrl+C to quit")
while True:
scale_value = float(read_value(f"{ADC_DIR}/in_voltage_scale"))
IN0_raw_value = float(read_value(f"{ADC_DIR}/in_voltage0_raw"))
IN1_raw_value = float(read_value(f"{ADC_DIR}/in_voltage1_raw"))

IN0_voltage = f"{IN0_raw_value * scale_value / 1000:.2f}"
IN1_voltage = f"{IN1_raw_value * scale_value / 1000:.2f}"

print(f"IN0_Voltage: {IN0_voltage} V, IN1_Voltage: {IN1_voltage} V")
time.sleep(1)

if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass

3.2 读取电压值

这段代码从指定的 ADC 的设备文件中读取用于电压值计算的比例因子和双通道的原始电压值。

scale_value = float(read_value(f"{ADC_DIR}/in_voltage_scale"))
IN0_raw_value = float(read_value(f"{ADC_DIR}/in_voltage0_raw"))
IN1_raw_value = float(read_value(f"{ADC_DIR}/in_voltage1_raw"))

3.3 计算电压值

这段计算了两个输入通道的电压值,结果保留两位小数并以字符串的形式存储在 IN0_voltageIN1_voltage 变量中。

IN0_voltage = f"{IN0_raw_value * scale_value / 1000:.2f}"
IN1_voltage = f"{IN1_raw_value * scale_value / 1000:.2f}"

3.4 运行程序

  1. 使用 nano 工具在终端创建 py 文件,粘贴并保存 python 程序

    # nano adc.py
  2. 运行程序

    # python3 adc.py

4.读取ADC(C程序)

在前文中,我们演示了如何使用 Shell 脚本和 Python 程序获取实际的电压值。此外,我们还可以使用C库函数或系统调用来读取设备文件,从而实现获取电压值的目标。请注意,为了在特定的嵌入式系统上运行程序,通常需要使用交叉编译工具来编译代码,以生成可在目标开发板上执行的可执行文件。接下来,让我们一起探讨具体的实施步骤。

4.1 完整代码

通过以下程序,可以实现获取 ADC 通道0和通道1的电压值。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
printf("Press Ctrl+C to quit\n");
const char *adc_dir = "/sys/bus/iio/devices/iio:device0";
char in_voltage0_raw_path[256];
char in_voltage1_raw_path[256];
char in_voltage_scale_path[256];

sprintf(in_voltage0_raw_path, "%s/in_voltage0_raw", adc_dir);
sprintf(in_voltage1_raw_path, "%s/in_voltage1_raw", adc_dir);
sprintf(in_voltage_scale_path, "%s/in_voltage_scale", adc_dir);

FILE *scale_file = fopen(in_voltage_scale_path, "r");
FILE *in0_raw_file = fopen(in_voltage0_raw_path, "r");
FILE *in1_raw_file = fopen(in_voltage1_raw_path, "r");

while (1) {
char buffer[32];

fseek(scale_file, 0, SEEK_SET);
fseek(in0_raw_file, 0, SEEK_SET);
fseek(in1_raw_file, 0, SEEK_SET);

if (scale_file && in0_raw_file && in1_raw_file) {
fgets(buffer, sizeof(buffer), scale_file);
float scale = strtof(buffer, NULL);

fgets(buffer, sizeof(buffer), in0_raw_file);
int in0_raw_value = atoi(buffer);

fgets(buffer, sizeof(buffer), in1_raw_file);
int in1_raw_value = atoi(buffer);

float in0_voltage = (in0_raw_value * scale) / 1000.0;
float in1_voltage = (in1_raw_value * scale) / 1000.0;

printf("IN0 Voltage: %.6f V, IN1 Voltage: %.6f V\n", in0_voltage, in1_voltage);
}
sleep(1);
}

fclose(scale_file);
fclose(in0_raw_file);
fclose(in1_raw_file);
return 0;
}

4.2 打开文件

这段代码定义了文件路径相关的变量,通过 sprintf 根据 adc_dir 动态构建了三个文件路径并以只读的方式打开这三个文件,分别用于后续读取ADC设备双通道的原始电压值和比例因子。

const char *adc_dir = "/sys/bus/iio/devices/iio:device0";
char in_voltage0_raw_path[256];
char in_voltage1_raw_path[256];
char in_voltage_scale_path[256];

sprintf(in_voltage0_raw_path, "%s/in_voltage0_raw", adc_dir);
sprintf(in_voltage1_raw_path, "%s/in_voltage1_raw", adc_dir);
sprintf(in_voltage_scale_path, "%s/in_voltage_scale", adc_dir);

FILE *scale_file = fopen(in_voltage_scale_path, "r");
FILE *in0_raw_file = fopen(in_voltage0_raw_path, "r");
FILE *in1_raw_file = fopen(in_voltage1_raw_path, "r");

4.3 计算电压值

这段代码实现了一个循环,每秒读取一次ADC设备双通道的原始电压值和比例因子,然后计算并显示出实际电压值,保留小数点后六位。文件操作通过fseek确保每次循环都从文件开头读取数据。

while (1) {
char buffer[32];

fseek(scale_file, 0, SEEK_SET);
fseek(in0_raw_file, 0, SEEK_SET);
fseek(in1_raw_file, 0, SEEK_SET);

if (scale_file && in0_raw_file && in1_raw_file) {
fgets(buffer, sizeof(buffer), scale_file);
float scale = strtof(buffer, NULL);

fgets(buffer, sizeof(buffer), in0_raw_file);
int in0_raw_value = atoi(buffer);

fgets(buffer, sizeof(buffer), in1_raw_file);
int in1_raw_value = atoi(buffer);

float in0_voltage = (in0_raw_value * scale) / 1000.0;
float in1_voltage = (in1_raw_value * scale) / 1000.0;

printf("IN0 Voltage: %.6f V, IN1 Voltage: %.6f V\n", in0_voltage, in1_voltage);
}
sleep(1);
}

5. 设备树简介

  1. ADC DTS 源文件在kernel-6.10/arch/arm64/boot/dts/rockchip/rk3576.dtsi已经定义,我们可以直接调用。

    saradc: adc@2ae00000 {    
    compatible = "rockchip,rk3576-saradc", "rockchip,rk3588-saradc";
    reg = <0x0 0x2ae00000 0x0 0x10000>;
    interrupts = <GIC_SPI 124 IRQ_TYPE_LEVEL_HIGH>;
    #io-channel-cells = <1>;
    clocks = <&cru CLK_SARADC>, <&cru PCLK_SARADC>;
    clock-names = "saradc", "apb_pclk";
    resets = <&cru SRST_P_SARADC>;
    reset-names = "saradc-apb";
    status = "disabled";
    };
  2. 设备文件路径位于kernel-6.1/arch/arm64/boot/dts/rockchip/luckfox-core3576.dtsi,开启saradc的代码片段如下:

    &saradc {
    status = "okay";
    vref-supply = <&vcca_1v8_s0>;
    };