
利用 STM32 复刻电赛 H 题F103C8T6 MPU6050 五路灰度传感器双环控制实战最近在整理电赛备战笔记时不少同学问我基于 STM32F103C8T6 做电机平衡与巡线小车角度环和位置环到底怎么配合MPU6050 姿态数据怎么滤波五路灰度传感器的阈值怎么定与其一个个回复不如把整个复刻过程整理成一篇完整教程。本文围绕电赛 24 年 H 题的方向使用 STM32F103C8T6 作为主控搭配 MPU6050 六轴传感器和五路灰度传感器阵列实现基于角度环与位置环的闭环控制。内容包括硬件选型、姿态解算、传感器逻辑、双环 PID 调参、完整代码示例以及调试过程中常见的坑适合准备电赛、做课程设计和入门平衡车/循迹车的开发者参考。1. 项目背景与核心概念1.1 电赛 H 题方向到底是什么电赛控制类题目历来是参赛人数最多的方向之一。H 题这类题目通常要求小车在特定赛道上完成寻迹、平衡或定点运动涉及传感器融合、电机驱动、闭环控制等多个知识点。复刻这套题目的目的不是背题而是掌握一套通用的控制框架。只要理解了角度环和位置环的组合方式后面做自平衡车、倒立摆、双轮差速小车都能复用这套思路。1.2 什么是角度环和位置环在控制系统中环Loop指的是反馈回路。角度环以内环形式存在控制对象是小车车身倾角。通过 MPU6050 获取当前角度与目标角度通常是 0° 或设定平衡角比较输出电机控制量让车身保持直立平衡。位置环以外环形式存在控制对象是小车在赛道上的位置偏移。通过五路灰度传感器检测当前偏离赛道中心的程度输出目标速度或目标倾角修正量让小车沿赛道行驶。两个环的配合逻辑是位置环输出角度环的目标值角度环再控制电机快速响应。这样既保证小车“站得稳”又保证小车“走得正”。1.3 为什么选 STM32F103C8T6 MPU6050 五路灰度STM32F103C8T6 是性价比极高的入门级 ARM 单片机72MHz 主频、64KB Flash、20KB RAM对于双环 PID 和小型控制任务完全够用。MPU6050 集成了三轴陀螺仪和三轴加速度计是姿态解算的经典方案。五路灰度传感器则用于检测地面黑线输出数字或模拟信号比单路传感器更稳定也方便做位置环控制。2. 系统架构与硬件准备2.1 整体系统框图整个系统的数据流可以简化为MPU6050角度 → STM32F103C8T6 → PID 角度环 → 电机 PWM 输出 五路灰度位置 → STM32F103C8T6 → PID 位置环 → 修正角度目标值简单来说位置环是慢速外环角度环是快速内环。控制频率上角度环可以做到 1kHz 左右位置环 100Hz 左右即可。2.2 硬件清单模块型号/规格数量说明主控STM32F103C8T6 最小系统板1蓝板/黑板均可姿态传感器MPU6050 模块带稳压1I2C 接口灰度传感器五路数字/模拟灰度模块1建议独立供电电机驱动TB6612 或 DRV88331驱动两个直流减速电机电机N20 或 TT 电机带编码器可选2输出轴带减速电池7.4V 或 11.1V 锂电池1根据电机额定电压选择稳压LM2596 或 MP1584 模块1降压到 5V/3.3V下载器ST-Link V2 或 J-Link1程序下载与调试如果条件允许电机建议选择带编码器的版本后续想加速度环或编码器测距会方便很多。2.3 引脚分配开发阶段建议把引脚直接写在代码注释里方便改线和排查。参考分配如下外设引脚说明MPU6050 SCLPB6I2C1 时钟MPU6050 SDAPB7I2C1 数据灰度模块 D1-D5PA0-PA4数字输出或模拟输入电机 PWM1PA8TIM1_CH1电机 PWM2PA9TIM1_CH2电机方向 AIN1/AIN2PB12/PB13左电机方向电机方向 BIN1/BIN2PB14/PB15右电机方向如果你用的灰度模块是模拟输出可以接在 ADC 引脚上通过 ADC 读取精确灰度值如果是数字输出直接配置 GPIO 输入读取高低电平。3. 环境准备与工程搭建3.1 开发环境本文示例基于以下环境操作系统Windows 10/11开发工具Keil MDK5或 STM32CubeIDE固件库标准外设库StdPeriph或 HAL 库本文以标准库为例下载器ST-Link V2如果你还没有搭建好 STM32 开发环境可以先完成 Keil MDK5 的安装再下载对应的 Device Pack。标准库工程也可以从网上找现成模板重点是确认工程能编译、能下载、能运行 LED 闪烁程序再继续后面的开发。3.2 新建工程要点使用标准库时以下文件是必不可少的stm32f10x.h芯片寄存器定义头文件system_stm32f10x.c系统时钟初始化stm32f10x_gpio.cGPIO 驱动stm32f10x_i2c.cI2C 驱动stm32f10x_tim.c定时器驱动stm32f10x_adc.cADC 驱动如果灰度传感器用模拟量misc.cNVIC 中断优先级配置新建工程后建议先写一个简单的 GPIO 翻转程序确认下载链路正常避免后面调试时分不清是硬件问题还是代码问题。4. MPU6050 姿态解算与数据读取4.1 MPU6050 内部结构MPU6050 内部集成三轴陀螺仪和三轴加速度计通过 I2C 接口输出原始数据。陀螺仪测量角速度单位通常为 dps度/秒通过积分可以得到角度。加速度计测量比力单位是 g可以用来计算倾斜角。单独使用陀螺仪积分会出现零漂累积的问题单独使用加速度计会因为振动产生噪声所以工程上常用互补滤波或 Mahony 算法进行融合。4.2 硬件连接与 I2C 初始化首先初始化 I2C1用于读取 MPU6050 寄存器。代码如下// 文件路径mpu6050.c #include mpu6050.h #include delay.h void MPU6050_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); I2C_InitStructure.I2C_Mode I2C_Mode_I2C; I2C_InitStructure.I2C_ClockSpeed 400000; I2C_InitStructure.I2C_DutyCycle I2C_DutyCycle_2; I2C_InitStructure.I2C_Ack I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_OwnAddress2 0x00; I2C_Init(I2C1, I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); // 唤醒 MPU6050 MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00); // 设置陀螺仪量程为 ±2000 dps MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 设置加速度计量程为 ±16g MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 关闭低通滤波或设置为默认 MPU6050_WriteReg(MPU6050_CONFIG, 0x00); }注意MPU6050 从机地址在 7 位地址模式下通常为0x68如果 AD0 引脚拉高则为0x69。读取时需要在寄存器地址前拼接写读位标准库封装函数中要处理这个问题。4.3 读取原始数据MPU6050 的加速度计和陀螺仪数据寄存器是连续的。加速度计从ACCEL_XOUT_H寄存器 0x3B开始占 6 字节陀螺仪从GYRO_XOUT_H寄存器 0x43开始占 6 字节。可以通过连续读取的方式一次性读出全部数据。// 文件路径mpu6050.c #define MPU6050_ADDR 0x68 #define MPU6050_ACCEL_XOUT_H 0x3B #define MPU6050_GYRO_XOUT_H 0x43 // 读取6字节原始数据 void MPU6050_ReadRaw(int16_t* accel, int16_t* gyro) { uint8_t buf[14]; I2C_ReadMultiBytes(MPU6050_ADDR, MPU6050_ACCEL_XOUT_H, buf, 14); accel[0] (int16_t)((buf[0] 8) | buf[1]); accel[1] (int16_t)((buf[2] 8) | buf[3]); accel[2] (int16_t)((buf[4] 8) | buf[5]); gyro[0] (int16_t)((buf[8] 8) | buf[9]); gyro[1] (int16_t)((buf[10] 8) | buf[11]); gyro[2] (int16_t)((buf[12] 8) | buf[13]); }4.4 互补滤波解算角度互补滤波的思路是陀螺仪积分得到的高频角度变化可信度高加速度计计算出的静态角度低频分量可信度高将两者按比例融合。计算公式如下angle 0.98 * (angle gyro_y * dt) 0.02 * accel_angle其中gyro_y * dt是角速度积分得到的角度增量accel_angle是由加速度计计算的横滚角。系数 0.98 和 0.02 可以微调实际调参时根据响应速度调整。// 文件路径mpu6050.c #define FILTER_COEF 0.98f #define RAD_TO_DEG 57.29578f // 以绕Y轴旋转为例适合小车前后倾斜的情况 float MPU6050_GetAngle(float* angle, float dt) { int16_t accel[3], gyro[3]; float accel_angle, gyro_drift; MPU6050_ReadRaw(accel, gyro); // 由加速度计计算角度atan2(ax, az) accel_angle atan2f(accel[0], accel[2]) * RAD_TO_DEG; // 陀螺仪Y轴角速度积分 gyro_drift gyro[1] / 16.4f; // 16.4 对应 ±2000dps 量程下的灵敏度 *angle FILTER_COEF * (*angle gyro_drift * dt) (1 - FILTER_COEF) * accel_angle; return *angle; }这里需要注意gyro[1] / 16.4f中的 16.4 是 ±2000dps 量程下的 LSB 灵敏度。如果初始化时设置的是 ±250dps则灵敏度是 131。4.5 使用 DMP 的说明很多开发者喜欢使用 MPU6050 内部 DMPDigital Motion Processor直接输出四元数再转化为欧拉角。DMP 的优势是减少了主控计算量姿态解算更平滑缺点是移植代码体积较大初始化流程复杂。在 F103C8T6 资源有限的情况下如果项目侧重点是学习和调参建议先用互补滤波跑通如果追求稳定性和后续扩展可以尝试移植 DMP 库。5. 五路灰度传感器原理与位置环输入5.1 灰度传感器工作原理灰度传感器的核心是一个红外发射管和一个光敏接收管。发射管发出红外光光线照射到地面后反射接收管根据反射光的强度产生不同的电压值。当传感器对着白色地面时反射光强输出电压高对着黑色线时反射光弱输出电压低。通过判断电压值就能知道当前传感器是否压在黑线上。5.2 五路传感器的布局与逻辑五路灰度传感器一般按直线排列编号从左到右为 1 到 5。当小车在赛道正中时中间第 3 路传感器在黑线上其余在白色区域。一种常见的判断方法是计算“误差值”误差 位置加权值 - 中间位置例如五个传感器按照从左边到右边分别赋予位置权重 -2、-1、0、1、2当只有中间传感器检测到黑线时误差为 0当黑线偏向左边第 2 个传感器时误差为 -1。// 文件路径grayscale.h #define GRAY_THRESHOLD 1500 // 根据实际环境调整 // 读取五路灰度传感器状态返回误差值 int Gray_GetError(void) { int pos 0; int cnt 0; if (ADC_GetValue(0) GRAY_THRESHOLD) { pos -2; cnt; } if (ADC_GetValue(1) GRAY_THRESHOLD) { pos -1; cnt; } if (ADC_GetValue(2) GRAY_THRESHOLD) { pos 0; cnt; } if (ADC_GetValue(3) GRAY_THRESHOLD) { pos 1; cnt; } if (ADC_GetValue(4) GRAY_THRESHOLD) { pos 2; cnt; } if (cnt 0) { return 0; // 丢失黑线返回 0 或进入丢线逻辑 } return pos * 100 / cnt; // 归一化便于PID处理 }如果使用数字输出的五路灰度模块代码更简单直接读 GPIO 电平即可。5.3 阈值标定方法灰度传感器的阈值不要拍脑袋定要在实际赛道上标定。方法如下将小车放在白色区域读取各路 ADC 值取平均记为white_value。将传感器对准黑色引导线读取 ADC 值取平均记为black_value。阈值取(white_value black_value) / 2。不同光照条件下白色和黑色的 ADC 值会有明显变化所以正式比赛前最好重新标定。6. 角度环与位置环 PID 控制实现6.1 PID 控制基础PID 控制器由比例、积分、微分三个环节组成。比例项让输出与误差成正比积分项消除稳态误差微分项抑制超调。在平衡控制中很多项目只用到 PD 控制因为积分项可能引入低频振荡。位置环则常用 PI 或 PD视赛道和速度而定。6.2 角度环内环角度环的目的让小车倾角快速回到目标角。这里的“目标角”不是固定 0°而是平衡点角度。因为机械结构差异每辆车都有一个实际的机械平衡角通常在 0° 附近需要实测。角度环输出的是电机 PWM 占空比修正值。控制周期建议使用定时器中断比如 1ms 调用一次。// 文件路径pid.c typedef struct { float Kp; float Ki; float Kd; float Target; float Error; float LastError; float Integral; float Output; } PID_TypeDef; void PID_Init(PID_TypeDef* pid, float kp, float ki, float kd, float target) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-Target target; pid-Error 0; pid-LastError 0; pid-Integral 0; pid-Output 0; } float PID_Calculate(PID_TypeDef* pid, float measure) { pid-Error pid-Target - measure; pid-Integral pid-Error; pid-Output pid-Kp * pid-Error pid-Ki * pid-Integral pid-Kd * (pid-Error - pid-LastError); pid-LastError pid-Error; return pid-Output; }在角度环中measure是 MPU6050 解算得到的倾角Target是机械平衡角Output映射到电机 PWM。6.3 位置环外环位置环的输入是灰度传感器误差输出是角度环目标角的修正量。假如小车偏左了位置环会让车身向右倾斜一点平衡控制自然会让小车向右移动。位置环控制周期可以比角度环慢比如 10ms 调用一次。// 文件路径control.c PID_TypeDef pid_angle; // 角度环 PID_TypeDef pid_position; // 位置环 #define ANGLE_TARGET -1.5f // 机械平衡角需要实测 #define SPEED_MAX 1000 // 角度环输出限幅 void Control_Init(void) { PID_Init(pid_angle, 180.0f, 0.0f, 3.0f, ANGLE_TARGET); PID_Init(pid_position, 0.5f, 0.0f, 0.1f, 0.0f); } void Control_Task_1ms(void) { // 读取 MPU6050 解算角度 float angle MPU6050_GetAngle(angle_buffer, 0.001f); // 角度环计算 float output PID_Calculate(pid_angle, angle); // 限幅 if (output SPEED_MAX) output SPEED_MAX; if (output -SPEED_MAX) output -SPEED_MAX; // 输出到电机 Motor_SetPWM(output); } void Control_Task_10ms(void) { int err Gray_GetError(); // 位置环输出修正角度 float angle_fix PID_Calculate(pid_position, err); pid_angle.Target ANGLE_TARGET angle_fix; }6.4 串级 PID 的调参顺序串级 PID 调参必须遵循“先内环后外环”的顺序否则会非常混乱。第一步调角度环。屏蔽位置环把角度环目标设为 0只保留角度环手动给一个小幅扰动观察小车回正速度和振荡情况。第二步调位置环。在角度环稳定的基础上加入位置环。先把 Kp 调小观察小车巡线响应再逐步加大。第三步微调两个环的 Kd。Kd 用于增加阻尼抑制振荡但过大的 Kd 会放大噪声。具体调参经验可以总结为一句口诀角度环 Kp 决定“站在哪”Kd 决定“怎么站稳”位置环 Kp 决定“回得快慢”Kd 决定“回得是否顺滑”。7. 电机驱动与 PWM 控制7.1 TIM1 输出 PWM直流减速电机一般用 PWM 调速方向由 GPIO 控制驱动芯片的 IN1/IN2。下面以 STM32F103 的 TIM1 为例初始化 PWM 输出// 文件路径motor.c void Motor_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // PA8 PA9 输出 PWM GPIO_InitStructure.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PB12-PB15 方向控制 GPIO_InitStructure.GPIO_Pin GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; TIM_TimeBaseStructure.TIM_Period 1000 - 1; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse 0; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC2Init(TIM1, TIM_OCInitStructure); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }上面配置的是 72MHz / 72 / 1000 1kHz 的 PWM 频率。一般直流电机 PWM 频率在 10kHz 到 20kHz 效果更好你可以把预分频改小、周期改大灵活调整。7.2 电机方向与死区控制电机时需要根据 PID 输出正负切换方向void Motor_SetPWM(int16_t speed) { if (speed 0) { // 正转 GPIO_SetBits(GPIOB, GPIO_Pin_12); GPIO_ResetBits(GPIOB, GPIO_Pin_13); TIM_SetCompare1(TIM1, speed); } else if (speed 0) { // 反转 GPIO_ResetBits(GPIOB, GPIO_Pin_12); GPIO_SetBits(GPIOB, GPIO_Pin_13); TIM_SetCompare1(TIM1, -speed); } else { // 停止 GPIO_ResetBits(GPIOB, GPIO_Pin_12); GPIO_ResetBits(GPIOB, GPIO_Pin_13); TIM_SetCompare1(TIM1, 0); } }这里需要特别注意如果 PWM 占空比很小电机因为摩擦和静摩擦无法转动此时电机处于“死区”。调试时可以先手动输出一个较大的 PWM 值确认电机能转起来再调 PID。8. 完整工程代码结构8.1 工程目录建议清晰的目录结构对后续调试和代码移植非常友好project/ ├── User/ │ ├── main.c │ ├── control.c │ ├── control.h │ ├── motor.c │ ├── motor.h │ ├── mpu6050.c │ ├── mpu6050.h │ ├── grayscale.c │ └── grayscale.h ├── HARDWARE/ │ ├── delay.c │ ├── delay.h │ ├── i2c.c │ └── i2c.h └── SYSTEM/ ├── stm32f10x_it.c └── stm32f10x_it.h上面的分层思路是底层驱动I2C、delay与业务逻辑control、grayscale分离这样更换硬件平台时只需要修改底层驱动业务逻辑可以最大程度复用。8.2 主循环与中断配合main 函数的逻辑很简单初始化硬件然后进入一个后台任务循环。核心控制放在定时器中断里执行因为 PID 对时间一致性要求高。// 文件路径main.c #include stm32f10x.h #include delay.h #include mpu6050.h #include motor.h #include control.h #include grayscale.h int main(void) { delay_init(); MPU6050_Init(); Motor_Init(); Gray_Init(); Control_Init(); while (1) { // 后台任务OLED显示、蓝牙通信、按键处理 // 控制任务由定时器中断驱动不要在循环里做阻塞 } }定时器中断服务函数示例// 文件路径stm32f10x_it.c void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); Control_Task_1ms(); count_10ms; if (count_10ms 10) { count_10ms 0; Control_Task_10ms(); } } }9. 调试过程与常见问题9.1 MPU6050 读不到数据现象I2C 通信超时读到的数据全为 0 或固定值。排查步骤检查接线SDA 和 SCL 是否接反。检查模块供电是否为 3.3V部分模块有板载稳压可以接 5V但需要确认电平转换。确认从机地址使用 0x68 还是 0x69。用示波器或逻辑分析仪查看 SCL/SDA 波形确认 I2C 时序。9.2 角度数据漂移严重原因陀螺仪零漂没有补偿或互补滤波系数不合适。解决上电后静置 2 秒采集陀螺仪零偏值在解算时减去零偏。适当增大互补滤波中加速度计的权重比如把系数从 0.98 调整到 0.95。确认模块安装牢固振动会直接影响加速度计数据。9.3 小车一上电就倒可能原因角度方向反了电机输出方向相反。可以先把 PID 输出取反测试。机械平衡角设置不对需要尝试在 -3° 到 3° 之间扫描。角度环 Kp 太小导致恢复力矩不足。9.4 位置环振荡位置环振荡通常表现为小车左右大幅度摆动。可以按顺序做如下调整降低位置环 Kp。增大位置环 Kd。检查灰度传感器阈值是否合适避免频繁丢失黑线。确认位置环执行周期是否稳定周期不稳定会导致 PID 参数漂移。9.5 调试建议汇总问题现象常见原因解决思路MPU6050 无数据接线错误或从机地址不对检查 I2C 引脚和 0x68/0x69角度漂移陀螺仪零偏未补偿上电静置采集零偏并减去小车往一侧加速倒下电机方向反了取反 PID 输出或交换电机接线小车持续抖动Kd 过小或角度滤波不够增大 Kd加强互补滤波巡线时冲出赛道位置环 Kp 过小增大位置环 Kp检查灰度阈值电机不转PWM 频率或方向引脚错误单独测试电机 PWM 输出10. 工程优化建议与扩展方向10.1 代码层面的优化使用volatile修饰中断和主循环共享的变量。PID 计算放在中断里不要在 main 循环里调用避免任务卡死导致控制周期漂移。对 PID 输出做限幅避免输出突变造成电机过流。积分项增加限幅防止积分饱和。10.2 硬件层面的优化MPU6050 和电机驱动尽量分开供电避免电机大电流导致传感器供电波动。传感器安装要牢靠紧固螺丝后可以使用少量热熔胶固定。小车重心越低越容易控制电池尽量放在底盘中间偏下位置。10.3 扩展方向基础版本跑通后可以继续扩展以下功能增加编码器速度环实现精确速度闭环适合做定点停车。使用 OLED 显示实时角度和 PID 输出现场调参会方便很多。用蓝牙模块无线调节 PID 参数不用反复插拔下载器。将互补滤波替换为 Mahony 或 DMP 方案对比姿态解算效果。如果赛道有十字路口或断线可以增加灰度传感器的状态机处理逻辑。11. 写在最后从硬件接线到双环闭环控制复刻电赛 H 题的过程本质上就是一次完整的嵌入式控制项目训练。建议不要着急直接抄代码而是先理解每个模块的职责再逐步把代码移植到自己的工程里。调 PID 是最耗时但也是最涨经验的部分多记录参数变化和现象慢慢就会有“手感”。如果这篇教程对你有帮助可以收藏备用。文中示例基于 STM32F103C8T6 和标准外设库如果你使用的是 HAL 库核心控制思路完全一致只需替换底层驱动接口。欢迎在评论区交流你的调试心得和踩坑经验。