STM32蓝牙遥控小车项目实战:硬件选型、代码实现与调试全解析

STM32蓝牙遥控小车项目实战:硬件选型、代码实现与调试全解析
1. 项目概述从零到一的蓝牙遥控小车最近整理硬盘翻出来一个几年前带学生做的STM32蓝牙遥控小车项目。当时为了教学演示从硬件选型、电路设计、代码编写到最后的调试优化每一步都踩过不少坑也积累了一整套完整的资料。这个项目麻雀虽小五脏俱全涵盖了嵌入式开发中单片机控制、外设驱动、无线通信、电机控制、电源管理等多个核心知识点非常适合初学者作为第一个综合项目来练手也适合有一定基础的开发者作为代码框架参考。今天就把这个项目的完整思路、关键代码、硬件连接图以及所有调试心得打包分享出来目标是让你拿到资料后能快速复现一辆属于自己的、稳定可靠的蓝牙遥控小车。这辆小车的核心控制芯片是STM32F103C8T6也就是常说的“蓝桥杯”或“最小系统板”核心成本低、资源足。通过手机APP或电脑串口助手发送指令指令经由HC-05或HC-06蓝牙模块传输给STM32STM32解析指令后通过L298N电机驱动模块控制两个直流电机的正反转和PWM调速从而实现小车的前进、后退、左转、右转、停止以及变速功能。整个项目不涉及复杂的算法和操作系统重点在于打通“感知-决策-控制”的完整链路理解嵌入式系统如何与外部世界交互。2. 硬件系统设计与核心器件选型硬件是整个项目的骨架选型合理与否直接决定了项目的稳定性、成本和开发难度。我们的设计原则是在满足功能的前提下优先选择成熟、通用、文档丰富的模块以降低学习和调试门槛。2.1 主控芯片为什么是STM32F103C8T6选择STM32F103C8T6以下简称F103几乎是入门STM32的必然选择。它基于ARM Cortex-M3内核主频72MHz拥有64KB Flash和20KB RAM对于小车控制来说绰绰有余。更重要的是它的生态极其成熟Keil MDK、STM32CubeMX等开发工具支持完善网络上资料、例程、问题解答浩如烟海其外设资源如多个定时器用于PWM生成、USART用于蓝牙通信、GPIO等完全契合本项目需求。注意市面上有些F103核心板标注的Flash可能是128KB但实际只有64KB后者是C8T6的真实容量。在编写程序时如果代码量较大需要注意不要超过64KB否则可能无法正常烧录或运行。我遇到过学生代码编译后超过64KB下载失败排查了半天才发现是芯片型号选错了。2.2 无线通信模块HC-05与HC-06的抉择蓝牙模块负责无线指令的收发。HC-05和HC-06是最常见的两款价格都在十元左右。HC-05支持主从一体模式。既可以作为主机主动搜索并连接其他蓝牙设备也可以作为从机被连接。功能更强大但AT指令集稍复杂。HC-06仅支持从机模式。只能等待手机等主机设备来连接它。AT指令集简单对于只需要接收指令的小车项目来说完全够用。我们的选择是HC-06。理由很简单小车只需要被动接收手机APP发来的指令不需要主动去连接其他设备。HC-06配置更简单出错的概率更低。购买时务必确认模块的通信电平是3.3V与STM32 GPIO电平兼容如果是5V电平的模块需要加电平转换电路否则可能损坏STM32芯片。2.3 电机驱动模块L298N的经典与替代方案驱动直流电机需要较大的电流单片机GPIO无法直接提供因此必须使用电机驱动模块。L298N是一款双H桥驱动芯片可以同时驱动两个直流电机实现正反转和PWM调速经典且皮实。接线要点电源隔离L298N的驱动电源VCC最好单独供电如7-12V的电池不要与STM32的逻辑电源5V或3.3V混用否则电机启动时的电流冲击可能导致单片机复位。L298N上有一个5V输出引脚可以反过来给STM32板子供电但前提是你的驱动电源电压不要太高建议不超过12V且要做好滤波。控制信号IN1、IN2控制一个电机的方向ENA接入PWM信号控制其速度。另一个电机对应IN3、IN4和ENB。我们将IN1/2/3/4连接到STM32的普通GPIO将ENA/ENB连接到STM32的定时器PWM输出引脚。替代方案如果觉得L298N体积大、发热明显可以考虑使用TB6612FNG驱动芯片。它的效率更高体积更小但价格稍贵引脚定义需要重新适配。2.4 电源系统稳定供电是基石小车的“心脏病”多源于电源。我们采用两套供电方案电机驱动电源使用一块7.4V2S的锂电池组直接接入L298N的VCC和GND。这块电池负责提供电机运转所需的大电流。控制系统电源从L298N的5V输出引脚取电连接到STM32核心板的5V输入引脚。这样只要电机电池有电整个控制系统就有电。务必确保L298N的5V输出使能跳线帽插上。实操心得强烈建议在STM32的电源入口处增加一个100μF的电解电容和一个0.1μF的瓷片电容用于滤除电源噪声。电机启停时会在电源线上产生很大的电压毛刺这个简单的滤波电路能极大提高系统稳定性避免单片机莫名其妙地重启。3. 电路连接与PCB布局要点有了器件下一步就是按照“最小系统”的原则把它们连接起来。下面给出核心的连接表并附上关键原理说明。STM32F103C8T6 核心连接表STM32引脚连接目标功能说明备注PA9 (USART1_TX)HC-06 RXD串口发送数据到蓝牙模块需串联1k电阻限流PA10 (USART1_RX)HC-06 TXD串口接收蓝牙模块数据直接连接PB6 (TIM4_CH1)L298N ENA生成PWM控制电机A速度必须为定时器PWM输出引脚PB7 (TIM4_CH2)L298N ENB生成PWM控制电机B速度必须为定时器PWM输出引脚PB8L298N IN1控制电机A方向高电平/低电平PB9L298N IN2控制电机A方向与IN1电平相反PB10L298N IN3控制电机B方向高电平/低电平PB11L298N IN4控制电机B方向与IN3电平相反5VL298N 5V Out系统电源输入注意电源极性GNDL298N GND共地至关重要所有模块GND必须相连接线详解与避坑指南串口连接STM32的TX应接蓝牙模块的RXRX接蓝牙模块的TX。这是一个常见的错误点。HC-06的RXD引脚逻辑电平是3.3V虽然STM32的PA9输出也是3.3V但为防止意外我习惯在TX线上串联一个1kΩ的电阻起到限流保护作用。PWM引脚选择不是所有GPIO都能方便地输出PWM。需要查阅STM32的数据手册或引脚复用功能表找到哪些引脚对应哪个定时器的哪个通道。我们选择TIM4的CH1和CH2对应PB6和PB7。在代码中需要正确配置定时器和GPIO的复用功能。共地共地共地这是所有电子项目中最重要也最容易被忽视的一点。STM32、L298N、HC-06、电池的负极GND必须全部连接在一起形成一个统一的参考零电位。否则通信会乱码控制会失灵。建议使用面包板或PCB的电源地线层来保证良好的共地。关于PCB如果想让小车更紧凑可靠可以设计一块简单的PCB。布局时要注意电源路径尽量宽电机驱动部分的走线要粗以减少电阻和压降。数字模拟分离电机驱动大电流模拟区和单片机控制数字区在布局上尽量远离地线可以在一点连接单点接地避免噪声串扰。添加滤波电容在每一个芯片的电源引脚附近放置一个0.1μF的退耦电容最近距离放置。4. 软件架构与关键代码解析软件部分我们采用模块化编程将不同功能封装成独立的.c和.h文件结构清晰便于调试和复用。整个工程主要包含以下几个模块系统初始化、蓝牙串口通信、电机PWM驱动、指令解析与控制逻辑。4.1 开发环境搭建与工程创建我们使用STM32CubeMX进行图形化初始化和Keil MDK-ARM进行代码编写和调试。这是目前最主流的STM32开发组合。使用CubeMX初始化选择芯片型号STM32F103C8Tx。SYSDebug选择Serial Wire这是ST-LINK下载调试接口。RCCHSE选择Crystal/Ceramic Resonator如果你的核心板有外部8MHz晶振。USART1模式选择Asynchronous参数默认115200波特率8位数据无校验1停止位。这需要与蓝牙模块的波特率匹配HC-06默认通常是9600或115200建议统一为115200。TIM4选择时钟源为Internal Clock通道1和通道2选择PWM Generation CH1/CH2。在下方参数设置中设置Prescaler预分频器和Counter Period自动重装载值ARR以产生特定频率的PWM。例如系统时钟72MHz预分频器设为71ARR设为999则PWM频率为72MHz / (711) / (9991) 1kHz。这是一个常用的电机PWM频率。GPIO将PB8、PB9、PB10、PB11设置为GPIO_Output。最后生成代码选择MDK-ARM V5工具链。工程结构Project/ ├── Core/ │ ├── Inc/ 头文件 │ │ ├── motor.h │ │ ├── bluetooth.h │ │ └── command.h │ └── Src/ 源文件 │ ├── motor.c │ ├── bluetooth.c │ └── command.c ├── Drivers/ ├── MDK-ARM/ └── .mxproject4.2 电机驱动模块实现motor.c和motor.h负责封装所有与电机控制相关的函数。关键代码PWM初始化与速度控制// motor.h #ifndef __MOTOR_H #define __MOTOR_H #include main.h // 电机编号定义 typedef enum { MOTOR_A 0, MOTOR_B } Motor_TypeDef; // 电机方向定义 typedef enum { MOTOR_STOP 0, MOTOR_FORWARD, MOTOR_BACKWARD } Motor_Dir_TypeDef; void Motor_Init(void); // 初始化GPIO和PWM void Motor_SetSpeed(Motor_TypeDef motor, uint16_t speed); // 设置PWM占空比 (0-1000) void Motor_Run(Motor_TypeDef motor, Motor_Dir_TypeDef dir); // 控制电机启停和方向 #endif// motor.c #include motor.h #include tim.h // CubeMX生成的定时器句柄 // 控制引脚定义根据你的实际接线修改 #define MOTOR_A_IN1_GPIO_Port GPIOB #define MOTOR_A_IN1_Pin GPIO_PIN_8 // ... 定义IN2, IN3, IN4 // PWM句柄CubeMX生成 extern TIM_HandleTypeDef htim4; void Motor_Init(void) { // GPIO初始化代码已由CubeMX生成此处通常无需额外操作 // 启动PWM输出 HAL_TIM_PWM_Start(htim4, TIM_CHANNEL_1); // ENA HAL_TIM_PWM_Start(htim4, TIM_CHANNEL_2); // ENB // 初始状态停止 Motor_Run(MOTOR_A, MOTOR_STOP); Motor_Run(MOTOR_B, MOTOR_STOP); } void Motor_SetSpeed(Motor_TypeDef motor, uint16_t speed) { // 限制速度值在0-1000之间对应ARR999 if(speed 1000) speed 1000; switch(motor) { case MOTOR_A: __HAL_TIM_SET_COMPARE(htim4, TIM_CHANNEL_1, speed); break; case MOTOR_B: __HAL_TIM_SET_COMPARE(htim4, TIM_CHANNEL_2, speed); break; default: break; } } void Motor_Run(Motor_TypeDef motor, Motor_Dir_TypeDef dir) { switch(motor) { case MOTOR_A: switch(dir) { case MOTOR_FORWARD: HAL_GPIO_WritePin(MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, GPIO_PIN_RESET); break; case MOTOR_BACKWARD: HAL_GPIO_WritePin(MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, GPIO_PIN_SET); break; case MOTOR_STOP: default: HAL_GPIO_WritePin(MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, GPIO_PIN_RESET); break; } break; case MOTOR_B: // 类似地控制IN3和IN4... break; } }提示__HAL_TIM_SET_COMPARE是HAL库提供的宏用于快速设置定时器通道的比较寄存器值即PWM脉冲宽度。速度值speed越大占空比越高电机转速越快。4.3 蓝牙通信与指令解析模块bluetooth.c负责通过串口接收数据command.c负责解析接收到的字符串指令并调用相应的电机控制函数。关键代码串口接收与指令解析我们采用中断方式接收串口数据将接收到的字符存入缓冲区当检测到行结束符如\n或\r时认为一条指令接收完成然后进行解析。// bluetooth.h #define BLUETOOTH_RX_BUFFER_SIZE 64 extern uint8_t bluetooth_rx_buffer[BLUETOOTH_RX_BUFFER_SIZE]; extern volatile uint16_t bluetooth_rx_index; extern volatile uint8_t bluetooth_rx_complete_flag; void Bluetooth_Init(void); void Bluetooth_SendString(char *str);// bluetooth.c #include bluetooth.h #include usart.h uint8_t bluetooth_rx_buffer[BLUETOOTH_RX_BUFFER_SIZE]; volatile uint16_t bluetooth_rx_index 0; volatile uint8_t bluetooth_rx_complete_flag 0; // 重写HAL库的串口接收回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { uint8_t received_char; // 从串口数据寄存器读取一个字节此部分HAL库已处理我们通常在中断服务程序中直接处理 // 实际项目中我们通常在main.c中调用HAL_UART_Receive_IT并在回调中处理数据 } } // 更实用的做法在main.c的while(1)循环中使用HAL_UART_Receive进行轮询或中断接收 // 以下是一个简化的中断接收处理思路需在main初始化后启动一次中断接收 // HAL_UART_Receive_IT(huart1, rx_byte, 1); // 然后在回调函数中 // void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // if(huart-Instance USART1) { // if(rx_byte ! \n bluetooth_rx_index BLUETOOTH_RX_BUFFER_SIZE-1) { // bluetooth_rx_buffer[bluetooth_rx_index] rx_byte; // } else { // bluetooth_rx_buffer[bluetooth_rx_index] \0; // 字符串结束符 // bluetooth_rx_complete_flag 1; // bluetooth_rx_index 0; // } // HAL_UART_Receive_IT(huart1, rx_byte, 1); // 重新开启接收中断 // } // }// command.c #include command.h #include motor.h #include string.h // 使用strcmp等函数 void Command_ParseAndExecute(char *cmd) { if(strcmp(cmd, FORWARD) 0) { Motor_Run(MOTOR_A, MOTOR_FORWARD); Motor_Run(MOTOR_B, MOTOR_FORWARD); Motor_SetSpeed(MOTOR_A, 700); // 设置速度值例如700/1000 Motor_SetSpeed(MOTOR_B, 700); } else if(strcmp(cmd, BACK) 0) { Motor_Run(MOTOR_A, MOTOR_BACKWARD); Motor_Run(MOTOR_B, MOTOR_BACKWARD); Motor_SetSpeed(MOTOR_A, 700); Motor_SetSpeed(MOTOR_B, 700); } else if(strcmp(cmd, LEFT) 0) { // 左转右轮前进左轮后退或停止 Motor_Run(MOTOR_A, MOTOR_BACKWARD); // 假设MOTOR_A是左轮 Motor_Run(MOTOR_B, MOTOR_FORWARD); // MOTOR_B是右轮 Motor_SetSpeed(MOTOR_A, 500); Motor_SetSpeed(MOTOR_B, 500); } else if(strcmp(cmd, RIGHT) 0) { // 右转左轮前进右轮后退或停止 Motor_Run(MOTOR_A, MOTOR_FORWARD); Motor_Run(MOTOR_B, MOTOR_BACKWARD); Motor_SetSpeed(MOTOR_A, 500); Motor_SetSpeed(MOTOR_B, 500); } else if(strcmp(cmd, STOP) 0) { Motor_Run(MOTOR_A, MOTOR_STOP); Motor_Run(MOTOR_B, MOTOR_STOP); } else if(strncmp(cmd, SPEED:, 6) 0) { // 解析速度指令如 SPEED:800 uint16_t speed atoi(cmd 6); Motor_SetSpeed(MOTOR_A, speed); Motor_SetSpeed(MOTOR_B, speed); } // 可以添加更多指令... }主循环逻辑main.c中的关键部分// main.c #include bluetooth.h #include command.h uint8_t rx_byte; // 用于中断接收的变量 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_TIM4_Init(); Motor_Init(); // 启动串口接收中断每次接收1个字节 HAL_UART_Receive_IT(huart1, rx_byte, 1); while (1) { // 检查指令接收完成标志 if(bluetooth_rx_complete_flag) { bluetooth_rx_complete_flag 0; // 解析并执行指令 Command_ParseAndExecute((char*)bluetooth_rx_buffer); } // 这里可以添加其他任务如传感器读取 HAL_Delay(10); // 短暂延时防止CPU空转过热 } } // 串口接收中断回调函数放在main.c末尾或单独文件 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { static uint16_t index 0; if(rx_byte ! \n index BLUETOOTH_RX_BUFFER_SIZE-1) { bluetooth_rx_buffer[index] rx_byte; } else { bluetooth_rx_buffer[index] \0; bluetooth_rx_complete_flag 1; index 0; } // 重新启动中断接收等待下一个字节 HAL_UART_Receive_IT(huart1, rx_byte, 1); } }5. 手机APP设计与上下位机通信协议为了让小车动起来我们需要一个发送指令的终端。最简单的方法是使用手机上的蓝牙串口APP如“蓝牙串口助手”、“Serial Bluetooth Terminal”等。这些APP可以连接HC-06模块并发送字符串。自定义简单通信协议 为了控制更灵活我们设计一个简单的文本协议。手机APP发送特定的字符串STM32解析后执行对应动作。F: 前进B: 后退L: 左转R: 右转S: 停止0~9: 速度等级0最慢9最快APP端可以做成滑块或按钮。在STM32的Command_ParseAndExecute函数中相应地修改判断条件即可。例如if(cmd[0] F) { // 单字符指令 // 前进操作 Motor_SetSpeed(MOTOR_A, speed_level * 100); // speed_level 由0-9映射到PWM值 }进阶自己开发简易APP 如果你会一点Android开发可以使用Android Studio和蓝牙API开发一个专属遥控器界面。核心步骤是获取蓝牙适配器搜索设备。配对并连接名为“HC-06”的设备HC-06的默认名称。通过蓝牙Socket获取输入输出流。在按钮点击事件中向输出流写入对应的指令字符串如F\n。这样体验更好界面可以自定义油门、方向盘等控件。6. 系统调试与问题排查实录调试是项目开发中耗时最长的部分。下面记录几个典型问题及其解决方法。6.1 蓝牙模块无法连接或通信乱码现象手机搜不到蓝牙模块或连接后发送指令小车无反应串口助手显示乱码。排查步骤供电检查首先用万用表测量蓝牙模块VCC电压是否为3.3V或5V视模块而定。电压不足会导致模块无法正常工作。接线检查确认TX、RX是否交叉连接GND是否共地。波特率匹配这是最常见的问题。HC-06默认波特率可能是9600或115200。使用USB转TTL模块将蓝牙模块的TX接USB转TTL的RX然后打开串口助手尝试不同的波特率给蓝牙模块通电看是否能收到“OK”或类似就绪信息。也可以在STM32代码中将串口初始化波特率改为9600试试。模块状态灯正常未连接时LED慢闪连接后LED快闪或常亮。根据灯的状态可以初步判断。AT指令测试有些模块需要进入AT模式设置参数如名称、配对码。具体操作是在模块未连接状态下按住模块上的按键如果有再上电或将其KEY引脚接高电平再上电此时LED慢闪进入AT模式波特率固定为38400用串口助手发送AT回车换行应返回OK。6.2 电机不转或只单向转动现象上电后电机不转或只能一个方向转PWM调速无效。排查步骤电源功率首先检查电机驱动电源电池电压是否足够电量是否充足。电机启动瞬间电流很大劣质电池或容量太小的电池会瞬间被拉低电压导致系统重启。可以尝试单独用稳压电源给驱动部分供电测试。L298N使能检查L298N的ENA和ENB跳线帽是否拔掉如果跳线帽插着ENA/ENB引脚的电平控制无效电机会全速转动。我们的方案是通过PWM控制所以跳线帽需要拔掉。控制信号电平用万用表或逻辑分析仪测量STM32输出到IN1/2/3/4和ENA/ENB的引脚电平。在发送控制指令时电平是否按预期变化如果ENA/ENB没有PWM波形检查定时器PWM输出配置是否正确是否调用了HAL_TIM_PWM_Start。电机接线交换电机的两根线可以测试电机本身是否完好。软件逻辑检查Motor_Run函数中的GPIO控制逻辑是否正确。例如前进时是否是一个GPIO置高另一个置低停止时是否两个都置低6.3 小车运行不稳定时好时坏现象小车偶尔不受控制或突然停止单片机有时会重启。排查步骤电源干扰这是首要怀疑对象。确保按照前面所述在STM32的电源入口处增加了滤波电容。电机电源线和控制板电源线尽量分开走。机械结构检查车轮是否安装牢固底盘是否平衡电机轴与轮子连接是否打滑。机械卡顿会导致电机堵转电流急剧上升可能触发驱动模块保护或拉低电压。程序逻辑在控制逻辑中加入“看门狗”IWDG。STM32内置独立看门狗当程序跑飞或陷入死循环时看门狗超时复位单片机。在CubeMX中启用IWDG并在主循环中定时“喂狗”。指令缓冲溢出如果蓝牙指令发送过快而STM32解析速度慢可能导致串口接收缓冲区溢出。可以增加缓冲区大小或简化指令解析逻辑。在串口接收中断中加入缓冲区满的判断防止写越界。6.4 常见问题速查表问题现象可能原因解决方案手机搜不到蓝牙模块未供电模块损坏手机蓝牙问题检查模块VCC/GND用其他手机测试模块接USB-TTL测试AT指令蓝牙已连接但无指令响应串口线接反波特率不匹配代码未处理接收数据检查TX/RX交叉统一波特率常用115200检查串口中断是否开启缓冲区是否正常电机完全不转电机电源未接L298N使能跳线帽未拔控制线未接检查电机电源电压拔掉ENA/ENB跳线帽用万用表测控制引脚电平电机只朝一个方向转控制方向的一根信号线断路或程序逻辑错误检查IN1/IN2或IN3/IN4的电平是否一高一低调试Motor_Run函数PWM调速无效PWM未输出ENA/ENB跳线帽未拔PWM频率不对用示波器或LED测PWM引脚拔掉跳线帽检查CubeMX中定时器PWM配置小车跑起来一卡一卡电源容量不足机械阻力大程序中有大延时更换大容量电池润滑机械结构检查主循环中是否有HAL_Delay(500)这类长延时单片机频繁重启电机启动电流冲击电源纹波大程序跑飞加强电源滤波电容电机电源与控制电源分离启用看门狗7. 项目优化与功能扩展思路基础功能实现后你可以在此基础上添加更多功能让小车变得更智能。增加超声波避障添加HC-SR04超声波模块测量前方障碍物距离。当距离小于阈值时自动停车或转向。这需要学习定时器输入捕获功能来测量高电平时间。增加红外巡线使用TCRT5000等红外传感器让小车沿着黑色轨迹线自动行驶。这需要学习ADC采集或数字IO读取实现PID控制算法则更高级。改用WiFi或无线串口控制将HC-06换成ESP8266或ESP32模块实现通过WiFi在网页或手机APP上控制控制距离更远且可传输视频流需额外摄像头。增加姿态传感器接入MPU6050陀螺仪和加速度计可以实现手势控制通过手机APP发送传感器数据或小车自身姿态监测。升级电机与驱动使用带编码器的直流电机减速电机搭配TB6612驱动可以实现精确的转速测量和闭环控制让小车速度更稳定走直线更直。设计更优雅的控制协议使用二进制协议或更紧凑的文本协议如F,800表示前进速度800减少数据传输量提高响应速度。这个项目的所有源码、原理图、PCB文件如有、手机APP源码以及详细的文档我已经整理打包。在复现过程中最关键的是理解每个模块的工作原理和它们之间如何协同而不是简单地照搬连线图和代码。遇到问题时善用调试工具万用表、逻辑分析仪、串口打印和网络搜索大部分坑前人都踩过。