40-Pin Extension
The Quectel Pi H1 single-board computer features a standard 40-pin GPIO expansion interface, supporting various peripheral interfaces such as GPIO, I2C, SPI, UART, PWM, etc. The following sections will introduce how to test the functions of these interfaces.
Pin Definition


Interface Configuration
The configuration file for some low-speed interfaces (i2c9/spi10/uart12_2w/i2c13/spi14) on the 40-pin interface is located at /etc/qpi-config/qpi-config.ini.
Configuration Steps:
- Modify the
qpi-config.iniconfiguration file - Apply the configuration
qpi-config 40pin set
- Restart the system for the configuration to take effect
GPIO Testing
This section uses pin3 of the 40-pin interface as an example to demonstrate how to use the GPIO function. The gpio_num corresponding to pin3 is 36.
Hardware Connection
Method 1: Measure voltage with a multimeter
Connect pin3 (GPIO_36) to the positive terminal of the multimeter, and pin6 (GND) to the negative terminal of the multimeter. You can verify if the GPIO function is working correctly by measuring the voltage output of the pin with the multimeter.
Method 2: Use the GPIO expansion board indicator lights
You can also connect a Raspberry Pi 4B/3B GPIO terminal expansion board to the 40‑pin expansion interface to test GPIO high/low levels. This expansion board brings out all GPIO pins and is equipped with a corresponding indicator light for each pin. You can configure the target GPIO pin to output mode and connect it to the expansion board. The indicator lights up when the GPIO outputs a high level and turns off when it outputs a low level, allowing you to visually verify if the GPIO level changes are as expected.
For more information about this expansion board, refer to the GPIO Terminal Expansion Board entry in the supported accessories chapter (see: Supported Accessories).
Method 1: Control via SHELL Commands
The lgpiod service is enabled by default in the system. Execute the following commands in sequence:
Step 1: Open GPIO device
rgs c 999 go 4
Use the go command to open the file /dev/gpiochip4.
Step 2: Set GPIO mode
rgs c 999 gso 0 36
Use the gso command to set gpio 36 to output mode. The 0 in this command is the return value from the previous command, modify it according to the actual situation.
Step 3: Set low level
rgs c 999 gw 0 36 0
Set gpio 36 to low level. At this time, the test pin voltage value is 0V.
Step 4: Set high level
rgs c 999 gw 0 36 1
Set gpio 36 to high level. At this time, the test pin voltage value is 3.3V.
Method 2: Control via Python/C Code
If you need to use programming languages to control GPIO, please refer to the following documents:
- Python GPIO Development - Use the python-periphery library for GPIO development
- C/C++ GPIO Development - Use the lgpio library for GPIO development
These documents provide complete code examples, compilation methods, and testing steps.
I2C Testing
Pin3 and pin5 of the 40-pin interface are the data and clock pins of I2C by default, corresponding to the device node /dev/i2c9.
If the device node does not appear, you can first use qpi-config mentioned at the beginning of this chapter to configure and enable it.
Test Preparation
This test uses the Waveshare Environmental Sensor Expansion Board (BME280), connected through the 40-pin interface.
Hardware Connection Diagram:
Waveshare Environmental Sensor Expansion Board
Quectel Pi H1 40-Pin Interface
Quectel Pi H1 with Environmental Sensor Expansion Board
Control via C Code
If the lgpio library is not available in the system, please refer to the C/C++ GPIO Development document to install the lgpio library.
Step 1: Create source file
Create a new file named envtest.c with the following content:
Click to expand/collapse the full code
#include <stdio.h>
#include <lgpio.h>
#define I2C_DEV_NUM 9
#define BME280_ADDR 0x76
int32_t digT[3],digP[9],digH[6];
int32_t t_fine = 0.0;
double compensate_P(int32_t adc_P)
{
double pressure = 0.0;
double v1,v2;
v1 = (t_fine / 2.0) - 64000.0;
v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5];
v2 = v2 + ((v1 * digP[4]) * 2.0);
v2 = (v2 / 4.0) + (digP[3] * 65536.0);
v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8) + ((digP[1] * v1) / 2.0)) / 262144;
v1 = ((32768 + v1) * digP[0]) / 32768;
if(v1 == 0)
return 0;
pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125;
if (pressure < 0x80000000)
pressure = (pressure * 2.0) / v1;
else
pressure = (pressure / v1) * 2;
v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096;
v2 = ((pressure / 4.0) * digP[7]) / 8192.0;
pressure = pressure + ((v1 + v2 + digP[6]) / 16.0) ;
return (pressure/100);
}
double compensate_T(int32_t adc_T)
{
double temperature = 0.0;
double v1,v2;
v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1];
v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2];
t_fine = v1 + v2;
temperature = t_fine / 5120.0;
return temperature;
}
double compensate_H(int32_t adc_H)
{
double var_h = t_fine - 76800.0;
if (var_h == 0)
return 0;
var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) *
(digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h)));
var_h = var_h * (1.0 - digH[0] * var_h / 524288.0);
if (var_h > 100.0)
var_h = 100.0;
else if (var_h < 0.0)
var_h = 0.0;
return var_h;
}
void get_calib_param(int handle)
{
uint8_t calib[32];
for(int i=0;i<24;i++) {
calib[i] = lgI2cReadByteData(handle, 0x88 + i);
}
calib[24] = lgI2cReadByteData(handle, 0xA1);
for(int i=25,o=0;i<32;i++,o++) {
calib[i] = lgI2cReadByteData(handle, 0xE1 + o);
}
digT[0] = (calib[1] << 8) | calib[0];
digT[1] = (calib[3] << 8) | calib[2];
digT[2] = (calib[5] << 8) | calib[4];
digP[0] = (calib[7] << 8) | calib[6];
digP[1] = (calib[9] << 8) | calib[8];
digP[2] = (calib[11] << 8) | calib[10];
digP[3] = (calib[13] << 8) | calib[12];
digP[4] = (calib[15] << 8) | calib[14];
digP[5] = (calib[17] << 8) | calib[16];
digP[6] = (calib[19] << 8) | calib[18];
digP[7] = (calib[21] << 8) | calib[20];
digP[8] = (calib[23] << 8) | calib[22];
digH[0] = calib[24];
digH[1] = (calib[26] << 8) | calib[25];
digH[2] = calib[27];
digH[3] = (calib[28] << 4) | (0x0f & calib[29]);
digH[4] = (calib[30] << 4) | ((calib[29] >> 4) & 0x0f);
digH[5] = calib[31];
for(int i=1;i<2;i++)
if((digT[i] & 0x8000) != 0) digT[i] = (-digT[i] ^ 0xFFFF) + 1;
for(int i=1;i<8;i++)
if ((digP[i] & 0x8000) != 0) digP[i]=(-digP[i] ^ 0xFFFF) + 1 ;
for(int i=0;i<6;i++)
if ((digH[i] & 0x8000) != 0) digH[i] = (-digH[i] ^ 0xFFFF) + 1;
}
int main(int argc, char **argv)
{
uint8_t data[8];
double value[3];
int handle = lgI2cOpen(I2C_DEV_NUM, BME280_ADDR, 0);
lgI2cWriteByteData(handle, 0xF2, 0x01);
lgI2cWriteByteData(handle, 0xF4, 0x27);
lgI2cWriteByteData(handle, 0xF5, 0xA0);
get_calib_param(handle);
for(int i=0;i<8;i++) {
data[i] = lgI2cReadByteData(handle, 0xF7 + i);
}
value[0] = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
value[1] = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
value[2] = (data[6] << 8) | data[7];
value[0] = compensate_P(value[0]);
value[1] = compensate_T(value[1]);
value[2] = compensate_H(value[2]);
printf("pressure: %7.2f hPa\n", value[0]);
printf("temperature: %7.2f C\n" , value[1]);
printf("humidity: %7.2f %\n" , value[2]);
lgI2cClose(handle);
}
Step 2: Compile the program
gcc -o envtest envtest.c -llgpio
Step 3: Run the test
sudo ./envtest
If a "Permission denied" error occurs, you need to run the program with
sudo.
After successful execution, the output contains the collected air pressure, temperature, and humidity information:
pressure: 1013.25 hPa
temperature: 25.36 C
humidity: 45.67 %
SPI Testing
The SPI function on the 40-pin interface corresponds to the device node /dev/spi10.
If the device node does not appear, you can first use qpi-config mentioned at the beginning of this chapter to configure and enable it.
SPI Loopback Testing
This test controls the SPI interface through SHELL commands to verify data transmission and reception functions.
Step 1: Open SPI device
rgs c 999 SPIO 10 0 10000000 0
Use the SPIO command to open /dev/spidev10.0 with a baud rate of 10000000. The command returns a file handle (usually 0).
Step 2: Perform SPI data transmission test
Short data test:
rgs c 999 SPIX 0 1 2 3 4
Long data test:
rgs c 999 SPIX 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
Use the SPIX command for SPI data transmission, where 0 is the file handle returned by the previous command, and the following numbers are the data to be sent.
Test Results:
- When MISO and MOSI are shorted (loopback mode), returns the sent data:
4 1 2 3 4
The first number 4 indicates the number of data bytes received.
- When MISO and MOSI are not shorted, returns all 255 (indicating no valid data received):
4 255 255 255 255
UART Testing
Pin8 and pin10 of the 40-pin interface are configured as UART function by default, corresponding to the device node /dev/ttyHS2. If the device node does not appear, you can first use qpi-config mentioned at the beginning of this chapter to configure and enable it.
You can use the following command to view all serial port devices in the system:
ls /dev/tty*
UART Loopback Testing
This test verifies whether the serial port transmission and reception functions are working properly by shorting pin8 and pin10.
Hardware Connection: Short pin8 (TX) and pin10 (RX) of the 40-pin interface.
The python3-serial library needs to be installed:
apt-get update
apt-get install python3-serial
Step 1: Create test script
Create a new file named uart_loop.py with the following content:
Click to expand/collapse the full code
import serial
import time
def serial_loopback_test(port='/dev/ttyHS2', baudrate=115200, timeout=1):
try:
# Open serial port
ser = serial.Serial(
port=port,
baudrate=baudrate,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=timeout
)
if not ser.is_open:
print(f"Unable to open serial port {port}")
return
print(f"Serial port {port} opened, starting loopback test (Press Ctrl+C to exit)...")
test_data = b"Hello, Serial Loopback!" # Test data
while True:
# Send data
ser.write(test_data)
print(f"Sent: {test_data.decode('utf-8')}")
# Read loopback data
time.sleep(0.1) # Wait for data reception
received = ser.read(len(test_data))
# Verify results
if received == test_data:
print(f"Received: {received.decode('utf-8')} → Test passed\n")
else:
print(f"Reception exception: Sent[{len(test_data)}] vs Received[{len(received)}] → Test failed\n")
time.sleep(1) # Repeat test every 1 second
except serial.SerialException as e:
print(f"Serial port error: {e}")
except KeyboardInterrupt:
print("\nUser interrupted the test")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print(f"Serial port {port} closed")
if __name__ == "__main__":
# Modify serial port and baud rate according to actual situation
serial_loopback_test(port='/dev/ttyHS2', baudrate=115200)
Step 2: Run the test
python3 uart_loop.py
Test Results:
When pin8 and pin10 are correctly shorted, the program will continuously send data and verify whether the received data is consistent:
Serial port /dev/ttyHS2 opened, starting loopback test (Press Ctrl+C to exit)...
Sent: Hello, Serial Loopback!
Received: Hello, Serial Loopback! → Test passed
Sent: Hello, Serial Loopback!
Received: Hello, Serial Loopback! → Test passed
Sent: Hello, Serial Loopback!
Received: Hello, Serial Loopback! → Test passed
Press Ctrl+C to exit the test program.
PWM Testing
Pin33 of the 40-pin interface is configured as PWM function by default. Here, we choose Waveshare's 4pin PWM protocol adjustable speed fan as the test device.
Hardware Connection
PWM Fan Wiring Definition:
| Pin | Function | Wire colour | 40-Pin Connection |
|---|---|---|---|
| 1 | +5V | Red | pin2 or pin4 (5V) |
| 2 | PWM | Blue | pin33 (GPIO_78) |
| 3 | GND | Black | pin6/9/14/20/25/30/34/39 (any GND) |
| 4 | Tach | Yellow | pin32 (GPIO_76) |
Connection Diagram:
Fan Connected to 40-Pin Interface
PWM Fan Interface Definition
- The Tach pin is used to read fan speed and is an optional connection
- If you don't need to read speed information, the Tach pin can be left unconnected
Control via C Code
Create a pwm.c file with the following content:
#include <stdio.h> #include <lgpio.h> int main(int argc, char **argv) { int h; int gpio = 78; float pwmFrequency = 1000; float pwmDutyCycle = 50; h = lgGpiochipOpen(4); // Open /dev/gpiochip4 device if (h < 0) { printf("ERROR: %s (%d)\n", lguErrorText(h), h); return 1; } int e = lgGpioClaimOutput(h, 0, gpio, 0); if (e < 0) { printf("ERROR: %s (%d)\n", lguErrorText(e), e); return 1; } e = lgTxPwm(h, gpio, pwmFrequency, pwmDutyCycle, 0, 0); if (e < 0) { printf("ERROR: %s (%d)\n", lguErrorText(e), e); return 1; } lguSleep(5); lgGpioFree(h, gpio); lgGpiochipClose(h); return 0; }Compile:
gcc pwm.c -o pwm -llgpioExecute:
sudo ./pwmThe fan will run at medium speed. You can modify the
pwmDutyCyclevalue in the code (range 0-100) to adjust the fan speed.