使用指南¶
Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.
使用指南旨在为用户提供 Quectel Pi M1 / L1 智能主控板的全方位使用指导,涵盖设备规格介绍、电源供给、显示及多类外设(摄像头、Wi-Fi/BT、蜂窝网络、以太网、音频、USB等)的功能说明、操作方法与注意事项等内容,帮助用户快速认知设备性能、掌握使用流程、解决应用难题,为设备的调试、部署及运维提供清晰、详实的参考依据。
规格介绍¶
请移步至 规格介绍 页面查看。
显示¶
MIPI¶
本文档详细介绍了 Quectel Pi M1 / L1 智能主控板的 MIPI—DSI 显示屏使用方法,并以微雪 8-inch DSI 接口 IPS 电容触摸屏为例进行详细说明。
MIPI屏幕连接¶
硬件接口连接¶
此屏幕共有4处接口可供电,任选一处即可,示例选择“1”处供电。
Type-C 接口。
I2C 通讯与供电接口。
5V 输入输出接口。
I2C 通讯与供电接口,需将螺丝孔对准 Quectel Pi M1 / L1 智能主控板拧紧,让顶针与 40-pin 连接牢固。
开机启动¶
开机启动后,进入系统桌面。
功能使用¶
触摸功能的使用方法¶
连接上 MIPI 显示器并且上电开机后,可以直接使用触摸功能。
调整音频和显示¶
音频具体配置请依次进入:Android 桌面 → 菜单 → 设置 → 提示音和震动,即可对音频进行设置。
显示具体配置请依次进入:Android 桌面 → 菜单 → 设置 → 显示 即可对显示进行设置。
注:此处仅展示了屏幕和声音的操作图,像其他的电池、存储、壁纸、位置信息等操作步骤均与屏幕和声音的操作步骤一致。
视频播放功能¶
Android桌面的视频播放功能
使用 adb 工具将视频文件推送到智能主控板。
adb push "E:\FeiShu\Animal World.mp4" /sdcard/DCIM #将相关视频推送到智能主控板SD卡的DCIM目录下#进入到智能主控板下
su #切换到root用户
am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Animal World.mp4 #重新扫描媒体库的这个文件
具体配置请依次进入:
Android 桌面 → 文件管理器 → 视频文件 → 播放视频。
音频播放功能¶
Android桌面的音频播放功能:
注:播放音频需要在 Quectel Pi M1 / L1 智能主控板上将喇叭连接。
使用 adb 工具将音频文件推送到智能主控板。
adb push E:\eage\test.mp3 /sdcard/Music #将音频推送到智能主控板SD卡的Music目录下#进入到智能主控板下
su #切换到root用户
am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/Music/test.mp3 #重新扫描媒体库的这个文件
具体配置请依次进入:
Android 桌面 → 文件管理器 → 播放音频。
HDMI¶
本节详细介绍了 Quectel Pi M1 / L1 智能主控板的 Micro HDMI 显示屏的使用方法,以 10.1英寸HDMI 显示屏为例进行详细说明。
Micro HDMI屏幕连接¶
硬件接口连接¶
开机启动¶
开机启动后,进入系统桌面。
功能使用¶
触摸功能的使用方法¶
HDMI 显示器上电开机后,需要将 USB 口连接到 HDMI 屏幕。
注:此功能需要带有触摸功能的 HDMI 屏幕。
按照下图进行连接:
调整音频和显示¶
注:接入 HDMI 显示器后,分辨率是自适应的,不可更改。目前所支持的 HDMI 分辨率是 3840 × 2160、1920 × 1080、1280 × 720、720 × 480
音频具体配置请依次进入:Android 桌面 → 菜单 → 设置 → 提示音和震动,即可对音频进行设置。
显示具体配置请依次进入:Android 桌面 → 菜单 → 设置 → 显示,即可对显示进行设置。
视频播放功能¶
Android桌面的视频播放功能
使用 adb 工具将视频文件推送到智能主控板。
adb push "E:\FeiShu\Animal World.mp4" /sdcard/DCIM #将相关视频推送到智能主控板SD卡的DCIM目录下#进入到智能主控板下su #切换到root用户
am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Animal World.mp4 #重新扫描媒体库的这个文件
具体配置请依次进入:
Android 桌面->文件管理器->视频文件->播放视频。
音频播放功能¶
Android桌面的音频播放功能:
注:播放音频需要在 Quectel Pi M1 / L1 智能主控板上将喇叭连接。
使用 adb 工具将音频文件推送到智能主控板。
```SHELL
adb push E:\eage\test.mp3 /sdcard/Music #将音频推送到智能主控板SD卡的Music目录下
#进入到智能主控板下
su #切换到root用户
am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/Music/test.mp3 #重新扫描媒体库的这个文件
2. 具体配置请依次进入:`Android 桌面->文件管理器->播放音频`。
```{image} images/image_BxWabw213o3Z7qxPRBccDibvn2w.webp
:width: 720px
:height: 448px
进阶功能¶
HDMI屏幕的热插拔¶
HDMI 屏幕支持热插拔功能,可以在系统运行过程中插拔 HDMI 屏幕,系统会自动识别并显示画面。
热插拔使用方法¶
在系统运行状态下,直接将 HDMI 屏幕插入智能主控板的 Micro HDMI 接口。
系统会自动检测到 HDMI 设备并启动显示输出。
如需断开 HDMI 屏幕,可直接拔出 Micro HDMI 线缆,系统会自动切换显示输出。
热插拔注意事项¶
热插拔功能仅支持通过 Micro HDMI 接口连接的显示屏。
使用DP转 Micro HDMI 适配器连接的屏幕可能无法正常支持热插拔。
在插拔过程中请避免频繁操作,建议每次插拔间隔不少于5秒。
相机¶
本文档以 IMX219 摄像头模块为例,详细介绍了 Quectel Pi M1 / L1 智能主控板 MIPI CSI 摄像头接口的识别与连接方法,并提供了功能使用指导及常见问题解决方案。
硬件接口¶
Quectel Pi M1 /L1 智能主控板共支持两路摄像头 MIPI CSI 接口,如下图所示:
快速开始¶
1.按照下图连接摄像头,连接时向上拉开连接器的黑色锁扣部分,将 FPC 排线插入第一个 CSI 卡槽,注意排线金手指朝向板内金属接触面,按下锁扣,确认 FPC 排线稳定没有松动。
2.参考显示连接显示屏
功能使用¶
预览¶
IMX219 摄像头所需的配置和参数已预先集成在内核中。具体操作步骤如下:摄像头连接至 Quectel-Pi-M1 / L1,上电开机->Android桌面->相机->摄像头界面。
视频录制¶
具体操作步骤:摄像头界面->点击 录制->开始录制视频->保存到相册,录制的视频文件也可通过文件系统查找。
拍照¶
具体操作步骤:摄像头界面->点击 拍照->开始拍照->保存到相册,照片文件也可通过文件系统查找。
Wi-Fi/BT¶
Quectel Pi M1 / L1 智能主控板支持 Wi-Fi 和 蓝牙 (BT) 连接。用户需连接 MIPI 显示器,开机进入 Android 桌面后,选择目标网络或设备进行连接,以实现无线通信。
Wi-Fi功能使用¶
启用并连接Wi-Fi¶
具体操作步骤如下:连接MIPI显示器->上电开机->Android桌面->下拉桌面->点击Wi-Fi图标->进入Wi-Fi界面。
蓝牙功能使用¶
Quectel Pi M1 / L1 智能主控板内置蓝牙功能,支持连接蓝牙外设。
启用蓝牙¶
具体操作步骤如下:连接MIPI显示器->上电开机->Android桌面->下拉桌面->点击BT图标->进入蓝牙(BT)界面。
蜂窝网络¶
蜂窝网络(Cellular Network)是一种基于无线电通信的广域网络架构,广泛应用于移动通信和物联网设备。其核心特点是将覆盖区域划分为多个“蜂窝”(Cell),每个蜂窝由基站(Base Station)提供无线接入,终端设备通过基站连接到运营商核心网,实现语音、短信和数据业务。
本节主要针对 USIM 连接网络使用方法介绍。
支持双卡双待,本文档只针对USIM进行介绍。
仅支持4G LTE网络。
硬件连接¶
SIM卡卡槽
Nano SIM拔插式卡座。
SIM卡天线接口
IPEX-4TH天线座。
快速开始¶
开机启动¶
进入系统,可查看SIM卡是否正常识别,正常识别如下图显示。
联网确认¶
使用adb进入系统,ping 8.8.8.8确认网络是否连接正常。
功能使用¶
打开蜂窝网络¶
从屏幕顶部下滑,下拉菜单会显示 Internet 选项,点击选择对应LTE的切换开关,点亮成打开状态。
关闭蜂窝网络¶
从屏幕顶部下滑,下拉菜单会显示 Internet 选项,点击选择对应LTE的切换开关,置暗成关闭状态。
常见问题¶
无线网卡激活成功,但是无法进行网络通信可能是什么原因?
一般有下面两种情况可能会导致这种问题:
SIM卡欠费 部分SIM卡在欠费状态下,模组仍能成功注册到网络并激活蜂窝无线网卡,但运营商会限制模组进行实际的网络通信。
APN配置问题 SIM卡支持通过特定的APN接入到特殊的网络,并且用户配置的也是接入特殊网络的APN,此时设备接入的是特殊网络,并非互联网。用户可能在这种情况下进行了访问互联网的操作,那么就会出现访问失败的情况。具体APN配置请依次进入 Settings -> Network & Internet -> Internet -> CMCC (connected/LTE) -> Access Point network 后,进行配置新的APN进行网络连接。
以太网¶
Quectel Pi M1 / L1 智能主控板自带1个标准 RJ45 接口,支持10/100/1000 Mbps通信速率的以太网。
以太网对网线的要求:仅支持直连网线,不支持交叉网线。
硬件接口¶
查看网络配置¶
系统默认开启DHCP自动获取网络配置。
终端输入 ifconfig 命令查看网络的接口地址:
ifconfig
输出示例:inet addr: 后面的 192.168.90.116 为本机的ip地址。
终端输入 ip route 命令查看网络路由信息:
ip route
输出示例:
查看 DNS 服务器地址:
dumpsys connectivity | grep -o ' DnsAddresses: \[[^]]*\]'
输出示例:
测试网络¶
**执行ping**命令测试网络连接
执行
ping 8.8.8.8命令。如果能够成功 ping 通(接收到响应),则表示外网网络连接正常。执行
ping www.example.com命令。如果能够成功 ping 通,则表示外网网络连接正常且 DNS 解析功能正常。
音频功能¶
本文档提供音频系统操作的简明指南。系统采用 ALSA + PulseAudio 的双层音频架构,从底层驱动到应用层提供完整的音频解决方案。
硬件层面,系统支持:
外置麦克风输入(外接 SH1.0-4P 音频转耳机接口)
外置扬声器/耳机输出,可实现耳机播放与麦克风输入的全双工通信
软件层面,系统提供一组标准音频配置与调试命令,用于完成设备检查、录音、播放和音量控制等常见操作。
通过本文档,您无需深入复杂配置,即可快速定位并解决大多数音频相关问题。
硬件接口¶
外接 SH1.0-4P 音频转耳机接口
板载 MEMS 麦克风
查看声卡信息¶
可通过以下命令检查系统声卡与 PCM 设备的加载情况:
查看声卡列表:
cat /proc/asound/cards
查看已分配的 pcm 流列表:
cat /proc/asound/pcm
说明:若系统未检测到声卡信息或 PCM 流,则 ALSA 与 PulseAudio 均无法正常工作。
功能使用¶
音频录制¶
Android系统下录制
tinymix "TX DEC0 MUX" "SWR_MIC"
tinymix "ADC2 ChMap" "SWRM_TX2_CH1"
tinymix "TX SMIC MUX0" "SWR_MIC4"
tinymix "TX_AIF1_CAP Mixer DEC0" "1"
tinymix "ADC2_MIXER Switch" "1"
tinymix "ADC2 MUX" "INP3"
tinymix 'TX_AIF1_CAP Mixer DEC2' '1'
agmcap ./testMic3.wav -D 100 -d 101 -c 1 -r 48000 -i "CODEC_DMA-LPAIF_RXTX-TX-3"
结束录制
按下“Ctrl” + “Z”结束录音:
耳机麦克风录制¶
Android系统下录制
tinymix "TX DEC0 MUX" "SWR_MIC"
tinymix "TX SMIC MUX0" "SWR_MIC5"
tinymix "DEC0_BCS Switch" "1"
tinymix "TX_AIF1_CAP Mixer DEC0" "1"
tinymix "ADC2 ChMap" "SWRM_TX2_CH2"
tinymix "MBHC ChMap" "SWRM_TX3_CH3"
tinymix "ADC2_MIXER Switch" "1"
tinymix "ADC2 MUX" "INP2"
tinymix "BCS Channel" "CH10"
agmcap ./rec2.wav -D 100 -d 101 –c 1 -r 48000 -i "CODEC_DMA-LPAIF_RXTX-TX-3"
注意:操作时需要确保当前目录有写入权限。
音频播放¶
板载扬声器未焊接,请使用外接耳机通道播放。
耳机播放
Android系统下播放(3.5 mm耳机)
adb root
adb shell
cd /data/local/tmp
tinymix "RX_MACRO RX0 MUX" "AIF1_PB"
tinymix "RX_MACRO RX1 MUX" "AIF1_PB"
tinymix "RX INT0_1 MIX1 INP0" "RX0"
tinymix "RX INT1_1 MIX1 INP0" "RX1"
tinymix "RX INT0 DEM MUX" "CLSH_DSM_OUT"
tinymix "RX INT1 DEM MUX" "CLSH_DSM_OUT"
tinymix "RX_COMP1 Switch" "1"
tinymix "RX_COMP2 Switch" "1"
tinymix "HPHL_RDAC Switch" "1"
tinymix "HPHR_RDAC Switch" "1"
agmplay ./vvdn_ring.wav -D 100 -d 100 -i CODEC_DMA-LPAIF_RXTX-RX-0 -dkv 0xA2000002
疑难解答¶
问题 |
可能原因 |
|---|---|
录音文件未生成 |
|
音频播放失败 |
|
USB¶
USB host是指能够控制和管理USB设备的系统或硬件,它负责为连接的USB设备(如U盘、键盘、鼠标等)提供电源、数据传输和管理功能。
Quectel Pi M1/L1智能主控板自带2个标准USB 3.0 Type-A接口、2个标准USB 2.0 Type-A接口和1个USB 3.1 Type-C接口。USB 2.0接口最高数据传输速率可达480 Mbps,USB 3.1接口最高数据传输速率可达5 Gbps。
功能使用¶
接入U盘¶
在Type-A口中插入一个U盘。
通知栏显示USB设备。
下拉并展开通知栏。
点击 EXPLORE 按钮浏览U盘内容。
SD卡¶
SD 卡 (Secure Digital Card)是一种广泛使用的便携式存储介质,具备体积小、容量大、功耗低及易用性强等特点。它常用于手机、相机、嵌入式设备及单板计算机,用于存储系统文件、应用程序及数据,是现代移动与嵌入式设备中最主流的存储方案之一。
硬件接口¶
Quectel Pi M1/L1 智能主控板配备了一个 micro SD + Nano SIM 二合一卡座,支持 SD 3.0 协议。
注意:上方为 micro SD 卡座,下方是 Nano SIM 卡座。
功能使用¶
系统识别¶
插入 SD 卡后,可通过以下命令确认系统是否识别到设备:
fdisk -l
对比SD卡插入前后的输出信息,可以看到系统已成功识别SD卡:
挂载与卸载¶
查看挂载路径:
df -h
如下所示,设备已自动挂载至 /mnt/sdcard:
查看挂载目录下的文件:
ls /mnt/sdcard
挂载到其他目录:
sudo mkdir -p /media/sdcard
sudo mount /dev/mmcblk1p1 /media/sdcard
查看挂载结果:
ls /media/sdcard
使用完毕后卸载:
sudo umount /media/sdcard
提示:卸载后再拔出SD卡可防止数据损坏。
文件操作与权限¶
普通用户访问 SD 卡时,可能遇到权限不足的问题。可通过以下命令更改挂载点的所有权:
sudo chown <username> /mnt/sdcard
sudo chmod 755 /mnt/sdcard
将 <username> 替换为当前登录的用户名。修改后即可在 /mnt/sdcard 目录中正常读写文件。
ADC¶
ADC 子系统由信号复用器和电压模数转换器(VADC)组成,用于将内部及外部模拟信号通过多路复用方式路由至 VADC,并完成模拟量到数字量的转换。
ADC模块硬件架构图¶
M1模块支持使用的ADC¶
模块支持使用的ADC如下:
接口 |
引脚号 |
引脚名 |
PM4250引脚号 |
电压域 |
MPM中断 |
引脚描述 |
|---|---|---|---|---|---|---|
ADC |
128 |
ADC0 |
GPIO4 |
1.8 V |
- |
PMU_GPIO04 |
ADC |
185 |
ADC1 |
GPIO7 |
1.8 V |
- |
PMU_GPIO07 |
ADC位置¶
智能主控板上ADC接口的位置如下图所示:
ADC节点使用示例¶
ADC节点主要用来查看该节点输入的ADC数值。
使用方法¶
测试时使用可调直流电源作为输入信号源:
将电源 正极 连接到 ADC0 或 ADC1 引脚。
电源 GND 与智能主控板 GND 相连。
各通道对应关系及输入特性如下:
ADC0:
连接到GPIO4
节点:
in_voltage_adc_gpio4_input支持输入电压范围:约 0~5.25 V
ADC1:
连接到GPIO7
节点:
in_voltage_adc_gpio7_input支持输入电压范围:约 0~5.25 V
设置直流电源输出为目标电压后,可通过以下命令读取 ADC 采样结果(单位:μV):
adb shell
cd /sys/bus/iio/devices/iio:device0
cat in_voltage_adc_gpio4_input
cat in_voltage_adc_gpio7_input
测试代码¶
以下示例代码演示了在应用程序中读取 ADC 电压值的基本方法。程序通过访问ADC对应的节点文件,读取ADC原始值(单位为 μV),并转换为整数以供后续处理。
点击展开/折叠:ADC读取测试代码(可自行NDK编译)
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <errno.h>int main()
{
int fd = -1;
int adc_value;
char buff[64] = {0};
// 打开ADC节点文件(ADC0对应GPIO4)
fd = open("/sys/bus/iio/devices/iio:device0/in_voltage_adc_gpio4_input", O_RDONLY);
if(fd < 0){
perror("open error\n");
return fd;
}
// 读取ADC值int n_read = read(fd, buff, sizeof(buff));
if(n_read < 0) {perror("read error\n");close(fd);return -1;}// 将读取的字符串转换为整数(单位为μV)
adc_value = atol(buff);
printf("read %d bytes, content: %s\n", n_read, buff);
printf("ADC value: %d μV\n", adc_value);
close(fd);
return 0;
}
代码说明:
使用
open()函数打开ADC节点文件/sys/bus/iio/devices/iio:device0/in_voltage_adc_gpio4_input。使用
read()函数读取ADC原始值(字符串格式,单位为μV)。使用
atol()函数将字符串转换为长整型数值。读取的ADC值单位为微伏(μV),可根据需要转换为电压值。
编译和运行:
# 使用NDK编译# 将编译好的可执行文件推送到设备并运行
adb push test_adc /data/local/tmp/test_adc
adb shell "chmod +x /data/local/tmp/test_adc"
adb shell "/data/local/tmp/test_adc"
测试数据¶
ADC引脚规格¶
模块上的ADC引脚定义如下:
接口 |
模块引脚号 |
模块引脚名 |
模块 I/O |
NET名称(复用功能) |
说明 |
|---|---|---|---|---|---|
ADC |
128 |
ADC0 |
I |
ADC0 |
1) 连接器输入电压范围:0–5.25 V。 |
ADC |
185 |
ADC1 |
I |
ADC1 |
与ADC0相同的电气特性(0–5.25 V输入范围,RH = 18 kΩ,RL = 10 kΩ,ADC引脚电压 = Vin × 10/28)。 |
ADC0测试数据¶
电压源 |
ADC0(μV) |
理论值(V) |
|---|---|---|
1.8 V |
638408 |
1.8 V×10/28 = 0.642857 |
3 V |
1066414 |
3 V×10/28 = 1.07143 |
5 V |
1784623 |
5 V×10/28 = 1.78571 |
ADC1测试数据¶
电压源 |
ADC1(μV) |
理论值(V) |
|---|---|---|
1.8 V |
640549 |
1.8 V×10/28 = 0.642857 |
3 V |
1069269 |
3 V×10/28 = 1.07143 |
5 V |
1789814 |
5 V×10/28 = 1.78571 |
L1模块支持使用的ADC¶
接口 |
引脚号 |
引脚名 |
PM4250引脚号 |
电压域 |
MPM中断 |
引脚描述 |
|---|---|---|---|---|---|---|
ADC |
128 |
ADC0 |
GPIO4 |
1.8 V |
- |
PMU_GPIO04 |
ADC位置¶
智能主控板上ADC接口的位置如下图所示:
ADC节点使用示例¶
ADC节点主要用来查看该节点输入的ADC数值。
使用方法¶
测试时使用可调直流电源作为输入信号源:
将电源 正极 连接到 ADC0 或 ADC1 引脚。
电源 GND 与智能主控板 GND 相连。
各通道对应关系及输入特性如下:
ADC0:
连接到GPIO4
节点:
in_voltage_adc_gpio6_input支持输入电压范围:约 0~5.25 V
设置直流电源输出为目标电压后,可通过以下命令读取 ADC 采样结果(单位:μV):
adb shell
cd /sys/bus/iio/devices/iio:device0
cat in_voltage_adc_gpio6_input
测试代码¶
以下示例代码演示了在应用程序中读取 ADC 电压值的基本方法。程序通过访问ADC对应的节点文件,读取ADC原始值(单位为 μV),并转换为整数以供后续处理。
点击展开/折叠:ADC读取测试代码(可自行NDK编译)
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <errno.h>int main()
{
int fd = -1;
int adc_value;
char buff[64] = {0};
// 打开ADC节点文件(ADC0对应GPIO4)
fd = open("/sys/bus/iio/devices/iio:device0/in_voltage_adc_gpio6_input", O_RDONLY);
if(fd < 0){
perror("open error\n");
return fd;
}
// 读取ADC值int n_read = read(fd, buff, sizeof(buff));
if(n_read < 0) {perror("read error\n");close(fd);return -1;}// 将读取的字符串转换为整数(单位为μV)
adc_value = atol(buff);
printf("read %d bytes, content: %s\n", n_read, buff);
printf("ADC value: %d μV\n", adc_value);
close(fd);
return 0;
}
代码说明:
使用
open()函数打开ADC节点文件/sys/bus/iio/devices/iio:device0/in_voltage_adc_gpio6_input。使用
read()函数读取ADC原始值(字符串格式,单位为μV)。使用
atol()函数将字符串转换为长整型数值。读取的ADC值单位为微伏(μV),可根据需要转换为电压值。
编译和运行:
# 使用NDK编译# 将编译好的可执行文件推送到设备并运行
adb push test_adc /data/local/tmp/test_adc
adb shell "chmod +x /data/local/tmp/test_adc"
adb shell "/data/local/tmp/test_adc"
测试数据¶
ADC引脚规格¶
模块上的ADC引脚定义如下:
接口 |
模块引脚号 |
模块引脚名 |
模块 I/O |
NET名称(复用功能) |
说明 |
|---|---|---|---|---|---|
ADC |
128 |
ADC0 |
I |
ADC0 |
1) 连接器输入电压范围:0–5.25 V。 |
ADC0测试数据¶
电压源 |
ADC0(μV) |
理论值(V) |
|---|---|---|
1.8 V |
638408 |
1.8 V×10/28 = 0.642857 |
3 V |
1066414 |
3 V×10/28 = 1.07143 |
5 V |
1784623 |
5 V×10/28 = 1.78571 |
40‑Pin扩展¶
Quectel Pi M1智能主控板提供了标准的40‑pin GPIO扩展接口,支持GPIO、I2C、SPI、UART、PWM等多种外设接口。下面将介绍如何测试这些接口的功能。
引脚定义¶
M1GPIO引脚对应列表
L1GPIO引脚对应列表
GPIO测试¶
本节以测试M1 40‑Pin的pin7引脚为例,演示如何使用GPIO功能。pin7对应的gpio_num为83。
L1的GPIO测试可复用代码,但是需要在代码中修改对应引脚对应的GPIO值
硬件连接¶
连接方法一:万用表测量电压
将pin7(GPIO_83)引脚接万用表正极,pin6(GND)引脚接万用表负极。通过万用表可以测量引脚输出的电压值,验证GPIO功能是否正常。
连接方法二:使用GPIO扩展板指示灯
也可以通过接插 树莓派4B/3B GPIO扩展板 到40-Pin扩展接口来测试GPIO高低电平。该扩展板将各GPIO引脚引出并配有对应的指示灯,可将待测GPIO引脚配置为输出并将其接到扩展板上,当GPIO输出高电平时,小灯点亮,输出低电平时,小灯熄灭,从而直观观察GPIO电平变化是否符合预期。
关于该扩展板的更多信息,可参考配件章节中的 GPIO terminal expansion board 条目。
测试方法¶
以下示例基于Android平台,需要使用NDK对C代码进行交叉编译后才可使用
pin_control_sysfs源码(可自行使用NDK编译)
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <errno.h>// GPIO引脚映射表 (SC200U Gamma Linux项目)typedef struct {int pin;int gpio_num; // Linux sysfs GPIO编号} pin_map_t;// 根据Bengal平台的GPIO映射 (与Android项目一致)static const pin_map_t pin_mapping[] = {{3, 494}, {5, 495}, {7, 481}, {8, 467}, {10, 468}, {11, 504}, {12, 496}, {13, 429},{15, 469}, {16, 482}, {18, 423}, {19, 399}, {21, 398}, {22, 478}, {23, 400}, {24, 401},{26, 480}, {27, 412}, {28, 413}, {29, 403}, {31, 402}, {32, 465}, {33, 396}, {35, 497},{36, 414}, {37, 415}, {38, 499}, {40, 498}};#define PIN_COUNT (sizeof(pin_mapping) / sizeof(pin_map_t))/**
* 根据pin号获取GPIO映射信息
*/const pin_map_t* get_pin_info(int pin) {for (int i = 0; i < PIN_COUNT; i++) {if (pin_mapping[i].pin == pin) {return &pin_mapping[i];}}return NULL;}/**
* 导出GPIO到sysfs
*/int export_gpio(int gpio_num) {char path[64];int fd;// 检查GPIO是否已导出snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d", gpio_num);if (access(path, F_OK) == 0) {return 0; // 已导出}// 导出GPIO
fd = open("/sys/class/gpio/export", O_WRONLY);if (fd < 0) {printf("无法打开 /sys/class/gpio/export: %s\n", strerror(errno));return -1;}char gpio_str[8];snprintf(gpio_str, sizeof(gpio_str), "%d", gpio_num);if (write(fd, gpio_str, strlen(gpio_str)) < 0) {// 如果该GPIO已被导出,写入时会返回错误,忽略此非致命问题if (errno != EBUSY) {printf("无法export GPIO%d: %s\n", gpio_num, strerror(errno));close(fd);return -1;}}close(fd);usleep(100000); // 延时100ms,等待sysfs节点创建完成return 0;}/**
* 取消导出GPIO
*/int unexport_gpio(int gpio_num) {int fd;
fd = open("/sys/class/gpio/unexport", O_WRONLY);if (fd < 0) {return -1;}char gpio_str[8];snprintf(gpio_str, sizeof(gpio_str), "%d", gpio_num);write(fd, gpio_str, strlen(gpio_str));close(fd);return 0;}/**
* 设置GPIO方向
*/int set_gpio_direction(int gpio_num, const char *direction) {char path[64];int fd;snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/direction", gpio_num);
fd = open(path, O_WRONLY);if (fd < 0) {printf("无法打开 %s: %s\n", path, strerror(errno));return -1;}if (write(fd, direction, strlen(direction)) < 0) {printf("设置GPIO%d方向失败: %s\n", gpio_num, strerror(errno));close(fd);return -1;}close(fd);return 0;}/**
* 设置GPIO值
*/int set_gpio_value(int gpio_num, int value) {char path[64];int fd;snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", gpio_num);
fd = open(path, O_WRONLY);if (fd < 0) {printf("无法打开 %s: %s\n", path, strerror(errno));return -1;}char value_str[2];snprintf(value_str, sizeof(value_str), "%d", value);if (write(fd, value_str, 1) < 0) {printf("设置GPIO%d值失败: %s\n", gpio_num, strerror(errno));close(fd);return -1;}close(fd);return 0;}/**
* 读取GPIO值
*/int get_gpio_value(int gpio_num, int *value) {char path[64];int fd;char val[2];snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", gpio_num);
fd = open(path, O_RDONLY);if (fd < 0) {printf("无法打开 %s: %s\n", path, strerror(errno));return -1;}if (read(fd, val, 1) < 0) {printf("读取GPIO%d失败: %s\n", gpio_num, strerror(errno));close(fd);return -1;}close(fd);*value = (val[0] == '1') ? 1 : 0;return 0;}/**
* 使用sysfs控制GPIO
* action: 0=拉低, 1=拉高, 2=读取
*/int control_gpio_sysfs(int gpio_num, int action) {// 确保GPIO已导出if (export_gpio(gpio_num) < 0) {return -1;}if (action == 0 || action == 1) {// 设置方向为输出if (set_gpio_direction(gpio_num, "out") < 0) {return -1;}// 设置值if (set_gpio_value(gpio_num, action) < 0) {return -1;}printf("✓ GPIO%d已设置为%s电平\n", gpio_num, action ? "高" : "低");// 验证设置是否生效usleep(50000);int verify_value;if (get_gpio_value(gpio_num, &verify_value) == 0) {printf(" 验证: GPIO%d实际状态: %s电平 (值: %d)\n",
gpio_num, verify_value ? "高" : "低", verify_value);}} else if (action == 2) {// 读取GPIO值int value;if (get_gpio_value(gpio_num, &value) == 0) {printf("✓ GPIO%d当前状态: %s电平 (值: %d)\n",
gpio_num, value ? "高" : "低", value);} else {return -1;}}return 0;}/**
* 读取所有引脚状态
*/int read_all_pins() {printf("========================================\n");printf("=== 读取所有引脚状态 ===\n");printf("========================================\n");int success_count = 0;for (int i = 0; i < PIN_COUNT; i++) {printf("pin%d (GPIO%d): ", pin_mapping[i].pin, pin_mapping[i].gpio_num);fflush(stdout);if (control_gpio_sysfs(pin_mapping[i].gpio_num, 2) == 0) {
success_count++;} else {printf("✗ 读取失败\n");}}printf("========================================\n");printf("成功读取 %d/%lu 个引脚\n", success_count, PIN_COUNT);printf("========================================\n");return 0;}/**
* 控制所有引脚
*/int control_all_pins(int action) {printf("========================================\n");printf("开始控制所有引脚为%s电平...\n", action ? "高" : "低");printf("========================================\n");int success_count = 0;for (int i = 0; i < PIN_COUNT; i++) {printf("pin%d (GPIO%d): ", pin_mapping[i].pin, pin_mapping[i].gpio_num);fflush(stdout);if (control_gpio_sysfs(pin_mapping[i].gpio_num, action) == 0) {
success_count++;} else {printf("✗ 控制失败\n");}}printf("========================================\n");printf("成功控制 %d/%lu 个引脚\n", success_count, PIN_COUNT);printf("========================================\n");return (success_count == PIN_COUNT) ? 0 : 1;}/**
* 显示帮助信息
*/void show_help(const char *prog_name) {printf("用法: %s <pin_number|all> <up|down|get>\n", prog_name);printf("例如: %s pin3 up # 拉高pin3\n", prog_name);printf("例如: %s pin5 down # 拉低pin5\n", prog_name);printf("例如: %s pin7 get # 读取pin7\n", prog_name);printf("例如: %s all up # 拉高所有pin\n", prog_name);printf("例如: %s all down # 拉低所有pin\n", prog_name);printf("例如: %s all get # 读取所有pin状态\n", prog_name);printf("\n支持的引脚:\n");for (int i = 0; i < PIN_COUNT; i++) {printf("pin%d(GPIO%d) ", pin_mapping[i].pin, pin_mapping[i].gpio_num);if ((i + 1) % 4 == 0) printf("\n");}printf("\n");}/**
* 主函数
*/int main(int argc, char *argv[]) {if (argc < 3) {show_help(argv[0]);return 1;}char *pin_str = argv[1];char *action_str = argv[2];// 解析动作参数int action;if (strcmp(action_str, "up") == 0) {
action = 1;} else if (strcmp(action_str, "down") == 0) {
action = 0;} else if (strcmp(action_str, "get") == 0) {
action = 2;} else {printf("错误: 动作必须是up, down或get\n");return 1;}// 处理all命令if (strcmp(pin_str, "all") == 0) {if (action == 2) {return read_all_pins();} else {return control_all_pins(action);}}// 解析引脚号int pin;if (sscanf(pin_str, "pin%d", &pin) != 1) {printf("错误: 无效的引脚格式,请使用pin3, pin5等\n");return 1;}const pin_map_t *pin_info = get_pin_info(pin);if (pin_info == NULL) {printf("错误: 不支持的引脚 pin%d\n", pin);return 1;}printf("控制 pin%d (GPIO%d)...\n", pin, pin_info->gpio_num);return control_gpio_sysfs(pin_info->gpio_num, action);}
将交叉编译后可执行文件推送到开发板并赋权:
adb push <本地存放路径>/pin_control_sysfs /data/local/tmp/pin_control_sysfs
adb shell "chmod +x /data/local/tmp/pin_control_sysfs"
adb shell "ls -lh /data/local/tmp/pin_control_sysfs"
进入设备端shell并运行工具以查看用法:
adb shell
cd /data/local/tmp
./pin_control_sysfs
运行后会打印可用命令和已支持的40‑pin引脚映射,示例输出如下:
常用操作示例:
拉高单个引脚:
./pin_control_sysfs pin7 up拉低单个引脚:
./pin_control_sysfs pin7 down读取单个引脚:
./pin_control_sysfs pin7 get操作全部引脚:
./pin_control_sysfs all up | all down | all get
提示:运行时确保目标引脚未被其它功能(如I2C/SPI/UART)占用,建议先通过扩展板指示灯或万用表确认电平变化是否符合预期。
I2C测试¶
40‑pin接口的pin3和pin5默认为I2C的数据和时钟引脚。为了测试I2C接口,我们需要外接一个I2C设备。此处我们选用微雪环境传感器扩展板,对应的设备节点为 /dev/i2c-1。
测试准备¶
|
|
|
|
|---|---|---|---|
WSL25911FN |
0x29 |
0x12 |
0x50 |
BME280 |
0x76 |
0xD0 |
0x60 |
MPU9250 |
0x68 |
0x75 |
0x71 |
LTR390-UV-1 |
0x53 |
0x06 |
0xB2 |
PS:此处的WSL25911FN在读ID寄存器的时候需要带上command bit,也就是0xA0,即寄存器 0x12 实际访问时常写成 0xA0 | 0x12 = 0xB2,其他的不用。
本次测试使用微雪环境传感器扩展板,通过40‑pin接口进行连接。
硬件连接示意图:
测试方法¶
adb shell #进入ADB shell
ls -al /dev/i2c* #查看I2C-1是否在对应目录下
i2cdetect -y 1 #查看对应的I2C一共挂了多少个设备
I2C读取/写入¶
i2cget -y 1 0x53 0x05 b #读取i2c1下设备地址为0x53的0x05寄存器的值,大小为一个byte
i2cset -y 1 0x53 0x05 0x03 b #设置i2c1下设备地址为0x53的0x05寄存器的值,大小为一个byte
SPI测试¶
40-pin接口中的SPI功能对应的设备片选节点为 /dev/spidev0.0 和 /dev/spidev0.1。在adb中输入以下内容可查看这些节点:
adb shell "ls /dev/spidev*"
微雪OLED显示器测试(cs0和cs1)¶
请按照以下表格进行接线:
OLED引脚 |
应该连接到 |
SC200U物理引脚 |
备注 |
|---|---|---|---|
VCC |
3.3V电源 |
Pin1或Pin17 |
必须是3.3V |
GND |
地线 |
Pin6/9/14等任意GND |
共地 |
DIN/MOSI |
SPI数据 |
Pin19 |
SPI_MOSI |
CLK/SCK |
SPI时钟 |
Pin23 |
SPI_CLK |
CS/CE |
片选 |
Pin24/Pin26 |
SPI_CE0/SPI_CE1 |
D/C |
数据/命令 |
Pin22 |
GPIO80 |
RES/RST |
复位 |
Pin18 |
GPIO25 |
硬件连接示意图:
执行步骤:
创建源文件
新建 test_spi.c 文件,内容如下:
test_spi源码(可自行使用NDK或交叉编译)
/*
* Test both CE0 and CE1 - requires manual wire switching
* This helps verify which CS pin your OLED is connected to
* Enhanced with text display and FPS performance test
*/#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <string.h>#include <unistd.h>#include <fcntl.h>#include <sys/ioctl.h>#include <linux/spi/spidev.h>#include <time.h>#define GPIO_BASE 398#define GPIO_RST (GPIO_BASE + 25)#define GPIO_DC (GPIO_BASE + 80)#define SPI_MODE SPI_MODE_3#define SPI_SPEED 8000000#define OLED_WIDTH 128#define OLED_HEIGHT 32#define OLED_PAGES 4// Complete 5x7 fontstatic const uint8_t font_5x7[][5] = {{0x00, 0x00, 0x00, 0x00, 0x00}, // Space{0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0{0x00, 0x42, 0x7F, 0x40, 0x00}, // 1{0x42, 0x61, 0x51, 0x49, 0x46}, // 2{0x21, 0x41, 0x45, 0x4B, 0x31}, // 3{0x18, 0x14, 0x12, 0x7F, 0x10}, // 4{0x27, 0x45, 0x45, 0x45, 0x39}, // 5{0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6{0x01, 0x71, 0x09, 0x05, 0x03}, // 7{0x36, 0x49, 0x49, 0x49, 0x36}, // 8{0x06, 0x49, 0x49, 0x29, 0x1E}, // 9{0x7E, 0x11, 0x11, 0x11, 0x7E}, // A{0x7F, 0x49, 0x49, 0x49, 0x36}, {0x3E, 0x41, 0x41, 0x41, 0x22}, // B C{0x7F, 0x41, 0x41, 0x22, 0x1C}, {0x7F, 0x49, 0x49, 0x49, 0x41}, // D E{0x7F, 0x09, 0x09, 0x09, 0x01}, {0x3E, 0x41, 0x49, 0x49, 0x7A}, // F G{0x7F, 0x08, 0x08, 0x08, 0x7F}, {0x00, 0x41, 0x7F, 0x41, 0x00}, // H I{0x20, 0x40, 0x41, 0x3F, 0x01}, {0x7F, 0x08, 0x14, 0x22, 0x41}, // J K{0x7F, 0x40, 0x40, 0x40, 0x40}, {0x7F, 0x02, 0x0C, 0x02, 0x7F}, // L M{0x7F, 0x04, 0x08, 0x10, 0x7F}, {0x3E, 0x41, 0x41, 0x41, 0x3E}, // N O{0x7F, 0x09, 0x09, 0x09, 0x06}, {0x3E, 0x41, 0x51, 0x21, 0x5E}, // P Q{0x7F, 0x09, 0x19, 0x29, 0x46}, {0x46, 0x49, 0x49, 0x49, 0x31}, // R S{0x01, 0x01, 0x7F, 0x01, 0x01}, {0x3F, 0x40, 0x40, 0x40, 0x3F}, // T U{0x1F, 0x20, 0x40, 0x20, 0x1F}, {0x3F, 0x40, 0x38, 0x40, 0x3F}, // V W{0x63, 0x14, 0x08, 0x14, 0x63}, {0x07, 0x08, 0x70, 0x08, 0x07}, // X Y{0x61, 0x51, 0x49, 0x45, 0x43}, // Z{0x08, 0x08, 0x08, 0x08, 0x08}, // -{0x00, 0x36, 0x36, 0x00, 0x00}, // :{0x00, 0x60, 0x60, 0x00, 0x00}, // .};static int char_to_index(char c) {if (c == ' ') return 0;if (c >= '0' && c <= '9') return 1 + (c - '0');if (c >= 'A' && c <= 'Z') return 11 + (c - 'A');if (c >= 'a' && c <= 'z') return 11 + (c - 'a');if (c == '-') return 37;if (c == ':') return 38;if (c == '.') return 39;return 0;}int gpio_write(int gpio, int value);void oled_quick_test(const char *device, const char *name);int main() {printf("\n╔════════════════════════════════════════════════════════╗\n");printf("║ Chip Select Pin Comparison Tool ║\n");printf("╚════════════════════════════════════════════════════════╝\n");printf("\nThis tool helps identify which CS pin is connected.\n");printf("\nInstruction:\n");printf(" 1. Connect OLED CS to Pin24 first\n");printf(" 2. Run test - should see display\n");printf(" 3. Move OLED CS to Pin26\n");printf(" 4. Run test again - should see display\n");printf("\n");char choice;printf("Which pin is your OLED CS currently connected to?\n");printf(" [0] Pin24 (CE0)\n");printf(" [1] Pin26 (CE1)\n");printf("Choice: ");scanf(" %c", &choice);if (choice == '0') {printf("\nTesting CE0 (Pin24)...\n");oled_quick_test("/dev/spidev0.0", "CE0-PIN24");} else if (choice == '1') {printf("\nTesting CE1 (Pin26)...\n");oled_quick_test("/dev/spidev0.1", "CE1-PIN26");} else {printf("Invalid choice\n");return 1;}return 0;}void oled_quick_test(const char *device, const char *name) {// Display bufferstatic uint8_t buffer[OLED_PAGES][OLED_WIDTH];// Simple GPIO functionsint gpio_export(int gpio) {int fd = open("/sys/class/gpio/export", O_WRONLY);if (fd < 0) return -1;char buf[10];snprintf(buf, sizeof(buf), "%d", gpio);write(fd, buf, strlen(buf));close(fd);usleep(100000);return 0;}int gpio_set_dir(int gpio) {char path[50];snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/direction", gpio);int fd = open(path, O_WRONLY);if (fd < 0) return -1;write(fd, "out", 3);close(fd);return 0;}int gpio_write(int gpio, int value) {char path[50];snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", gpio);int fd = open(path, O_WRONLY);if (fd < 0) return -1;char buf[2] = {value ? '1' : '0', 0};write(fd, buf, 1);close(fd);return 0;}void send_cmd(int fd, uint8_t cmd) {gpio_write(GPIO_DC, 0);write(fd, &cmd, 1);usleep(1000);}void set_pixel(int x, int y, int color) {if (x >= 0 && x < OLED_WIDTH && y >= 0 && y < OLED_HEIGHT) {int page = y / 8;int bit = y % 8;if (color)
buffer[page][x] |= (1 << bit);else
buffer[page][x] &= ~(1 << bit);}}void draw_char(int x, int y, char c) {int idx = char_to_index(c);for (int col = 0; col < 5; col++) {uint8_t data = font_5x7[idx][col];for (int row = 0; row < 8; row++) {if (data & (1 << row)) {set_pixel(x + col, y + row, 1);}}}}void draw_text(int x, int y, const char *text) {int cursor = x;while (*text) {draw_char(cursor, y, *text);
cursor += 6;
text++;}}void clear_buffer() {memset(buffer, 0, OLED_PAGES * OLED_WIDTH);}void display_buffer(int fd) {for (int page = 0; page < OLED_PAGES; page++) {send_cmd(fd, 0xB0 + page);send_cmd(fd, 0x04);send_cmd(fd, 0x10);gpio_write(GPIO_DC, 1);write(fd, buffer[page], OLED_WIDTH);}}// Initialize GPIOgpio_export(GPIO_RST);gpio_export(GPIO_DC);usleep(200000);gpio_set_dir(GPIO_RST);gpio_set_dir(GPIO_DC);// Open SPIprintf(" Opening %s...\n", device);int spi_fd = open(device, O_RDWR);if (spi_fd < 0) {perror(" ERROR: Cannot open device");printf(" → %s is NOT working or OLED not connected\n", name);return;}printf(" ✓ Device opened\n");// Configure SPIuint8_t mode = SPI_MODE;uint32_t speed = SPI_SPEED;ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);// Reset OLEDprintf(" Resetting OLED...\n");gpio_write(GPIO_RST, 0);usleep(50000);gpio_write(GPIO_RST, 1);usleep(50000);// Initialize OLEDprintf(" Initializing OLED...\n");send_cmd(spi_fd, 0xAE);send_cmd(spi_fd, 0x04); send_cmd(spi_fd, 0x10); send_cmd(spi_fd, 0x40);send_cmd(spi_fd, 0x81); send_cmd(spi_fd, 0xFF);send_cmd(spi_fd, 0xA1); send_cmd(spi_fd, 0xA6);send_cmd(spi_fd, 0xA8); send_cmd(spi_fd, 0x1F);send_cmd(spi_fd, 0xC8);send_cmd(spi_fd, 0xD3); send_cmd(spi_fd, 0x00);send_cmd(spi_fd, 0xD5); send_cmd(spi_fd, 0xF0);send_cmd(spi_fd, 0xD8); send_cmd(spi_fd, 0x05);send_cmd(spi_fd, 0xD9); send_cmd(spi_fd, 0xC2);send_cmd(spi_fd, 0xDA); send_cmd(spi_fd, 0x12);send_cmd(spi_fd, 0xDB); send_cmd(spi_fd, 0x08);send_cmd(spi_fd, 0xAF);printf(" ✓ OLED initialized\n");// Test 1: Display "USING: CE0" or "USING: CE1"printf("\n [Test 1] Displaying chip select info...\n");clear_buffer();draw_text(10, 4, "USING:");if (strstr(name, "CE0")) {draw_text(25, 16, "CE0");} else {draw_text(25, 16, "CE1");}display_buffer(spi_fd);printf(" ✓ Screen showing: USING %s\n", strstr(name, "CE0") ? "CE0" : "CE1");sleep(3);// Test 2: Fill whiteprintf("\n [Test 2] Filling screen WHITE...\n");memset(buffer, 0xFF, OLED_PAGES * OLED_WIDTH);display_buffer(spi_fd);printf(" ✓ Screen should be WHITE\n");sleep(2);// Test 3: Blink testprintf("\n [Test 3] Blink test (5 times)...\n");for (int i = 0; i < 5; i++) {// Whitememset(buffer, 0xFF, OLED_PAGES * OLED_WIDTH);display_buffer(spi_fd);usleep(200000);// Blackmemset(buffer, 0x00, OLED_PAGES * OLED_WIDTH);display_buffer(spi_fd);usleep(200000);printf(" Blink %d/5\n", i + 1);}printf(" ✓ Blink test complete\n");// Test 4: FPS Performance Testprintf("\n [Test 4] FPS Performance Test...\n");printf(" Running 100 frame updates...\n");struct timespec start, end;clock_gettime(CLOCK_MONOTONIC, &start);for (int frame = 0; frame < 100; frame++) {// Generate test patternfor (int page = 0; page < OLED_PAGES; page++) {for (int x = 0; x < OLED_WIDTH; x++) {
buffer[page][x] = (x + frame) & 0xFF;}}display_buffer(spi_fd);}clock_gettime(CLOCK_MONOTONIC, &end);double elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1000000000.0;double fps = 100.0 / elapsed;printf(" ✓ Performance: %.1f FPS\n", fps);// Display FPS result on screenclear_buffer();draw_text(10, 4, "FPS TEST");char fps_str[20];snprintf(fps_str, sizeof(fps_str), "%.1f FPS", fps);draw_text(20, 16, fps_str);display_buffer(spi_fd);printf(" ✓ FPS result displayed on screen\n");sleep(3);// Test 5: CS Pin Functionality Testprintf("\n [Test 5] CS Pin Functionality Test...\n");printf(" This test verifies CS pin actually controls the device\n");// Step 1: Display number 1printf(" Step 1: Displaying pattern 1...\n");clear_buffer();draw_text(15, 4, "PATTERN");draw_text(50, 16, "1");display_buffer(spi_fd);sleep(2);// Step 2: Try to update through the OTHER CS (should fail if CS works)printf(" Step 2: Trying to update via OTHER CS...\n");const char *other_device = strstr(name, "CE0") ? "/dev/spidev0.1" : "/dev/spidev0.0";const char *other_name = strstr(name, "CE0") ? "CE1" : "CE0";int other_fd = open(other_device, O_RDWR);if (other_fd >= 0) {uint8_t mode = SPI_MODE;uint32_t speed = SPI_SPEED;ioctl(other_fd, SPI_IOC_WR_MODE, &mode);ioctl(other_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);// Try to display pattern 2 via other CSclear_buffer();draw_text(15, 4, "PATTERN");draw_text(50, 16, "2");for (int page = 0; page < OLED_PAGES; page++) {send_cmd(other_fd, 0xB0 + page);send_cmd(other_fd, 0x04);send_cmd(other_fd, 0x10);gpio_write(GPIO_DC, 1);write(other_fd, buffer[page], OLED_WIDTH);}close(other_fd);printf(" Sent update via %s (other CS)\n", other_name);sleep(2);printf("\n ╔════════════════════════════════════════════════╗\n");printf(" ║ CHECK SCREEN: Still showing '1' or changed to '2'? ║\n");printf(" ╚════════════════════════════════════════════════╝\n");printf("\n If still showing '1': CS pin is WORKING correctly ✓\n");printf(" If changed to '2': CS pin NOT working (both CS active) ✗\n\n");sleep(2);}// Step 3: Update back via correct CSprintf(" Step 3: Updating via correct CS (%s)...\n", name);clear_buffer();draw_text(15, 4, "BACK TO");draw_text(50, 16, "1");display_buffer(spi_fd);printf(" ✓ Should see pattern 1 again\n");sleep(2);// Test 6: FPS result summaryprintf("\n [Test 6] Final Summary...\n");clear_buffer();draw_text(30, 4, name);char summary_str[20];snprintf(summary_str, sizeof(summary_str), "%.1fFPS", fps);draw_text(20, 16, summary_str);display_buffer(spi_fd);printf(" ✓ Summary displayed\n");sleep(3);// Clear screenclear_buffer();display_buffer(spi_fd);close(spi_fd);printf("\n======================================================================\n");printf(" %s Test Complete!\n", name);printf("======================================================================\n");printf(" CS Pin Test Result:\n");printf(" If screen didn't change to '2' in Step 2:\n");printf(" → CS pin is working correctly ✓\n");printf(" If screen changed to '2':\n");printf(" → CS pin might not be controlling the device ✗\n");printf(" → Both CS pins may be active simultaneously\n");printf("\n Performance: %.1f FPS\n", fps);printf("======================================================================\n");}
adb root
adb push "D:\tools\40pin android\test_android_spi" /data/local/tmp/test_android_spi
adb shell "chmod +x /data/local/tmp/test_android_spi"
adb shell
cd /data/local/tmp/
./test_android_spi
程序运行后会提示选择SPI设备:
选择0:使用
/dev/spidev0.0(CE0, Pin24)选择1:使用
/dev/spidev0.1(CE1, Pin26)
请根据实际接线情况选择对应的设备(CS/CE引脚连接到pin24则选择0,连接到pin26则选择1)。
测试结果:
程序执行后会依次进行以下测试:
╔══════════════════════════════════════════════════════╗
║ Android SPI OLED 测试程序 ║
║ 适配Android设备 ║
╚══════════════════════════════════════════════════════╝
选择SPI设备:
[0] /dev/spidev0.0 (CE0, Pin24)
[1] /dev/spidev0.1 (CE1, Pin26)
[c] 自定义设备路径
选择: 1
使用设备: /dev/spidev0.1
初始化GPIO...
✓ GPIO初始化成功
打开SPI设备: /dev/spidev0.1
✓ SPI设备已打开并配置
- 模式: 3
- 速度: 2000000 Hz
- 位宽: 8 bits
初始化OLED...
复位OLED...
发送初始化命令...
✓ OLED初始化完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
开始测试序列: SPI0.1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[测试1] 显示设备信息...
✓ 屏幕应显示: ANDROID SPI0.1
[测试2] 全白屏测试...
✓ 屏幕应全白
[测试3] 闪烁测试 (5次)...
闪烁 1/5
闪烁 2/5
闪烁 3/5
闪烁 4/5
闪烁 5/5
✓ 闪烁测试完成
[测试4] FPS性能测试...
运行100帧更新...
✓ 性能: 40.3 FPS (耗时: 2.48秒)
✓ FPS结果已显示
[测试5] 图案测试...
图案1: 横条纹
图案2: 竖条纹
图案3: 棋盘格
✓ 图案测试完成
[测试6] 测试总结...
✓ 总结已显示
测试说明:
测试1:验证OLED能够正常显示文本信息
测试2:验证OLED全屏显示功能
测试3:通过闪烁测试验证OLED刷新功能
测试4:测试SPI通信性能(FPS值)
测试5:测试不同图案显示功能
测试6:显示测试总结信息
若所有测试项均显示 √ 标记,则表明SPI通信与OLED功能一切正常。
UART测试¶
在 40-pin 接口中,pin8 和 pin10 默认配置为 UART 功能,对应的设备节点为 /dev/ttyHS0。
查看串口设备
可以使用以下命令查看系统中所有的串口设备:
ls /dev/tty*
UART回环测试¶
本测试通过将 pin8 和 pin10 短接,验证串口收发功能是否正常。
硬件连接: 将40‑pin的pin8(TX)和pin10(RX)短接。
执行步骤: 创建源文件
新建 uart_loopback.c 文件,内容如下:
uart_loopback源码(可自行使用NDK或交叉编译)
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <fcntl.h>#include <termios.h>#include <errno.h>#include <signal.h>// 全局变量用于信号处理static int serial_fd = -1;static volatile int running = 1;// 信号处理函数void signal_handler(int sig) {printf("\n用户中断测试\n");
running = 0;if (serial_fd >= 0) {close(serial_fd);printf("串口已关闭\n");}exit(0);}// 配置串口参数int configure_serial(int fd, int baudrate) {struct termios tty;if (tcgetattr(fd, &tty) != 0) {printf("获取串口属性失败: %s\n", strerror(errno));return -1;}// 设置波特率speed_t speed;switch (baudrate) {case 9600: speed = B9600; break;case 19200: speed = B19200; break;case 38400: speed = B38400; break;case 57600: speed = B57600; break;case 115200: speed = B115200; break;case 230400: speed = B230400; break;default: speed = B115200; break;}cfsetospeed(&tty, speed);cfsetispeed(&tty, speed);// 配置串口参数
tty.c_cflag &= ~PARENB; // 无奇偶校验
tty.c_cflag &= ~CSTOPB; // 1个停止位
tty.c_cflag &= ~CSIZE; // 清除数据位设置
tty.c_cflag |= CS8; // 8个数据位
tty.c_cflag &= ~CRTSCTS; // 禁用硬件流控制
tty.c_cflag |= CREAD | CLOCAL; // 启用接收和本地连接// 配置输入模式
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控制
tty.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始模式// 配置输出模式
tty.c_oflag &= ~OPOST; // 原始输出// 配置本地模式
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始模式// 设置超时
tty.c_cc[VTIME] = 10; // 1秒超时
tty.c_cc[VMIN] = 0; // 非阻塞读取if (tcsetattr(fd, TCSANOW, &tty) != 0) {printf("设置串口属性失败: %s\n", strerror(errno));return -1;}return 0;}// UART回环测试函数int serial_loopback_test(const char* port, int baudrate) {char test_data[] = "Hello, Serial Loopback!";char received_data[256];ssize_t bytes_written, bytes_read;// 打开串口设备
serial_fd = open(port, O_RDWR | O_NOCTTY | O_SYNC);if (serial_fd < 0) {printf("无法打开串口 %s: %s\n", port, strerror(errno));return -1;}// 配置串口if (configure_serial(serial_fd, baudrate) != 0) {close(serial_fd);return -1;}printf("串口 %s 已打开,开始回环测试(按Ctrl+C退出)...\n", port);while (running) {// 清空接收缓冲区tcflush(serial_fd, TCIOFLUSH);// 发送数据
bytes_written = write(serial_fd, test_data, strlen(test_data));if (bytes_written < 0) {printf("发送数据失败: %s\n", strerror(errno));break;}printf("发送: %s\n", test_data);// 等待数据接收usleep(100000); // 100ms// 读取回环数据
bytes_read = read(serial_fd, received_data, sizeof(received_data) - 1);if (bytes_read < 0) {printf("读取数据失败: %s\n", strerror(errno));break;}
received_data[bytes_read] = '\0'; // 添加字符串结束符// 验证结果if (bytes_read == strlen(test_data) &&
strncmp(received_data, test_data, strlen(test_data)) == 0) {printf("接收: %s → 测试通过\n\n", received_data);} else {printf("接收异常: 发送[%zu] vs 接收[%zd] → 测试失败\n",
strlen(test_data), bytes_read);if (bytes_read > 0) {printf("接收内容: %s\n\n", received_data);} else {printf("未接收到数据\n\n");}}sleep(1); // 间隔1秒重复测试}close(serial_fd);printf("串口 %s 已关闭\n", port);return 0;}int main(int argc, char* argv[]) {const char* port = "/dev/ttyHS0"; // 默认串口int baudrate = 115200; // 默认波特率// 解析命令行参数if (argc >= 2) {
port = argv[1];}if (argc >= 3) {
baudrate = atoi(argv[2]);}// 设置信号处理signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);printf("UART回环测试程序\n");printf("使用串口: %s\n", port);printf("波特率: %d\n", baudrate);printf("=====================================\n");// 执行回环测试int result = serial_loopback_test(port, baudrate);return result;}
执行步骤:
adb root
adb push <本地路径>/uart_loopback_test /data/local/tmp/uart_loopback
adb shell chmod +x /data/local/tmp/uart_loopback
adb shell ls -la /data/local/tmp/
adb shell
cd /data/local/tmp/
./uart_loopback
测试结果:
当pin8和pin10正确短接时,程序会持续发送数据并验证接收的数据是否一致:
UART回环测试程序
使用串口: /dev/ttyHS0
波特率: 115200
=====================================
串口 /dev/ttyHS0 已打开,开始回环测试(按Ctrl+C退出)...
发送: Hello, Serial Loopback!
接收: Hello, Serial Loopback! → 测试通过
发送: Hello, Serial Loopback!
接收: Hello, Serial Loopback! → 测试通过
发送: Hello, Serial Loopback!
接收: Hello, Serial Loopback! → 测试通过
按 Ctrl+C 可退出测试程序。程序会自动关闭串口并退出。
散热器控制¶
温控风扇原理¶
后台进程检测/sys/class/thermal/thermal_zone*
并轮读几个核的温度取最高,并且根据最高温度来配置风扇的PWM占空比。
温度 |
占空比/255 |
|---|---|
<30℃ |
0 |
30~40℃ |
64 |
40~50℃ |
128 |
50~60℃ |
192 |
>60℃ |
255 |
风扇引脚 |
M1开发板引脚(40PIN) |
|---|---|
红线(电源正极) |
PIN2 |
黑线(电源负极) |
PIN5 |
蓝线(PWM) |
PIN33 |
测试流程¶
默认风扇功能是关闭的,开机后输入对应指令查看风扇服务的状态
/system/bin/sh /vendor/bin/40Pin-ctrl fan status #查看当前风扇服务的状态,未启用风扇服务为disabled,启用为enable
/system/bin/sh /vendor/bin/40Pin-ctrl fan enable #使能风扇服务,使能后可以查看下状态是否为enable
/system/bin/sh /vendor/bin/40Pin-ctrl fan disable #关闭风扇服务,停止测试才需要输入该指令
在第一个终端里面输入
/system/bin/sh /vendor/bin/40Pin-ctrl fan status
/system/bin/sh /vendor/bin/40Pin-ctrl fan enable
#SC200U
watch -n 1 'PWM=/sys/class/pwm/pwmchip0/pwm0; p=$(cat "$PWM/period"); d=$(cat "$PWM/duty_cycle"); echo $(( (d * 255 + p / 2) / p ))'
#SC200E
watch -n 1 'PWM=/sys/bus/platform/devices/1c40000.qcom,spmi:qcom,pm2250@1:qcom,pwms@be00/pwm/pwmchip2/pwm0; p=$(cat "$PWM/period"); d=$(cat "$PWM/duty_cycle"); echo $(( (d * 255 + p / 2) / p ))'
开第二个终端,做升温
for i in 1 2 3 4; do
yes > /dev/null &
done
如果第3步里面的升温不够可以把负载调大
for i in $(seq 1 $(nproc)); do
yes > /dev/null &
done
看两个终端里面的温度和风扇的PWM占空比是不是和表里面的对齐
测试结束后停止负载
killall yes
应用场景¶
媒体娱乐终端: 使用 Quectel Pi M1 智能主控板作为媒体娱乐终端,可以播放视频、音乐、查看图片等。
工业数据可视化平台: 使用 Quectel Pi M1 智能主控板作为工业数据可视化平台,可以显示工业数据、监控设备运行情况、进行数据处理等。
智能交互终端:使用 Quectel Pi M1 智能主控板作为智能交互终端,可以使用于各类的人机交互应用。
疑难解答¶
如何查看系统日志¶
查看系统日志,需要将串口线接至电脑,并使用串口工具查看日志。
黑屏问题¶
在正常连接好设备后,开机后显示 黑屏,可能是以下的情况:
电源连接问题:
检查显示屏的供电接口是否正确连接,确认4个供电接口中至少有一个已正确连接,确保供电螺丝已拧紧,顶针与40-pin接口接触良好。
连接线路问题:
检查MIPI排线是否正确插入并牢固连接,确认排线方向是否正确,避免插反,检查连接器是否有物理损坏。
系统启动问题:
确认系统已正常启动并加载显示服务,查看系统日志确认显示驱动是否正常加载。
花屏问题¶
在正常连接好设备后,开机后显示 花屏,可能是以下的情况:
信号干扰:
检查MIPI排线是否过长或质量不佳,确认排线周围是否有强电磁干扰源,尝试更换高质量的MIPI排线。
硬件兼容性问题:
确认使用的显示屏型号与 Quectel Pi M1 完全兼容,检查显示屏固件版本是否为最新版本。
触摸功能异常¶
触摸功能异常,可能是以下情况:
驱动问题:
确认系统已正确加载触摸屏驱动,检查I2C通信是否正常建立。