跳到主要内容

ADC

在本章中,我们将学习如何使用 iio 在用户空间的设备文件读取开发板上的 ADC 数据。

示例程序:Code.zip

1.IIO子系统

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

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

2.读取ADC(Shell脚本)

2.1 引脚分布

开发板都默认开启了 ADC 接口,测量范围为0V~1.8V。我们通过接口图可以看到,开发板 ADC 接口对应的引脚编号为144和145。LuckFox Pico Ultra/Ultra W 引脚图:

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
  3. 实验现象

    获取ADC双通道电压值:

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);
}

4.4 交叉编译

  1. 指定交叉编译工具

    首先,我们要将交叉编译工具的路径添加到系统的 PATH 环境变量中,以便可以在任何地方使用交叉编译工具,您可以在shell配置文件中添加以下行(通常是 ~/.bashrc~/.bash_profile~/.zshrc,具体取决于您使用的shell),注意 PATH= 后的路径为交叉编译工具所在的目录。

    • gcc路径

      <SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
    • 打开shell配置文件

      vi ~/.bashrc 
    • 将交叉编译工具的路径添加到系统的PATH环境变量中,将 <SDK Directory> 修改为自己的 SDK 路径,如 /home/luckfox/luckfox-pico/

      export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
    • 重新加载shell配置文件,使更改生效

      source ~/.bashrc  
  2. 使用交叉编译工具编译程序

    arm-rockchip830-linux-uclibcgnueabihf-gcc adc.c -o adc
  3. 交叉编译成功后,将在当前目录下生成可在开发板运行的可执行文件

    # ls
    adc adc.c

4.5 运行程序

  1. 文件传输

    先将 adc 从虚拟机传输到 Windows,再通过 TFTP 或 ADB 传输到开发板,将文件从 Windows 通过 ADB 将文件传输到开发板的步骤如下:

    adb push 文件所在路径 开发板存储路径

    eg:(将当前目录下adc文件传输到开发板的根目录)
    adb push adc /
  2. 运行程序

    修改 adc 文件的操作权限后运行程序

    # chmod 777 adc
    # ./adc
  3. 实验现象

    获取ADC双通道电压值: