Skip to main content

ADC

In this chapter, we will learn how to read ADC data from the development board using IIO in user space device files.

Sample program : Code.zip

1. IIO Subsystem

The IIO (Industrial I/O) subsystem is an integral part of the Linux kernel designed specifically for handling various analog input and output devices in industrial and embedded applications. These devices can include sensors, ADCs (Analog-to-Digital Converters), DACs (Digital-to-Analog Converters), and other analog signal processing devices. The primary goal of the IIO subsystem is to provide a universal and unified framework for supporting various types of analog devices in Linux.

The IIO subsystem offers a uniform interface through the sysfs file system and user space, allowing users to easily access and configure analog devices. This means that applications can communicate with sensors and analog devices using standard Linux file I/O operations.

2. Read ADC (Shell Script)

2.1 Pinout

Development boards typically come with enabled ADC interfaces, with a measurement range of 0V to 1.8V. We can see from the pinout diagram that four development boards have ADC interfaces corresponding to pin numbers 144 and 145.

  1. LuckFox Pico Diagram:
  2. LuckFox Pico Mini A/B Diagram:
  3. LuckFox Pico Plus Diagram:

2.2 Device Directory

The /sys/bus/iio/devices directory contains subdirectories related to IIO devices detected in the system. Each subdirectory typically corresponds to a specific IIO device, and its name may include the device type and number. If you want to view the IIO devices present in the system, you can use the following command:

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

2.3 Device Properties

Each device subdirectory contains a set of property files used to retrieve and configure various parameters and states of the IIO device. For example, you can read these files to get sensor data, configure the sampling frequency, or set interrupt thresholds.

  1. View the property files of a device

    # 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. Property Files

    In this directory, the main files used are in_voltage0_raw, in_voltage1_raw, and in_voltage_scale.

    • The in_voltage0_raw file contains the raw voltage value of ADC input channel 0, usually represented as an integer. You can read it using the cat command, for example:

      # cat /sys/bus/iio/devices/iio\:device0/in_voltage0_raw
      1023
    • The in_voltage1_raw file contains the raw voltage value of ADC input channel 1, usually represented as an integer. You can read it using the cat command, for example:

      # cat /sys/bus/iio/devices/iio\:device0/in_voltage1_raw
      1022
    • The in_voltage_scale file contains a scaling factor that can be used to convert raw voltage values to actual voltage values. You can read it using the cat command, for example:

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

2.4 Writing a Shell Script to Get Voltage Values

To obtain the actual voltage values for ADC channels 0 and 1, you can write a shell script to read the values of the raw voltage in_voltage0_raw, in_voltage1_raw, and the scaling factor in_voltage_scale, and then multiply the two raw voltage values by the scaling factor. Here's an example:

  1. Use a text editor like vi to create a shell script:

    # vi adc.sh
  2. Write the shell script:

    #!/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. Run the script.

    # 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. Read ADC (Python Program)

In the previous section, we demonstrated how to retrieve actual voltage values using a shell script. Next, we will achieve voltage value retrieval using a Python program.

3.1 Full Code

With the following program, you can retrieve the voltage values for ADC channels 0 and 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 Read Voltage Values

This code reads the scale factor for voltage value calculation and the raw voltage values for the dual channels from the specified ADC device files.

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 Calculate Voltage Values

This section calculates the voltage values for two input channels. The results are rounded to two decimal places and stored as strings in the IN0_voltage and IN1_voltage variables.

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

3.4 Running the Program

  1. Use the vi tool in the terminal to create a Python file, paste the Python program, and save it.

    # nano adc.py
  2. Run the program.

    # python3 adc.py
  3. Experimental Observations

    Obtain ADC dual-channel voltage values:

4. Read ADC (C Program)

In the previous sections, we demonstrated how to retrieve actual voltage values using both Shell scripts and Python programs. Additionally, we can achieve the goal of obtaining voltage values by using C library functions or system calls to read device files. Please note that, to run programs on specific embedded systems, it's often necessary to use cross-compilation tools to compile the code, generating executable files that can be executed on the target development board. Now, let's explore the specific implementation steps.

4.1 Complete Code

With the following program, you can retrieve the voltage values of ADC channels 0 and 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 Opening Files

This code defines variables related to file paths. Using sprintf, it dynamically constructs three file paths based on adc_dir and opens these three files in read-only mode. These files will be used to read the raw voltage values and scaling factor of the ADC device's dual channels.

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 Calculating Voltage Values

This code implements a loop that reads the raw voltage values and scaling factor of the ADC device's dual channels every second. It then calculates and displays the actual voltage values with six decimal places. File operations ensure that data is read from the beginning of the file in each iteration using 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 Cross-Compilation

  1. Specify the Cross-Compilation Tool

    First, you need to add the path to the cross-compilation tool to the system's PATH environment variable so that you can use the cross-compilation tool from anywhere. You can add the following line to your shell configuration file (usually ~/.bashrc or ~/.bash_profile or ~/.zshrc, depending on your shell). Note that the path after PATH= should point to the directory where the cross-compilation tool is located.

    • gcc path

      <SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
    • Open the shell configuration file.

      vi ~/.bashrc 
    • Add the path of the cross-compilation tool to the system's PATH environment variable. Replace <SDK Directory> with your own SDK path, such as /home/luckfox/luckfox-pico/.

      export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
    • Reload the shell configuration file to apply the changes:

      source ~/.bashrc  
  2. Compile the Program Using the Cross-Compilation Tool

    arm-rockchip830-linux-uclibcgnueabihf-gcc adc.c -o adc
  3. After successful cross-compilation, an executable file that can run on the development board will be generated in the current directory.

    # ls
    adc adc.c

4.5 Running the Program

  1. File Transfer

    First, transfer the adc program from the virtual machine to Windows, and then transfer it to the development board via TFTP or ADB. Here are the steps to transfer the file from Windows to the development board using ADB:

    adb push path_to_file destination_on_development_board

    eg: (Transferring the `adc` file from the current directory to the root directory of the development board)
    adb push adc /
  2. Running the Program

    Modify the permissions of the adc file and then run the program:

    # chmod 777 adc
    # ./adc
  3. Experimental Observations

    Obtain the voltage values of the ADC dual channels: