# 40‑Pin扩展
***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.***
---
**Quectel Pi M1/L1** 智能主控板提供了标准的40‑pin GPIO扩展接口,支持GPIO、I2C、SPI、UART、PWM等多种外设接口,下面将介绍如何测试这些接口的功能。
```{image} images/image_EhghbCYO3oQ9S4xtZLNcPZZ6nlh.webp
:width: 882px
:height: 515px
:align: center
```
# 引脚定义
Function3
|
Function2
|
Function1
|
L1 GPIO#
|
M1 GPIO#
|
Pin#
|
Pin#
|
M1 GPIO#
|
L1 GPIO#
|
Function1
|
Function2
|
Function3
|
VCC 3V3
|
1
|
2
|
VCC 5V
|
Common IIC SDA
|
|
|
GPIO_109(I2C1_SDA)
|
3
|
4
|
VCC 5V
|
Common IIC SCL
|
|
|
GPIO_110(I2C1_SCL)
|
5
|
6
|
GND
|
|
|
|
GPIO_60
|
GPIO_83
|
7
|
8
|
GPIO_69(UART1_TXD)
|
SPI1_SCLK
|
UART1_TX
|
|
GND
|
9
|
10
|
GPIO_70(UART1_RXD)
|
SPI1_CS_N
|
UART1_RX
|
|
|
|
|
GPIO_106
|
11
|
12
|
GPIO_98
|
|
|
|
|
|
|
GPIO_31
|
13
|
14
|
GND
|
|
UART2_TX
|
SPI2_SCLK
|
GPIO_71
|
15
|
16
|
GPIO_84
|
PMU_GPIO3
|
|
|
|
VCC 3V3
|
17
|
18
|
GPIO_25
|
GPIO_32
|
|
|
|
I2C0_SCL
|
UART0_RTS
|
SPI0_MOSI
|
GPIO_1(SPI_MOSI)
|
19
|
20
|
GND
|
I2C0_SDA
|
UART0_CTS
|
SPI0_MISO
|
GPIO_0(SPI_MISO)
|
21
|
22
|
GPIO_80
|
SPI2_CS_N
|
UART2_RX
|
|
|
UART0_TX
|
SPI0_SCLK
|
GPIO_2(SPI_CLK)
|
23
|
24
|
GPIO_3(SPI_CE0)
|
SPI0_CS_N_0
|
UART0_RX
|
|
GND
|
25
|
26
|
GPIO_82(SPI_CE1)
|
|
|
|
I2C5_SDA
|
UART5_CTS
|
SPI5_MISO
|
GPIO_14(I2C0_SDA)
|
27
|
28
|
GPIO_15(I2C0_SCL)
|
SPI5_MOSI
|
UART5_RTS
|
I2C5_SCL
|
I2C1_SCL
|
UART1_RTS
|
SPI1_MOSI
|
GPIO_5
|
29
|
30
|
GND
|
I2C1_SDA
|
UART1_CTS
|
SPI1_MISO
|
GPIO_4
|
31
|
32
|
GPIO_67
|
PMU_GPIO8
|
|
|
|
|
|
PWM
|
PMU_GPIO2(PWM)
|
PMU_GPIO8(PWM)
|
33
|
34
|
GND
|
|
|
|
GPIO_99
|
35
|
36
|
GPIO_16
|
SPI5_SCLK
|
UART5_TX
|
|
|
UART5_RX
|
SPI5_CS_N
|
GPIO_17
|
37
|
38
|
GPIO_101
|
|
|
|
GND
|
39
|
40
|
GPIO_100
|
|
|
|
# GPIO测试
## 硬件连接
以 Pin13(GPIO_31)为例,万用表正极接 Pin13,负极接 GND(如 Pin14)。测试其他 GPIO 时,将命令里的 line offset 改成对应 GPIO 编号即可。
## 测试方法
进入 adb shell 后,使用 gpiod 命令测试 GPIO。普通 SoC GPIO 使用 gpiochip0,GPIO_31 对应 gpiochip0 line 31。
```bash
adb shell
# 查看 GPIO 控制器
gpiodetect
# 查看指定 GPIO 状态
gpioinfo gpiochip0 | grep -E "line[[:space:]]+31:"
# 读取 GPIO 当前电平
gpioget gpiochip0 31
# 拉高 GPIO,保持 10 秒
gpioset --mode=time --sec=10 gpiochip0 31=1
# 拉低 GPIO,保持 10 秒
gpioset --mode=time --sec=10 gpiochip0 31=0
```
**GPIO 中断测试方法:** 将待测 GPIO 接按键或外部信号源,让引脚电平发生变化;使用 gpiomon 监听该 GPIO 的上升沿和下降沿事件。这里仍以 GPIO_31 为例。
```bash
# 查看 GPIO_31 当前电平
gpioget gpiochip0 31
# 监听 GPIO_31 的上升沿、下降沿事件,触发 5 次后退出
gpiomon --num-events=5 --rising-edge --falling-edge gpiochip0 31
```
注意:如果报错 `gpiodetect: error while loading shared libraries: libgpiod.so.2: cannot open shared object file: No such file or directory`,先执行 `export LD_LIBRARY_PATH=/opt/qcom/lib:$LD_LIBRARY_PATH` 后再运行 gpiod 命令。
**万用表测量结果:** 执行高电平输出时测得接近 3.3V,执行低电平输出时测得接近 0V,即可判定 GPIO 输出功能正常。
```{image} images/image_Fdeebupk3oQ6KlxpR8KcpE4inUh.webp
:width: 1671px
:height: 1246px
:align: center
```
# I2C测试
40‑pin接口的pin3和pin5默认为I2C的数据和时钟引脚。为了测试I2C接口,我们需要外接一个I2C设备。此处我们选用 [微雪环境传感器扩展板](),对应的设备节点为 `/dev/i2c-1`。
## 测试准备
| **
IC
** | **
I2C Addr
** | **
ID Reg
** | **
期望ID返回值
** |
| --- | --- | --- | --- |
|
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接口进行连接。
**硬件连接示意图:**
```{image} images/image_TPhKbw09ColfhJxRvm2cUVNqnPh.webp
:width: 1189px
:height: 639px
```
接入扩展板的Quectel Pi M1
## 测试方法
```plaintext
adb shell #进入ADB shell
ls -al /dev/i2c* #查看I2C-1是否在对应目录下
i2cdetect -y 1 #查看对应的I2C一共挂了多少个设备
```
```{image} images/image_QfaAb4zc7oaT2MxoycrceMaJny9.webp
:width: 526px
:height: 339px
:align: center
```
## I2C读取/写入
```plaintext
i2cget -y 1 0x53 0x05 b #读取i2c1下设备地址为0x53的0x05寄存器的值,大小为一个byte
i2cset -y 1 0x53 0x05 0x03 b #设置i2c1下设备地址为0x53的0x05寄存器的值,大小为一个byte
```
```{image} images/image_GD3Nb3pqlo1XVPx0C6pcTY2onQd.webp
:width: 364px
:height: 112px
:align: center
```
# SPI测试
这里我们采用 [2.23寸OLED扩展板]()
40-pin接口中的SPI功能对应的设备片选节点为 `/dev/spidev0.0` 和 `/dev/spidev0.1`。在adb中输入以下内容可查看这些节点:
```bash
adb shell #进入ADB shell
ls /dev/spidev* #查看SPI dev
```
## 微雪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 | M1: GPIO25
L1: GPIO32 |
**硬件连接示意图:**
```{image} images/image_P29FbgSGooMqVLx7v43czirsnbd.webp
:width: 1137px
:height: 645px
```
SPI0.0 (CE0, Pin24) 测试 - OLED显示 "ANDROID SPI0.0"
```{image} images/image_EOvzbYrQOom7MDxUbjucxMs0noh.webp
:width: 1176px
:height: 646px
```
SPI0.1 (CE1, Pin26) 测试 - OLED显示 "ANDROID SPI0.1"
**Linux宿主机创建SPI测试文件spi_oled_demo.c:**
```cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPIO_BASE 385
#define GPIO_RST (GPIO_BASE + 32)
#define GPIO_DC (GPIO_BASE + 80)
// 大多数 SSD1306/SH1106 SPI OLED 使用 MODE0,先改为 MODE0,降速到 4MHz 提高可靠性
#define SPI_MODE SPI_MODE_0
#define SPI_SPEED 4000000
#define OLED_WIDTH 128
#define OLED_HEIGHT 32
#define OLED_PAGES 4
// Complete 5x7 font
static 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 buffer
static uint8_t buffer[OLED_PAGES][OLED_WIDTH];
// Simple GPIO functions
int 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;
}
int configure_spi(int fd) {
uint8_t mode = SPI_MODE;
uint32_t speed = SPI_SPEED;
if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {
perror(" ERROR: Cannot set SPI mode");
return -1;
}
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
perror(" ERROR: Cannot set SPI speed");
return -1;
}
printf(" SPI configured: mode %u, speed %u Hz\n", mode, speed);
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 GPIO
gpio_export(GPIO_RST);
gpio_export(GPIO_DC);
usleep(200000);
gpio_set_dir(GPIO_RST);
gpio_set_dir(GPIO_DC);
// Open SPI
printf(" 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 SPI
if (configure_spi(spi_fd) < 0) {
printf(" → %s SPI configuration failed\n", name);
close(spi_fd);
return;
}
// Reset OLED
printf(" Resetting OLED...\n");
gpio_write(GPIO_RST, 0);
usleep(50000);
gpio_write(GPIO_RST, 1);
usleep(50000);
// Initialize OLED
printf(" 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 white
printf("\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 test
printf("\n [Test 3] Blink test (5 times)...\n");
for (int i = 0; i < 5; i++) {
// White
memset(buffer, 0xFF, OLED_PAGES * OLED_WIDTH);
display_buffer(spi_fd);
usleep(200000);
// Black
memset(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 Test
printf("\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 pattern
for (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 screen
clear_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 Test
printf("\n [Test 5] CS Pin Functionality Test...\n");
printf(" This test verifies CS pin actually controls the device\n");
// Step 1: Display number 1
printf(" 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) {
if (configure_spi(other_fd) < 0) {
close(other_fd);
printf(" Skipping %s update because SPI configuration failed\n", other_name);
goto update_back;
}
// Try to display pattern 2 via other CS
clear_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 CS
update_back:
printf(" 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 summary
printf("\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 screen
clear_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");
}
```
**创建完成之后将其编译成可执行文件:**
```plaintext
mkdir -p out #创建输出目录
aarch64-linux-gnu-gcc spi_oled_demo.c -O2 -Wall -Wextra -o spi_oled_demo #编译成可执行文件
```
将编译好的文件从Linux环境copy到adb shell同级目录下。
**执行步骤:**
```bash
adb push spi_oled_demo /tmp/spi_oled_demo
adb shell chmod +x /tmp/spi_oled_demo
adb shell
/tmp/spi_oled_demo
```
程序运行后会提示选择SPI设备:
- **选择0**:使用 `/dev/spidev0.0` (CE0, Pin24)
- **选择1**:使用 `/dev/spidev0.1` (CE1, Pin26)
请根据实际接线情况选择对应的设备(CS/CE引脚连接到pin24则选择0,连接到pin26则选择1)。
**测试结果:**
程序执行后会依次进行以下测试:
```plaintext
╔══════════════════════════════════════════════════════╗
║ 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**。
**查看串口设备**
可以使用以下命令查看系统中所有的串口设备:
```bash
ls /dev/tty*
```
## UART回环测试
本测试通过将 **pin8** 和 **pin10** 短接,验证串口收发功能是否正常。
**硬件连接:** 将40‑pin的pin8(TX)和pin10(RX)短接。
**Linux宿主机创建UART测试文件:**
新建 `uart_loopback.c` 文件,内容如下:
```cpp
#include
#include
#include
#include
#include
#include
#include
#include
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;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
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;
tty.c_cc[VMIN] = 0;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("设置串口属性失败: %s\n", strerror(errno));
return -1;
}
return 0;
}
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);
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 == (ssize_t)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);
}
close(serial_fd);
printf("串口 %s 已关闭\n", port);
return 0;
}
void print_usage(const char* prog) {
printf("UART回环测试程序\n\n");
printf("用法:\n");
printf(" %s -n [-b 波特率] 指定ttyHS编号\n", prog);
printf(" %s -d <设备路径> [-b 波特率] 指定完整设备路径\n", prog);
printf(" %s 默认使用 /dev/ttyHS5 @ 115200\n\n", prog);
printf("示例:\n");
printf(" %s -n 3 使用 /dev/ttyHS3 @ 115200\n", prog);
printf(" %s -n 5 -b 9600 使用 /dev/ttyHS5 @ 9600\n", prog);
printf(" %s -d /dev/ttyUSB0 使用 /dev/ttyUSB0 @ 115200\n", prog);
}
int main(int argc, char* argv[]) {
char port_buf[64];
const char* port = "/dev/ttyHS5";
int baudrate = 115200;
int opt;
while ((opt = getopt(argc, argv, "n:d:b:h")) != -1) {
switch (opt) {
case 'n':
snprintf(port_buf, sizeof(port_buf), "/dev/ttyHS%s", optarg);
port = port_buf;
break;
case 'd':
port = optarg;
break;
case 'b':
baudrate = atoi(optarg);
break;
case 'h':
default:
print_usage(argv[0]);
return (opt == 'h') ? 0 : 1;
}
}
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
printf("UART回环测试程序\n");
printf("使用串口: %s\n", port);
printf("波特率: %d\n", baudrate);
printf("=====================================\n");
return serial_loopback_test(port, baudrate);
}
```
**创建完成之后将其编译成可执行文件:**
```plaintext
mkdir -p out #创建输出目录
aarch64-linux-gnu-gcc uart_loopback.c -O2 -Wall -Wextra -o out/uart_loopback #编译成可执行文件
```
将编译好的文件从Linux环境copy到adb shell同级目录下
**执行步骤:**
```bash
adb push uart_loopback /tmp/uart_loopback
adb shell chmod +x /tmp/uart_loopback
adb shell
/tmp/uart_loopback /dev/ttyHS1
```
**测试结果:**
当pin8和pin10正确短接时,程序会持续发送数据并验证接收的数据是否一致:
```plaintext
UART回环测试程序
使用串口: /dev/ttyHS1
波特率: 115200
=====================================
串口 /dev/ttyHS1 已打开,开始回环测试(按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占空比。
| **
CPU温度
** | **
占空比/255
** |
| --- | --- |
|
<30℃
|
0
|
|
30~40℃
|
64
|
|
40~50℃
|
128
|
|
50~60℃
|
192
|
|
>60℃
|
255
|
## 接线图
此次采用 [树莓派风扇](),根据图示接到对应的M1/L1 40Pin引脚上。
| **风扇引脚** | **M1开发板引脚(40PIN)** |
| --- | --- |
| 红线(电源正极) | PIN2 |
| 黑线(电源负极) | PIN6 |
| 蓝线(PWM) | PIN33 |
| 黄线(转速检测) | 不接 |
```{image} images/image_RAZebJLZgohgB8x9B8xcer5mnHe.webp
:width: 756px
:height: 587px
:align: center
```
## 测试流程
1. 默认风扇功能是关闭的,开机后输入对应指令查看风扇服务的状态。
```plaintext
adb shell
40pin-ctrl fan status #查看当前风扇服务的状态,未启用风扇服务为bound,启用为enable
40pin-ctrl fan enable #使能风扇服务,使能后可以查看下状态是否为enable
40pin-ctrl fan disable #关闭风扇服务,停止测试才需要输入该指令
```
2. 在第一个终端里面输入。
```cpp
40pin-ctrl fan enable
40pin-ctrl fan status
#M1
watch -n 1 'PWM=/sys/bus/platform/devices/1c40000.qcom,spmi:qcom,pm6125@1:qcom,pwms@b300/pwm/pwmchip0/pwm0; p=$(cat "$PWM/period"); d=$(cat "$PWM/duty_cycle"); echo $(( (d * 255 + p / 2) / p ))'
#L1
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 ))'
```
查看当前的温度和PWM占空比是不是对得上。
3. 开第二个终端,做升温。
```plaintext
for i in 1 2 3 4; do
yes > /dev/null &
done
```
4. 如果第3步里面的升温不够可以把负载调大。
```plaintext
for i in $(seq 1 $(nproc)); do
yes > /dev/null &
done
```
看两个终端里面的温度和风扇的PWM占空比是不是和表里面的对齐。
```{image} images/image_Yt2NbyJMRo8Jj9xPWddcSH2mnoc.webp
:width: 844px
:height: 395px
:align: center
```
5. 测试结束后停止负载。
```plaintext
killall yes
```