# 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 ```