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.
- LuckFox Pico Diagram:
- LuckFox Pico Mini A/B Diagram:
- 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.
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_elementsProperty Files
In this directory, the main files used are
in_voltage0_raw
,in_voltage1_raw
, andin_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 thecat
command, for example:# cat /sys/bus/iio/devices/iio\:device0/in_voltage0_raw
1023The
in_voltage1_raw
file contains the raw voltage value of ADC input channel 1, usually represented as an integer. You can read it using thecat
command, for example:# cat /sys/bus/iio/devices/iio\:device0/in_voltage1_raw
1022The
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 thecat
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:
Use a text editor like vi to create a shell script:
# vi adc.sh
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
doneRun 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
Use the vi tool in the terminal to create a Python file, paste the Python program, and save it.
# nano adc.py
Run the program.
# python3 adc.py
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
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 afterPATH=
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
Compile the Program Using the Cross-Compilation Tool
arm-rockchip830-linux-uclibcgnueabihf-gcc adc.c -o adc
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
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 /Running the Program
Modify the permissions of the
adc
file and then run the program:# chmod 777 adc
# ./adcExperimental Observations
Obtain the voltage values of the ADC dual channels: