40‑Pin扩展¶
Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.
Quectel Pi 智能主控板提供了标准的40‑pin GPIO扩展接口,支持GPIO、I2C、SPI、UART、PWM等多种外设接口,下面将介绍如何测试这些接口的功能。
引脚定义¶
Function10 |
Function9 |
Function8 |
Function7 |
Function6 |
Function5 |
Function4 |
Function3 |
M2 GPIO# |
Pin# |
Pin# |
M2 GPIO# |
Function3 |
Function4 |
Function5 |
Function6 |
Function7 |
Function8 |
Function9 |
Function10 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
VCC 3V3 |
1 |
2 |
VCC 5V |
||||||||||||||||
CAN1_RX_M2 |
I2C3_SDA_M0 |
UART2_RX_M1 |
- |
GPIO4_B4_d(I2C3_SDA) |
3 |
4 |
VCC 5V |
||||||||||||
CAN1_TX_M2 |
PCIE0_CLKREQN_M2 |
I2C3_SCL_M0 |
UART2_TX_M1 |
- |
GPIO4_B5_d(I2C3_SCL) |
5 |
6 |
GND |
|||||||||||
I2C8_SDA_M2 |
UART8_CTSN_M1 |
UART7_RX_M0 |
SAI0_LRCK_M0 |
ETH0_RXD2_M1 |
GPIO2_B7_d |
7 |
8 |
GPIO2_B0_d(UART1_TXD) |
SDMMC1_D2_M1 |
ETH0_TXD1_M1 |
SAI0_SDI0_M0 |
PDM0_SDI3_M3 |
UART1_TX_M1 |
||||||
GND |
9 |
10 |
GPIO2_B1_d(UART1_RXD) |
SDMMC1_D3_M1 |
ETH0_TXD0_M1 |
SAI0_SDI1_M0 |
PDM0_SDI2_M3 |
UART1_RX_M1 |
|||||||||||
I2C8_SCL_M2 |
UART8_RTSN_M1 |
UART7_TX_M0 |
SAI0_SCLK_M0 |
- |
GPIO2_B6_d |
11 |
12 |
GPIO2_C7_d |
ETH1_TXD1_M0 |
SAI4_LRCK_M3 |
UART4_RTSN_M0 |
I2C5_SDA_M2 |
PWM0_CH1_M2 |
||||||
SPI4_CSN1_M3 |
I2C4_SCL_M2 |
UART8_TX_M1 |
SAI0_SDO0_M0 |
SDMMC1_D0_M1 |
GPIO2_A6_d |
13 |
14 |
GND |
|||||||||||
- |
I2C4_SDA_M2 |
UART8_RX_M1 |
SAI0_SDO1_M0 |
SDMMC1_D1_M1 |
GPIO2_A7_d |
15 |
16 |
GPIO4_A4_d |
SAI4_SCLK_M0 |
PDM1_SDI3_M1 |
- |
SPI3_MOSI_M2 |
UART6_TX_M0 |
I2C4_SCL_M1 |
CAN0_TX_M2 |
||||
VCC 3V3 |
17 |
18 |
GPIO4_A6_d |
SAI4_LRCK_M0 |
PDM1_CLK0_M1 |
- |
SPI3_MISO_M2 |
UART6_RX_M0 |
I2C4_SDA_M1 |
CAN0_RX_M2 |
|||||||||
PWM1_CH2_M2 |
SPI1_MOSI_M1 |
UART11_CTSN_M1 |
PDM1_SDI2_M0 |
SAI2_SCLK_M1 |
- |
GPIO2_C2_d(SPI_MOSI) |
19 |
20 |
GND |
||||||||||
PWM0_CH0_M2 |
SPI1_MISO_M1 |
UART11_RTSN_M1 |
PDM1_SDI3_M0 |
SAI2_LRCK_M1 |
GPIO2_C3_d(SPI_MISO) |
21 |
22 |
GPIO2_B4_d |
SDMMC1_PWREN_M1 |
ETH0_TXD2_M1 |
SAI0_SDI3_M0 |
UART7_CTSN_M0 |
SPI4_MOSI_M3 |
SATA0_ACTLED_M0 |
|||||
PWM1_CH4_M2 |
SPI1_CLK_M1 |
UART11_RX_M1 |
PDM1_CLK0_M0 |
SAI2_SDI_M1 |
GPIO2_C5_d(SPI_CLK) |
23 |
24 |
GPIO2_C4_d(SPI_CE0) |
SAI2_SDO_M1 |
PDM1_SDI0_M0 |
UART11_TX_M1 |
SPI1_CSN0_M1 |
PWM1_CH3_M2 |
- |
|||||
GND |
25 |
26 |
GPIO2_C1_d(SPI_CE1) |
- |
SAI2_MCLK_M1 |
UART9_TX_M0 |
SPI1_CSN1_M1 |
PWM1_CH1_M2 |
|||||||||||
I2C7_SDA_M1 |
SPI3_MOSI_M0 |
UART3_RX_M0 |
SAI3_LRCK_M2 |
ETH0_MDC_M1 |
GPIO3_A1_d(I2C7_SDA) |
27 |
28 |
GPIO3_A0_d(I2C7_SCL) |
- |
SAI3_SCLK_M2 |
UART3_TX_M0 |
SPI3_CLK_M0 |
I2C7_SCL_M1 |
- |
|||||
PWM2_CH5_M2 |
I2C9_SCL_M2 |
UART6_CTSN_M1 |
GPIO2_D5_d |
29 |
30 |
GND |
|||||||||||||
PWM2_CH4_M2 |
I2C9_SDA_M2 |
UART6_RTSN_M1 |
GPIO2_D4_d |
31 |
32 |
GPIO2_B5_d |
SDMMC1_DETN_M1 |
ETH0_RXCLK_M1 |
SAI0_MCLK_M0 |
PDM0_CLK0_M3 |
UART7_RTSN_M0 |
SPI4_MISO_M3 |
SATA1_ACTLED_M0 |
||||||
PWM2_CH3_M2 |
I3C1_SDA_M0 |
UART6_RX_M1 |
GPIO2_D3_d(PWM) |
33 |
34 |
GND |
|||||||||||||
PWM1_CH5_M2 |
I2C5_SCL_M2 |
UART4_CTSN_M0 |
SAI4_SCLK_M3 |
GPIO2_C6_d |
35 |
36 |
GPIO2_D2_d |
CAM_CLK0_OUT_M1 |
SAI4_MCLK_M3 |
UART6_TX_M1 |
I3C1_SCL_M0 |
PWM2_CH2_M2 |
|||||||
PWM2_CH7_M2 |
SPI3_CSN1_M0 |
UART9_CTSN_M0 |
- |
SAI0_SDO3_M0 |
ETH_CLK0_25M_OUT_M1 |
CAM_CLK2_OUT_M1 |
GPIO2_D7_d |
37 |
38 |
GPIO2_D0_d |
- |
SAI4_SDI_M3 |
UART4_TX_M0 |
I2C6_SCL_M2 |
PWM2_CH0_M2 |
||||
GND |
39 |
40 |
GPIO2_D1_d |
ETH1_RXD0_M0 |
SAI4_SDO_M3 |
UART4_RX_M0 |
I2C6_SDA_M2 |
PWM2_CH1_M2 |
- |
||||||||||
GPIO测试¶
硬件连接¶
将 GPIO terminal expansion board 拓展版 直接扣在40Pin上,拉高或拉低,对应Pin 的 LED 会亮灭。
注:当前有 16 个普通 GPIO,以下复用功能对应引脚被占用,可以通过 qpi-config 去关闭对应复用功能作为普通GPIO使用。
Pin3/5:I2C3
Pin8/10:UART1
Pin19/21/23/24/26:SPI1
Pin27/28:I2C7
Pin33:PWM
直接使用 万用表测量 GPIO对应Pin脚电压,高电平应为3.3V,低电平为0V。
测试方法¶
以 Pin13(GPIO2_A6_d)为例,进入 adb shell 后,使用 gpiod 命令测试 GPIO。
adb shell
# 查看 GPIO 控制器
gpiodetect
# 查看 Pin13 GPIO2_A6
gpioinfo -c gpiochip2 6
# 读取当前电平
gpioget -c gpiochip2 6
# 拉高 10 秒
gpioset -c gpiochip2 -t 10s,0 6=1
# 拉低 10 秒
gpioset -c gpiochip2 -t 10s,0 6=0
GPIO 中断测试方法: 将待测 GPIO 接按键或外部信号源,让引脚电平发生变化;使用 gpiomon 监听该 GPIO 的上升沿和下降沿事件。这里仍以 GPIO2_A6_d 为例。
# 查看 GPIO2_A6 当前电平
gpioget -c gpiochip2 6
# 监听 GPIO2_A6 的上升沿、下降沿事件,触发 5 次后退出
gpiomon -n 5 -e rising -c gpiochip2 6
gpiomon -n 5 -e falling -c gpiochip2 6
I2C测试¶
Environment Sensor HAT测试:¶
40‑pin接口的pin3和pin5默认为I2C的数据和时钟引脚。为了测试I2C接口,我们需要外接一个I2C设备。此处我们选用 微雪环境传感器扩展板,对应的设备节点为 /dev/i2c-3。
本次测试使用微雪环境传感器扩展板,通过40‑pin接口进行连接。
硬件连接示意图:
I2C 接口验证(微雪环境传感器扩展板)¶
总线与设备探测¶
adb shell
ls /dev/i2c* # 确认 /dev/i2c-3 存在
i2cdetect -y 3 # 应看到 0x29 0x53 0x68 0x76(本板还会多一个 0x28)
读各 IC 的 ID 寄存器¶
# TSL2591:ID reg 0x12,需带 command bit 0xA0,即 0xA0|0x12=0xB2
i2cget -y 3 0x29 0xB2 b # 期望 0x50
# BME280:ID reg 0xD0
i2cget -y 3 0x76 0xD0 b # 期望 0x60
# LTR390:ID reg 0x06
i2cget -y 3 0x53 0x06 b # 期望 0xB2
# IMU:先选 bank 0 再读 WHO_AM_I(reg 0x00)
i2cset -y 3 0x68 0x7F 0x00 b
i2cget -y 3 0x68 0x00 b # 期望 0xEA
注:传感器板卡在直连40PIN的时候不支持用Pin27、28 ,板卡的40PIN 只连到Pin3、5的IIC 没接到27、28的,用杜邦线接27、28可以。
SPI测试¶
在 40-pin 接口中,Pin19/21/23/24/26 默认配置为 SPI 功能,对应的设备节点为 SPI1 。
查看串口设备
可以使用以下命令查看系统中所有的串口设备:
ls /dev/spidev*
/dev/spidev1.0 /dev/spidev1.1
SPI回环测试¶
硬件连接:Pin19 MOSI 与 Pin21 MISO 短接。
#!/bin/bash
# SPI loopback test (extracted from 40Pin-ctrl_M2.sh)
#
# Usage:
# ./spi_test.sh
#
# Wiring: connect Pin 19 (SPI1 MOSI) to Pin 21 (SPI1 MISO).
# Tests /dev/spidev1.0 and /dev/spidev1.1 at 100 kHz, 8 bits/word, mode 0.
# NOTE: MOSI/MISO loopback verifies SPI transfer and CLK.
# Verify Pin 24/26 CS waveforms with an oscilloscope or logic analyzer.
die()
{
echo "ERROR: $*" >&2
exit 1
}
require_root()
{
[ "$(id -u)" -eq 0 ] || die "must be run as root"
}
require_command()
{
command -v "$1" >/dev/null 2>&1 || die "$1 is not installed"
}
spi_test()
{
require_command python3
[ -c /dev/spidev1.0 ] || die "/dev/spidev1.0 does not exist"
[ -c /dev/spidev1.1 ] || die "/dev/spidev1.1 does not exist"
echo "Connect Pin 19 (SPI1 MOSI) to Pin 21 (SPI1 MISO)."
python3 - /dev/spidev1.0 /dev/spidev1.1 <<'PY'
import ctypes
import fcntl
import os
import struct
import sys
SPI_IOC_WR_MODE = 0x40016B01
SPI_IOC_WR_BITS_PER_WORD = 0x40016B03
SPI_IOC_WR_MAX_SPEED_HZ = 0x40046B04
SPI_IOC_MESSAGE_1 = 0x40206B00
PAYLOAD = bytes((0x55, 0xAA, 0x00, 0xFF, 0x12, 0x34, 0x56, 0x78))
def test_device(device):
tx = (ctypes.c_ubyte * len(PAYLOAD))(*PAYLOAD)
rx = (ctypes.c_ubyte * len(PAYLOAD))()
transfer = bytearray(struct.pack(
"=QQIIHBBBBBB",
ctypes.addressof(tx), ctypes.addressof(rx), len(PAYLOAD),
100000, 0, 8, 0, 0, 0, 0, 0,
))
fd = os.open(device, os.O_RDWR)
try:
fcntl.ioctl(fd, SPI_IOC_WR_MODE, struct.pack("=B", 0))
fcntl.ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, struct.pack("=B", 8))
fcntl.ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, struct.pack("=I", 100000))
fcntl.ioctl(fd, SPI_IOC_MESSAGE_1, transfer)
finally:
os.close(fd)
received = bytes(rx)
print("%s TX: %s" % (device, PAYLOAD.hex(" ").upper()))
print("%s RX: %s" % (device, received.hex(" ").upper()))
if received != PAYLOAD:
raise RuntimeError("%s loopback data mismatch" % device)
print("PASS: %s loopback data matched" % device)
try:
for path in sys.argv[1:]:
test_device(path)
except (OSError, RuntimeError) as exc:
print("FAIL: %s" % exc, file=sys.stderr)
sys.exit(1)
PY
status=$?
[ "$status" -eq 0 ] || return "$status"
echo "NOTE: MOSI/MISO loopback verifies SPI transfer and CLK."
echo " Verify Pin 24/26 CS waveforms with an oscilloscope or logic analyzer."
}
require_root
spi_test
脚本会测试 /dev/spidev1.0 和 /dev/spidev1.1,打印 TX/RX 数据并比较。
测试结果:
Connect Pin 19 (SPI1 MOSI) to Pin 21 (SPI1 MISO).
/dev/spidev1.0 TX: 55 AA 00 FF 12 34 56 78
/dev/spidev1.0 RX: 55 AA 00 FF 12 34 56 78
PASS: /dev/spidev1.0 loopback data matched
/dev/spidev1.1 TX: 55 AA 00 FF 12 34 56 78
/dev/spidev1.1 RX: 55 AA 00 FF 12 34 56 78
PASS: /dev/spidev1.1 loopback data matched
NOTE: MOSI/MISO loopback verifies SPI transfer and CLK.
Verify Pin 24/26 CS waveforms with an oscilloscope or logic analyzer.
UART测试¶
在 40-pin 接口中,pin8 和 pin10 默认配置为 UART 功能,对应的设备节点为 /dev/ttyS1。
查看串口设备
可以使用以下命令查看系统中所有的串口设备:
ls /dev/tty*
UART回环测试¶
本测试通过将 pin8 和 pin10 短接,验证串口收发功能是否正常。
硬件连接: 将 40‑pin 的 pin8(TX) 和 pin10(RX) 短接。
新建 uart_loopback_test.sh 文件,内容如下:
#!/bin/bash
# UART loopback test (extracted from 40Pin-ctrl_M2.sh)
#
# Usage:
# ./uart_loopback_test.sh [-n 1] [-b 115200]
# -n <num> UART number, maps to /dev/ttyS<num> (default 1)
# -b <bps> baud rate (default 115200)
#
# Wiring: connect Pin 8 (UART1 TX) to Pin 10 (UART1 RX).
die()
{
echo "ERROR: $*" >&2
exit 1
}
require_root()
{
[ "$(id -u)" -eq 0 ] || die "must be run as root"
}
require_command()
{
command -v "$1" >/dev/null 2>&1 || die "$1 is not installed"
}
uart_test()
{
device=/dev/ttyS1
baud=115200
while [ "$#" -gt 0 ]; do
case "$1" in
-n)
[ "$#" -ge 2 ] || die "missing UART number after -n"
case "$2" in
''|*[!0-9]*) die "invalid UART number: $2" ;;
esac
device="/dev/ttyS$2"
shift 2
;;
-b)
[ "$#" -ge 2 ] || die "missing baud rate after -b"
case "$2" in
''|*[!0-9]*) die "invalid baud rate: $2" ;;
esac
baud=$2
shift 2
;;
*)
die "usage: $0 [-n 1] [-b 115200]"
;;
esac
done
[ -c "$device" ] || die "$device does not exist"
require_command stty
require_command timeout
require_command cmp
echo "Connect Pin 8 (UART1 TX) to Pin 10 (UART1 RX)."
echo "Testing $device at $baud baud..."
tmp_dir=$(mktemp -d /tmp/uart-loopback.XXXXXX) || die "cannot create temporary directory"
trap 'rm -rf "$tmp_dir"' EXIT INT TERM
printf 'UART1_LOOPBACK\n' > "$tmp_dir/expected"
stty -F "$device" "$baud" raw -echo cs8 -cstopb -parenb \
-crtscts -ixon -ixoff || die "failed to configure $device"
timeout 5 dd if="$device" of="$tmp_dir/received" bs=1 count=15 status=none &
rx_pid=$!
sleep 1
printf 'UART1_LOOPBACK\n' > "$device"
wait "$rx_pid"
rx_status=$?
if [ "$rx_status" -ne 0 ]; then
die "UART loopback timed out or received incomplete data"
fi
if ! cmp -s "$tmp_dir/expected" "$tmp_dir/received"; then
echo "Received bytes:"
od -An -tx1 "$tmp_dir/received"
die "UART loopback data mismatch"
fi
echo "PASS: UART loopback sent and received 15 identical bytes"
rm -rf "$tmp_dir"
trap - EXIT INT TERM
}
require_root
uart_test "$@"
用法(在板子上):
# 给脚本权限
chmod +x uart_loopback_test.sh
# 默认 /dev/ttyS1, 115200,接线:Pin8(TX)<->Pin10(RX)
./uart_loopback_test.sh
# 指定 UART 号和波特率
./uart_loopback_test.sh -n 1 -b 9600
测试结果:
Connect Pin 8 (UART1 TX) to Pin 10 (UART1 RX).
Testing /dev/ttyS1 at 115200 baud...
PASS: UART loopback sent and received 15 identical bytes
PWM测试¶
接线: Pin33 (PWM, GPIO2_D3) ↔ Pin31 (GPIO92, GPIO2_D4) (默认)
新建 pwm_test.sh 文件,占空比为50%,频率为1000Hz,内容如下:
#!/bin/bash
# PWM output + GPIO edge capture test (extracted from 40Pin-ctrl_M2.sh)
#
# Usage:
# ./pwm_test.sh [capture-gpio]
# capture-gpio global GPIO number to capture PWM edges on
# (default 92 = Pin 31; 93 = Pin 29)
#
# Wiring: connect Pin 33 (PWM2_CH3, GPIO2_D3) to the capture GPIO.
# default Pin 31 / GPIO92 / GPIO2_D4 ; 93 = Pin 29 / GPIO2_D5.
# Output: 1000 Hz, 50% duty cycle.
# PASS criteria: measured 900-1100 Hz and 40-60% duty cycle, >=3 complete periods.
GPIO_SYSFS=/sys/class/gpio
die()
{
echo "ERROR: $*" >&2
exit 1
}
require_root()
{
[ "$(id -u)" -eq 0 ] || die "must be run as root"
}
require_command()
{
command -v "$1" >/dev/null 2>&1 || die "$1 is not installed"
}
pwm_test()
{
pwm_chip=
capture_gpio=${1:-92}
case "$capture_gpio" in
''|*[!0-9]*) die "invalid capture GPIO: $capture_gpio" ;;
esac
capture_chip=gpiochip$((capture_gpio / 32))
capture_line=$((capture_gpio % 32))
tmp_file=$(mktemp /tmp/pwm-test.XXXXXX) || die "cannot create temporary file"
require_command gpiomon
require_command timeout
require_command awk
for chip in /sys/class/pwm/pwmchip*; do
case "$(readlink -f "$chip/device" 2>/dev/null)" in
*/2ade3000.pwm) pwm_chip=$chip; break ;;
esac
done
[ -n "$pwm_chip" ] || die "PWM2_CH3 controller 2ade3000.pwm was not found"
pwm=$pwm_chip/pwm0
if [ -d "$GPIO_SYSFS/gpio$capture_gpio" ]; then
echo "$capture_gpio" > "$GPIO_SYSFS/unexport" 2>/dev/null || \
die "GPIO$capture_gpio is busy"
fi
if [ ! -d "$pwm" ]; then
echo 0 > "$pwm_chip/export" 2>/dev/null || die "cannot export pwm0"
fi
pwm_cleanup()
{
if [ -e "$pwm/enable" ] && [ "$(cat "$pwm/enable" 2>/dev/null)" = 1 ]; then
echo 0 > "$pwm/enable" 2>/dev/null || true
fi
rm -f "$tmp_file"
}
pwm_exit()
{
exit_status=$?
trap - EXIT INT TERM
pwm_cleanup
exit "$exit_status"
}
trap pwm_exit EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
if [ "$(cat "$pwm/enable" 2>/dev/null)" = 1 ]; then
echo 0 > "$pwm/enable" 2>/dev/null || die "cannot disable pwm0"
fi
if [ "$(cat "$pwm/period" 2>/dev/null)" != 0 ]; then
echo 0 > "$pwm/duty_cycle" 2>/dev/null || die "cannot reset PWM duty cycle"
fi
echo 1000000 > "$pwm/period" 2>/dev/null || die "cannot set PWM period"
echo 500000 > "$pwm/duty_cycle" 2>/dev/null || die "cannot set PWM duty cycle"
if [ "$capture_gpio" -eq 92 ]; then
echo "Connect Pin 33 (PWM, GPIO2_D3) to Pin 31 (GPIO92, GPIO2_D4)."
elif [ "$capture_gpio" -eq 93 ]; then
echo "Connect Pin 33 (PWM, GPIO2_D3) to Pin 29 (GPIO93, GPIO2_D5)."
else
echo "Connect Pin 33 (PWM, GPIO2_D3) to GPIO$capture_gpio."
fi
echo "Output: 1000 Hz, 50% duty cycle"
timeout 5 gpiomon -c "$capture_chip" -n 21 \
"$capture_line" > "$tmp_file" 2>&1 &
monitor_pid=$!
sleep 1
echo 1 > "$pwm/enable" 2>/dev/null || die "cannot enable pwm0"
wait "$monitor_pid"
monitor_status=$?
echo 0 > "$pwm/enable" 2>/dev/null || true
[ "$monitor_status" -eq 0 ] || die "no valid PWM edges captured on GPIO$capture_gpio"
awk -v capture_gpio="$capture_gpio" '
$2 == "rising" {
if (have_rise && have_fall) {
period = $1 - prev_rise
high = fall_time - rise_time
if (period > 0 && high >= 0) {
period_sum += period
high_sum += high
samples++
}
}
prev_rise = $1
rise_time = $1
have_rise = 1
have_fall = 0
}
$2 == "falling" && have_rise {
fall_time = $1
have_fall = 1
}
END {
if (samples < 3) {
print "FAIL: insufficient complete PWM periods" > "/dev/stderr"
exit 1
}
frequency = samples / period_sum
duty = 100 * high_sum / period_sum
printf "Measured: %.2f Hz, %.2f%% duty cycle (%d periods)\n", frequency, duty, samples
if (frequency < 900 || frequency > 1100 || duty < 40 || duty > 60) {
print "FAIL: PWM measurement is outside the expected range" > "/dev/stderr"
exit 1
}
printf "PASS: PWM output and GPIO%d capture are working\n", capture_gpio
}
' "$tmp_file" || exit 1
pwm_cleanup
trap - EXIT INT TERM
}
require_root
pwm_test "${1:-92}"
用法(在板子上):
# 给脚本权限
chmod +x pwm_test.sh
pwm_test.sh # 捕获 GPIO92 = Pin31
pwm_test.sh 93 # 捕获 GPIO93 = Pin29(改接 Pin33↔Pin29)
pwm_test.sh 86 # 任意全局 GPIO 号(改接对应脚)
测试结果:
Connect Pin 33 (PWM, GPIO2_D3) to Pin 31 (GPIO92, GPIO2_D4).
Output: 1000 Hz, 50% duty cycle
Measured: 1000.35 Hz, 49.98% duty cycle (10 periods)
PASS: PWM output and GPIO92 capture are working