40‑Pin扩展

Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.


Quectel Pi 智能主控板提供了标准的40‑pin GPIO扩展接口,支持GPIO、I2C、SPI、UART、PWM等多种外设接口,下面将介绍如何测试这些接口的功能。

../../_images/image_JJQZbzrVXoV7fNxzk3pc1xJSnmh.webp

引脚定义


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 会亮灭。

../../_images/image_Oz2fbql7koPYbvx06uVcx3Pdn9g.webp

注:当前有 16 个普通 GPIO,以下复用功能对应引脚被占用,可以通过 qpi-config 去关闭对应复用功能作为普通GPIO使用。

  • Pin3/5:I2C3

  • Pin8/10:UART1

  • Pin19/21/23/24/26:SPI1

  • Pin27/28:I2C7

  • Pin33:PWM

  1. 直接使用 万用表测量 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接口进行连接。

硬件连接示意图:

../../_images/image_Os17bkc1Eo3pL8xh8cZcpbAanWb.webp
接入扩展板的Quectel Pi M2

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 MOSIPin21 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 接口中,pin8pin10 默认配置为 UART 功能,对应的设备节点为 /dev/ttyS1

查看串口设备
可以使用以下命令查看系统中所有的串口设备:

ls /dev/tty*

UART回环测试

本测试通过将 pin8pin10 短接,验证串口收发功能是否正常。

硬件连接: 将 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