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