# 40‑Pin扩展
***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.***
---
**Quectel Pi** 智能主控板提供了标准的40‑pin GPIO扩展接口,支持GPIO、I2C、SPI、UART、PWM等多种外设接口,下面将介绍如何测试这些接口的功能。
```{image} images/image_JJQZbzrVXoV7fNxzk3pc1xJSnmh.webp
:width: 1172px
:height: 750px
:align: center
```
# 引脚定义
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测试
## 硬件连接
1. 将 **GPIO terminal expansion board 拓展版** 直接扣在40Pin上,拉高或拉低,对应Pin 的 LED 会亮灭。
```{image} images/image_Oz2fbql7koPYbvx06uVcx3Pdn9g.webp
:width: 4096px
:height: 3072px
:align: center
```
注:当前有 16 个普通 GPIO,以下复用功能对应引脚被占用,可以通过 [qpi-config](<../qpi-config/qpi-config.md>) 去关闭对应复用功能作为普通GPIO使用。
- Pin3/5:I2C3
- Pin8/10:UART1
- Pin19/21/23/24/26:SPI1
- Pin27/28:I2C7
- Pin33:PWM
2. 直接使用 **万用表测量** GPIO对应Pin脚电压,高电平应为3.3V,低电平为0V。
## 测试方法
以 Pin13(GPIO2_A6_d)为例,进入 adb shell 后,使用 gpiod 命令测试 GPIO。
```bash
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 为例。
```bash
# 查看 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接口进行连接。
**硬件连接示意图:**
```{image} images/image_Os17bkc1Eo3pL8xh8cZcpbAanWb.webp
:width: 4096px
:height: 3072px
:align: center
```
接入扩展板的Quectel Pi M2
## I2C 接口验证(微雪环境传感器扩展板)
### 总线与设备探测
```bash
adb shell
ls /dev/i2c* # 确认 /dev/i2c-3 存在
i2cdetect -y 3 # 应看到 0x29 0x53 0x68 0x76(本板还会多一个 0x28)
```
### 读各 IC 的 ID 寄存器
```python
# 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** 。
**查看串口设备**
可以使用以下命令查看系统中所有的串口设备:
```bash
ls /dev/spidev*
/dev/spidev1.0 /dev/spidev1.1
```
## SPI回环测试
**硬件连接:Pin19 MOSI** 与 **Pin21 MISO** 短接。
```c
#!/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 数据并比较。
**测试结果:**
```plaintext
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**。
**查看串口设备**
可以使用以下命令查看系统中所有的串口设备:
```bash
ls /dev/tty*
```
## UART回环测试
本测试通过将 **pin8** 和 **pin10** 短接,验证串口收发功能是否正常。
**硬件连接:** 将 40‑pin 的 pin8(TX) 和 pin10(RX) 短接。
新建 `uart_loopback_test.sh` 文件,内容如下:
```cpp
#!/bin/bash
# UART loopback test (extracted from 40Pin-ctrl_M2.sh)
#
# Usage:
# ./uart_loopback_test.sh [-n 1] [-b 115200]
# -n UART number, maps to /dev/ttyS (default 1)
# -b 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 "$@"
```
**用法(在板子上):**
```bash
# 给脚本权限
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
```
**测试结果:**
```plaintext
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,内容如下:
```bash
#!/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}"
```
**用法(在板子上):**
```bash
# 给脚本权限
chmod +x pwm_test.sh
pwm_test.sh # 捕获 GPIO92 = Pin31
pwm_test.sh 93 # 捕获 GPIO93 = Pin29(改接 Pin33↔Pin29)
pwm_test.sh 86 # 任意全局 GPIO 号(改接对应脚)
```
**测试结果:**
```plaintext
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
```