GPIO
In this chapter, we will learn how to use the sysfs file system to access and control GPIO (General-Purpose Input/Output) pins.
Sample program : Code.zip
1. GPIO Subsystem
GPIO (General-Purpose Input/Output) is a type of universal pin that can be controlled by microcontrollers (MCUs) or CPUs. It has various functions, including detecting high or low-level inputs and controlling outputs. In Linux, GPIO pins can be exported to user space and controlled through the sysfs file system, allowing GPIO pins to be used for various purposes such as serial communication, I2C, network communication, voltage detection, and more.
Linux has a dedicated GPIO subsystem driver framework for handling GPIO devices. Through this framework, users can easily interact with the GPIO pins of the CPU. This driver framework supports using pins for basic input and output functions, and input functions also support interrupt detection. This allows developers to easily use GPIO pins for various purposes, providing flexibility and programmability. More detailed information about the GPIO subsystem can be found in the <Linux Kernel Source>/Documentation/gpio
directory.
2. GPIO Control (Shell)
2.1 Pin Distribution
When exporting GPIO pins to user space, pin numbers are typically needed. These pin numbers can be determined using interface diagrams. For example, the pin name for pin 4 is GPIO1_C7_d, and the pin number is 55.
LuckFox Pico Diagram:
LuckFox Pico Mini A/B Diagram:
LuckFox Pico Plus Diagram:
LuckFox Pico Pro/Max Diagram:
LuckFox Pico Ultra/Ultra W Diagram:
2.2 Calculating Pin Numbers
The pin numbers corresponding to GPIOs are marked in the pinout diagram, and you can use them directly or calculate them as follows:
GPIOs are divided into 5 banks: GPIO0 to GPIO4, with each bank having 4 groups, resulting in a total of 32 pins: A0 to A7, B0 to B7, C0 to C7, D0 to D7.
GPIOs are named in the format GPIO{bank}_{group}{X}, as shown below:
GPIO0_A0 ~ A7
GPIO0_B0 ~ B7
GPIO0_C0 ~ C7
GPIO0_D0 ~ D7
GPIO1_A0 ~ A7
....
GPIO1_D0 ~ D7
....
GPIO4_D0 ~ D7The GPIO pin number can be calculated using the following formula:
GPIO pin number calculation formula: pin = bank * 32 + number
GPIO group number calculation formula: number = group * 8 + X
Therefore: pin = bank * 32 + (group * 8 + X)As an example, let's consider GPIO1_C7_d, where:
bank
: 1group
: 2 (A=0, B=1, C=2, D=3)X
: 7
So, the pin number of GPIO1_C7_d is: 1 x 32 + (2 x 8 + 7) = 55
2.3 Device Directory
In the /sys/class/gpio
directory, each GPIO device has its own folder. The folder names are gpio followed by the pin number, for example, /sys/class/gpio/gpio55
represents pin number 55, which is GPIO1_C7_d. You can use the following command to check:
# ls /sys/class/gpio/
gpio55 gpiochip96 gpiochip0 gpiochip128 unexport export gpiochip32
2.4 Device Attributes
In these device directories, you can also find control files related to GPIO pins, including direction, value, and interrupts, among others. Each GPIO device directory contains a set of attribute files used to configure and manage GPIO pins. You can use the following command in the GPIO device directory:
View the attribute files of GPIO 55 device:
# echo 55 > /sys/class/gpio/export
# cd gpio55
# ls
value power subsystem active_low
uevent edge device directionAttribute Files
Using these attribute files, you can easily configure and control GPIO pins to adapt to different application scenarios and requirements. Some of the key attributes include:
- Direction (direction):
- Configured as input: in
- Configured as output: out
- Value (value):
- Output low level: 0
- Output high level: 1
- Interrupt edge (edge):
- Rising edge-triggered: rising
- Falling edge-triggered: falling
- Both edge-triggered: both
- Disable interrupts: none
- Direction (direction):
2.5 Controlling GPIO Pin Levels
The device's attribute files serve as an interface for function parameters. For /sys/class/gpio/gpio55/value
, each time a write operation is performed on the file, it triggers the driver code to modify the level of GPIO pin 55 using the content written as a parameter. Each time a read operation is performed, it triggers the driver code to update the current level of GPIO pin 55 to the /sys/class/gpio/gpio55/value
file.
Export GPIO 55 to user space:
echo 55 > /sys/class/gpio/export
Read the level of GPIO1_C7_d:
echo 55 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio55/direction
cat /sys/class/gpio/gpio55/valueControl the level of GPIO1_C7_d:
echo out > /sys/class/gpio/gpio55/direction
echo 1 > /sys/class/gpio/gpio55/value
echo 0 > /sys/class/gpio/gpio55/valueUnexport GPIO 55 from user space:
echo 55 > /sys/class/gpio/unexport
3. GPIO Control (Python Program)
In the previous sections, we demonstrated how to use the echo
command to modify device files to achieve device control. Next, we will implement GPIO control using a Python program.
3.1 Complete Code
The following program enables the control and reading of GPIO pin levels.
from periphery import GPIO
import time
Write_Pin = 55
Read_Pin = 54
Write_GPIO = GPIO(Write_Pin, "out")
Read_GPIO = GPIO(Read_Pin, "in")
try:
while True:
try:
Write_GPIO.write(True)
pin_state = Read_GPIO.read()
print(f"Pin state: {pin_state}")
Write_GPIO.write(False)
pin_state = Read_GPIO.read()
print(f"Pin state: {pin_state}")
time.sleep(1)
except KeyboardInterrupt:
Write_GPIO.write(False)
break
except IOError:
print("Error")
finally:
Write_GPIO.close()
Read_GPIO.close()
3.2 Configure GPIO Direction
This code segment uses the periphery
library to open two GPIO pins in Python. Write_GPIO
is used for output, and Read_GPIO
is used for input.
Write_GPIO = GPIO(Write_Pin, "out")
Read_GPIO = GPIO(Read_Pin, "in")
3.3 Control Pin Output Level
This code segment uses the Write_GPIO
object to write a logic value of True to the GPIO pin to set the pin output to a high level. Writing a logic value of False to the GPIO pin sets the pin output to a low level.
Write_GPIO.write(True)
....
Write_GPIO.write(False)
3.4 Control Pin Read Level
This code reads the state of the Read_GPIO
pin and prints it. Read_GPIO.read()
returns the level state of the pin (True for high level, False for low level).
pin_state = Read_GPIO.read()
print(f"Pin state: {pin_state}")
3.5 Running the Program
Use the vi tool in the terminal to create a Python file, paste the Python program, and save it.
# nano gpio.py
Run the program.
# python3 gpio.py
Experimental Observations
Pins 55 and 54 not connected:
Pins 55 and 54 connected:
4. GPIO Control (C Program)
In the previous sections, we demonstrated how to control GPIO using the echo
command and Python programs. Additionally, you can also use the vi
editor to modify files, but be cautious about user permissions when making changes. Moreover, we can use C library functions or system calls to read and write device files for device control. Please note that to run programs on specific embedded systems, it's often necessary to use cross-compilation tools to compile the code and generate executable files that can run on the target development board. Let's explore the specific implementation steps together.
4.1 Complete Code
With the following program, you can control the GPIO pin levels.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
int gpio_pin;
printf("Please enter the GPIO pin number: ");
scanf("%d", &gpio_pin);
FILE *export_file = fopen("/sys/class/gpio/export", "w");
if (export_file == NULL) {
perror("Failed to open GPIO export file");
return -1;
}
fprintf(export_file, "%d", gpio_pin);
fclose(export_file);
char direction_path[50];
snprintf(direction_path, sizeof(direction_path), "/sys/class/gpio/gpio%d/direction", gpio_pin);
FILE *direction_file = fopen(direction_path, "w");
if (direction_file == NULL) {
perror("Failed to open GPIO direction file");
return -1;
}
fprintf(direction_file, "out");
fclose(direction_file);
char value_path[50];
char cat_command[100];
snprintf(value_path, sizeof(value_path), "/sys/class/gpio/gpio%d/value", gpio_pin);
snprintf(cat_command, sizeof(cat_command), "cat %s", value_path);
FILE *value_file = fopen(value_path, "w");
if (value_file == NULL) {
perror("Failed to open GPIO value file");
return -1;
}
for (int i = 0; i < 3; i++) {
fprintf(value_file, "1");
fflush(value_file);
system(cat_command);
sleep(1);
fprintf(value_file, "0");
fflush(value_file);
system(cat_command);
sleep(1);
}
fclose(value_file);
FILE *unexport_file = fopen("/sys/class/gpio/unexport", "w");
if (unexport_file == NULL) {
perror("Failed to open GPIO unexport file");
return -1;
}
fprintf(unexport_file, "%d", gpio_pin);
fclose(unexport_file);
return 0;
}
4.2 Exporting a Pin to User Space
This code reads the user-input pin number, opens the /sys/class/gpio/export
file, and writes that number to it, achieving the export operation for a GPIO pin.
printf("Please enter the GPIO pin number: ");
scanf("%d", &gpio_pin);
FILE *export_file = fopen("/sys/class/gpio/export", "w");
if (export_file == NULL) {
perror("Failed to open GPIO export file");
return -1;
}
fprintf(export_file, "%d", gpio_pin);
fclose(export_file);
4.3 Configuring GPIO Direction
This code opens the /sys/class/gpio/gpiox/direction
file and writes "out" to ensure that the GPIO pin is set to output mode. If you want to configure it as an input, you would write "in" instead.
char direction_path[50];
snprintf(direction_path, sizeof(direction_path), "/sys/class/gpio/gpio%d/direction", gpio_pin);
FILE *direction_file = fopen(direction_path, "w");
if (direction_file == NULL) {
perror("Failed to open GPIO direction file");
return -1;
}
fprintf(direction_file, "out");
fclose(direction_file);
4.4 Controlling Pin Output Level
This code is used to control the GPIO pin's logic level. First, you need to open the /sys/class/gpio/gpiox/value
file, which is used to set the GPIO pin's logic level. In a loop, we control the pin by sequentially writing "1" and "0" to the file. Then, we use the cat
command to check the value stored in the value
file to verify if the write was successful.
Note that the standard C library typically uses buffers to improve the efficiency of file operations rather than writing data to disk files immediately after each write. This means that even though you use fprintf
to write data, the data may be temporarily held in the buffer and not immediately written to the file. If you want to ensure that data is written to the file immediately, you can use the fflush
function to flush the buffer, which forces the data in the buffer to be written to the file, or use the fopen->fprintf->fclose
approach for each write.
char value_path[50];
char cat_command[100];
snprintf(value_path, sizeof(value_path), "/sys/class/gpio/gpio%d/value", gpio_pin);
snprintf(cat_command, sizeof(cat_command), "cat %s", value_path);
FILE *value_file = fopen(value_path, "w");
if (value_file == NULL) {
perror("Failed to open GPIO value file");
return -1;
}
for (int i = 0; i < 3; i++) {
fprintf(value_file, "1");
fflush(value_file);
system(cat_command);
sleep(1);
fprintf(value_file, "0");
fflush(value_file);
system(cat_command);
sleep(1);
}
fclose(value_file);
4.5 Unexporting a Pin
This code opens the /sys/class/gpio/unexport
file and writes the GPIO pin's number to it, achieving the unexport operation for a GPIO pin.
FILE *unexport_file = fopen("/sys/class/gpio/unexport", "w");
if (unexport_file == NULL) {
perror("Failed to open GPIO unexport file");
return -1;
}
fprintf(unexport_file, "%d", gpio_pin);
fclose(unexport_file);
4.6 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 gpio.c -o gpio
After successful cross-compilation, an executable file that can run on the development board will be generated in the current directory.
# ls
gpio gpio.c
4.7 Running the Program
File Transfer
First, transfer the
gpio
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 `gpio` file from the current directory to the root directory of the development board)
adb push gpio /Running the Program
Modify the permissions of the
gpio
file and then run the program:# chmod 777 gpio
# ./gpioExperimental Observations
Successful level flipping:
5. Modifying Device Tree
The pins 55 and 54 in the program can be used directly without modifying the device tree. If we need to configure other pins as general-purpose IO, we need to modify the device tree.
5.1 Modify Device Tree File
Device Tree File Paths
Development Board Configuration File
In the
<SDK directory>/project/cfg/BoardConfig_IPC/
directory, there are configuration files for different models of development boards. These files primarily include configuration parameters for various Luckfox Pico board models, covering aspects such as target architecture, boot medium, Uboot and kernel configurations, and partition settings. The SDK directory structure is as follows:├── build.sh -> project/build.sh ---- SDK compilation script
├── media --------------------------- Multimedia encoding, ISP, and related algorithms (can be compiled independently in the SDK)
├── sysdrv -------------------------- U-Boot, kernel, rootfs directory (can be compiled independently in the SDK)
├── project ------------------------- Reference applications, compilation configurations, and script directories
│ ├── cfg
│ ├── BoardConfig_IPC
│ ├── BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
│ ├── BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico_Mini_A-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Mini_B-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Plus-IPC.mk
│ ├── BoardConfig-SPI_NAND-NONE-RV1106_Luckfox_Pico_Pro_Max-IPC.mk
│ └── ...
├── output -------------------------- Directory for storing SDK compiled image files
├── docs ---------------------------- SDK documentation directory
└── tools --------------------------- Image burning packaging tools and burning toolsAmong them,
RK_KERNEL_DTS
specifies the Device Tree Source (DTS) file for the kernel. Taking Luckfox Pico as an example, by opening theBoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
file, we can see that the file pointed to byRK_KERNEL_DTS
isrv1103g-luckfox-pico.dts
Device Tree File Path
Based on
RK_KERNEL_DTS
, the device tree file path for Luckfox Pico can be determined as follows:<SDK directory>/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico.dts
Define GPIO
Defining a GPIO typically requires adding two sections of code. Note that the button is active-low and needs to be pulled up. Here's an example of how to add the definition of GPIO1_C7_d pin in the device tree.
The code snippets to add are as follows:
/{
gpio1pc7:gpio1pc7 {
compatible = "regulator-fixed";
pinctrl-names = "default";
pinctrl-0 = <&gpio1_pc7>;
regulator-name = "gpio1_pc7";
regulator-always-on;
};
};
&pinctrl {
gpio1-pc7 {
gpio1_pc7:gpio1-pc7 {
rockchip,pins = <1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};Comment Out the Pin's Peripheral Function
Disabling the peripheral function of a pin can be done by commenting out the relevant peripheral node in the device tree. Here's an example of how to disable the PWM function of GPIO1_C7_d in the device tree.
5.2 Compile the Kernel
Compile by selecting branches for LuckFox Pico, LuckFox Pico Mini A, LuckFox Pico Mini B, LuckFox Pico Plus, and LuckFox Pico Pro/Max.
luckfox@luckfox:~/luckfox-pico$ ./build.sh lunch
ls: cannot access 'BoardConfig*.mk': No such file or directory
You're building on Linux
Lunch menu...pick a combo:
BoardConfig-*.mk naming rules:
BoardConfig-"启动介质"-"电源方案"-"硬件版本"-"应用场景".mk
BoardConfig-"boot medium"-"power solution"-"hardware version"-"applicaton".mk
----------------------------------------------------------------
0. BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
boot medium(启动介质): EMMC
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
1. BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico_Mini_A-IPC.mk
boot medium(启动介质): EMMC
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico_Mini_A
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
2. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Mini_B-IPC.mk
boot medium(启动介质): SPI_NAND
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico_Mini_B
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
3. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1103_Luckfox_Pico_Plus-IPC.mk
boot medium(启动介质): SPI_NAND
power solution(电源方案): NONE
hardware version(硬件版本): RV1103_Luckfox_Pico_Plus
applicaton(应用场景): IPC
----------------------------------------------------------------
----------------------------------------------------------------
4. BoardConfig_IPC/BoardConfig-SPI_NAND-NONE-RV1106_Luckfox_Pico_Pro_Max-IPC.mk
boot medium(启动介质): SPI_NAND
power solution(电源方案): NONE
hardware version(硬件版本): RV1106_Luckfox_Pico_Pro_Max
applicaton(应用场景): IPC
----------------------------------------------------------------
Which would you like? [0]: 0
[build.sh:info] switching to board: /home/luckfox/luckfox-pico/project/cfg/BoardConfig_IPC/BoardConfig-EMMC-NONE-RV1103_Luckfox_Pico-IPC.mk
[build.sh:info] Running build_select_board succeeded.Recompile the kernel
luckfox@luckfox:~/Luckfox-Pico/luckfox-pico$ ./build.sh kernel
5.3 Flash the Firmware Again
- After successfully compiling the kernel, the generated files can be found in the
<SDK directory>/output/image
directory. - Replace the
boot.image
andenv.txt
files in the original firmware. - Recreate the SD card. For Luckfox Pico Plus, you may only need to modify the relevant partition.