尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32平衡小车实战:从PID控制到姿态解算的嵌入式开发指南

STM32平衡小车实战:从PID控制到姿态解算的嵌入式开发指南 在实际嵌入式开发项目中平衡小车是一个经典的综合性实践案例它融合了传感器数据采集、姿态解算、电机控制、PID算法和系统稳定性设计等多个核心知识点。很多开发者尤其是刚接触STM32或实时控制系统的初学者往往在硬件搭建后面对纷繁复杂的软件逻辑感到无从下手不知道如何将陀螺仪、加速度计的数据转化为稳定的控制信号更不清楚PID参数该如何调试。本文将以STM32微控制器为核心面向有一定C语言和单片机基础的开发者手把手带你完成平衡小车软件系统的构建。我们将从最基础的工程框架搭建开始逐步实现传感器数据读取、姿态融合以互补滤波为例、直立环PD控制、速度环PI控制以及最终的电机PWM输出。文章会详细解释每一段代码背后的物理意义和控制逻辑并提供可复现的代码片段、参数调试方法以及关键的排错思路。学习完成后你将能够理解双轮自平衡系统的基本控制原理并具备独立编写和调试一套简易平衡小车控制程序的能力。1. 理解平衡小车的核心控制原理在动手写代码之前必须建立清晰的物理模型和控制逻辑概念。平衡小车本质上是一个倒立摆系统其控制目标是让车体在竖直方向保持稳定。1.1 系统组成与控制闭环一个典型的STM32平衡小车软件系统包含以下几个关键部分它们共同构成了一个或多个控制闭环传感器模块通常采用MPU6050六轴传感器提供三轴加速度和三轴角速度数据。这是系统的“感官”用于感知车体的倾斜状态和旋转速度。姿态解算模块原始传感器数据存在噪声且不能直接反映角度。此模块通过算法如互补滤波、卡尔曼滤波融合加速度计和陀螺仪数据计算出相对准确的车体倾角Pitch和角速度。控制决策模块这是系统的大脑。它根据目标角度通常为0度即竖直、当前实际角度和角速度通过控制算法通常是PID计算出需要施加给电机的控制量。为了更好的性能通常会采用串级PID内环直立环快速响应角度变化外环速度环维持长期稳定。执行器模块通常由STM32的定时器产生PWM波驱动电机驱动板如TB6612、DRV8833来控制两个直流减速电机的正反转和转速从而产生使车体恢复平衡的力矩。反馈回路电机运动后车体姿态发生变化又被传感器捕捉到形成闭环。整个控制流程可以简化为传感器数据 - 姿态解算 - PID控制器 - PWM输出 - 电机运动 - 影响姿态 - 新的传感器数据。1.2 姿态解算从数据到角度MPU6050输出的加速度计数据可以用于计算倾角但动态情况下受线性加速度影响很大陀螺仪积分可以得到角度但存在零漂积分误差会随时间累积。因此需要融合两者。互补滤波是一种简单有效的融合算法其核心思想是在高频部分信任陀螺仪响应快在低频部分信任加速度计长期稳定。公式如下角度 α * (上一角度 陀螺仪角速度 * dt) (1 - α) * 加速度计角度其中α是一个介于0和1之间的滤波系数dt是采样周期。α越接近1越信任陀螺仪。1.3 控制算法PID与串级PID直立环角度环目标是让车体保持竖直。输入是目标角度0度和当前角度的偏差。使用PD比例-微分控制器。比例项P负责“扶正”车体偏差越大纠正力越大微分项D负责“抑制摆动”角速度越大抑制力越大防止小车来回振荡。直立环输出 Kp1 * 角度偏差 Kd1 * 角速度速度环位置环目标是让小车在平衡的同时能稳定在某个位置或者缓慢响应遥控指令。如果只有直立环小车为了平衡可能会朝一个方向持续加速。速度环通过测量电机编码器的脉冲数反映速度来调节。通常使用PI比例-积分控制器。积分项I可以消除静态误差使小车能真正停在某处。速度环输出 Kp2 * 速度偏差 Ki2 * 速度偏差积分串级控制速度环的输出作为直立环目标角度的微调量。即最终目标角度 0度原始平衡目标 速度环输出然后直立环根据这个最终目标角度与当前角度的偏差进行计算。这样当小车需要向前移动以平衡时速度环会给出一个微小的前倾目标直立环为了“扶正”这个前倾的车体就会驱动电机向前转从而实现移动中的平衡。2. 软件工程框架与准备工作在开始具体模块编码前一个清晰、模块化的工程结构至关重要。这能保证代码的可读性、可维护性并方便调试。2.1 开发环境与硬件准备主控STM32F103C8T6或其他系列如F4/F7性能更佳。开发环境Keil MDK-ARM 或 STM32CubeIDE。调试工具ST-LINK/V2 下载调试器。核心硬件MPU6050模块带或不带DMP。带编码器的直流减速电机编码器用于速度环反馈。电机驱动板如TB6612。锂电池及降压模块为STM32和驱动板供电。软件库通常需要HAL库或标准库。本文示例基于HAL库因其可移植性更好。2.2 工程模块划分建议在STM32CubeMX生成代码的基础上创建以下模块文件夹/Project /Core /Drivers /User /App balance_task.c/h // 主控制任务调度所有模块 pid.c/h // PID算法实现 /Bsp mpu6050.c/h // MPU6050驱动I2C encoder.c/h // 编码器读取定时器编码器模式 motor.c/h // 电机PWM控制定时器PWM输出 filter.c/h // 滤波算法互补滤波等 /Utils delay.c/h // 精确延时SysTick usart.c/h // 调试串口打印使用STM32CubeMX进行图形化配置是高效的第一步。主要配置项如下表外设模式参数说明用途SYSDebugSerial Wire启用SWD调试RCCHSECrystal/Ceramic Resonator使用外部高速时钟I2C1I2C标准模式 (100kHz) 或快速模式 (400kHz)连接MPU6050TIM1PWM Generation CH1, CH2, CH3, CH4预分频和周期决定PWM频率如72M/72/10001kHz生成4路PWM驱动两个电机TIM2Encoder Mode编码器接口模式上下沿计数读取电机编码器A、B相TIM3Encoder Mode同上读取另一个电机编码器TIM4Basic Timer预分频和周期决定中断频率如72M/7200/10010ms产生10ms定时中断作为控制周期USART1Asynchronous波特率115200调试信息输出配置完成后生成代码初始化工作就完成了大部分。3. 核心驱动模块实现控制逻辑建立在可靠的底层驱动之上。我们需要先确保能正确读取传感器数据和驱动电机。3.1 MPU6050驱动与数据读取MPU6050通过I2C通信。首先实现基本的读写函数。// mpu6050.c #include mpu6050.h #include i2c.h // HAL库I2C头文件 #define MPU6050_ADDR 0xD0 // 默认地址左移一位后为0xD0 #define SMPLRT_DIV_REG 0x19 #define PWR_MGMT_1_REG 0x6B #define ACCEL_CONFIG_REG 0x1C #define GYRO_CONFIG_REG 0x1B #define ACCEL_XOUT_H_REG 0x3B uint8_t MPU6050_Init(void) { uint8_t check; HAL_I2C_Mem_Read(hi2c1, MPU6050_ADDR, 0x75, 1, check, 1, 100); // 读取WHO_AM_I寄存器 if(check ! 0x68) return 1; // 器件ID错误 // 唤醒MPU6050设置时钟源 uint8_t data 0x00; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, PWR_MGMT_1_REG, 1, data, 1, 100); // 设置陀螺仪量程 ±2000°/s data 0x18; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, GYRO_CONFIG_REG, 1, data, 1, 100); // 设置加速度计量程 ±8g data 0x10; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, ACCEL_CONFIG_REG, 1, data, 1, 100); // 设置采样率分频器 (1kHz/(19)100Hz) data 0x09; HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, SMPLRT_DIV_REG, 1, data, 1, 100); return 0; } void MPU6050_Read_Accel(MPU6050_Data *mpu_data) { uint8_t buf[6]; HAL_I2C_Mem_Read(hi2c1, MPU6050_ADDR, ACCEL_XOUT_H_REG, 1, buf, 6, 100); mpu_data-accel_x (int16_t)(buf[0] 8 | buf[1]); mpu_data-accel_y (int16_t)(buf[2] 8 | buf[3]); mpu_data-accel_z (int16_t)(buf[4] 8 | buf[5]); } void MPU6050_Read_Gyro(MPU6050_Data *mpu_data) { uint8_t buf[6]; HAL_I2C_Mem_Read(hi2c1, MPU6050_ADDR, 0x43, 1, buf, 6, 100); // GYRO_XOUT_H mpu_data-gyro_x (int16_t)(buf[0] 8 | buf[1]); mpu_data-gyro_y (int16_t)(buf[2] 8 | buf[3]); mpu_data-gyro_z (int16_t)(buf[4] 8 | buf[5]); }注意原始数据是int16类型需要根据量程转换为物理量。例如加速度计量程为±8g时灵敏度为4096 LSB/g。所以accel_x_g accel_x_raw / 4096.0。陀螺仪量程为±2000°/s时灵敏度为16.4 LSB/(°/s)。所以gyro_x_dps gyro_x_raw / 16.4。3.2 编码器速度测量使用STM32定时器的编码器接口模式是最佳选择硬件自动计数不占用CPU。// encoder.c #include encoder.h // 定时器2和3已由CubeMX配置为编码器模式 int32_t Get_Encoder_CNT(TIM_HandleTypeDef *htim) { int32_t cnt; cnt (int32_t)(__HAL_TIM_GET_COUNTER(htim)); // 获取当前计数值 __HAL_TIM_SET_COUNTER(htim, 0); // 读取后清零方便下次计算增量 return cnt; } // 在定时中断中调用计算速度脉冲数/控制周期 int32_t g_left_speed, g_right_speed; void Encoder_Calculate_Speed(void) { static int32_t left_last 0, right_last 0; int32_t left_now Get_Encoder_CNT(htim2); int32_t right_now Get_Encoder_CNT(htim3); // 简单差分得到速度可加入低通滤波 g_left_speed left_now - left_last; g_right_speed right_now - right_last; left_last left_now; right_last right_now; }3.3 电机PWM驱动通过改变定时器通道的占空比来控制电机速度和方向。// motor.c #include motor.h // 电机PWM输出范围取决于定时器的自动重载值ARR #define PWM_MAX 1000 #define PWM_MIN -1000 void Motor_Set_PWM(int16_t left_pwm, int16_t right_pwm) { // 限幅 if(left_pwm PWM_MAX) left_pwm PWM_MAX; if(left_pwm -PWM_MAX) left_pwm -PWM_MAX; if(right_pwm PWM_MAX) right_pwm PWM_MAX; if(right_pwm -PWM_MAX) right_pwm -PWM_MAX; // 根据正负值控制两个电机的方向引脚和PWM占空比 // 假设驱动逻辑IN11, IN20 正转 IN10, IN21 反转 PWM控制速度 if(left_pwm 0) { HAL_GPIO_WritePin(MOTOR_LEFT_IN1_GPIO_Port, MOTOR_LEFT_IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(MOTOR_LEFT_IN2_GPIO_Port, MOTOR_LEFT_IN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, left_pwm); // CH1对应左电机PWM } else { HAL_GPIO_WritePin(MOTOR_LEFT_IN1_GPIO_Port, MOTOR_LEFT_IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_LEFT_IN2_GPIO_Port, MOTOR_LEFT_IN2_Pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, -left_pwm); } // 右电机同理使用CH2 if(right_pwm 0) { HAL_GPIO_WritePin(MOTOR_RIGHT_IN1_GPIO_Port, MOTOR_RIGHT_IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(MOTOR_RIGHT_IN2_GPIO_Port, MOTOR_RIGHT_IN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_2, right_pwm); } else { HAL_GPIO_WritePin(MOTOR_RIGHT_IN1_GPIO_Port, MOTOR_RIGHT_IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_RIGHT_IN2_GPIO_Port, MOTOR_RIGHT_IN2_Pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_2, -right_pwm); } }4. 姿态解算与控制算法实现底层驱动就绪后进入核心算法层。4.1 互补滤波姿态解算// filter.c #include filter.h #include math.h float Complementary_Filter(float angle_acc, float gyro, float *angle, float dt, float alpha) { // angle_acc: 由加速度计计算出的角度atan2(accY, accZ) // gyro: 陀螺仪角速度已转换为度/秒 // *angle: 上一周期的融合角度同时作为输出 // dt: 采样周期秒如0.01 // alpha: 滤波系数通常0.98-0.995 // 陀螺仪积分得到角度预测 float angle_gyro (*angle) gyro * dt; // 互补滤波融合 *angle alpha * angle_gyro (1 - alpha) * angle_acc; return *angle; } // 在应用层调用 MPU6050_Data mpu; float g_angle, g_gyro; // 全局变量存储最终角度和角速度 void Attitude_Update(float dt) { float acc_angle; // 1. 读取原始数据 MPU6050_Read_Accel(mpu); MPU6050_Read_Gyro(mpu); // 2. 转换为物理量示例需根据实际量程调整 float acc_x mpu.accel_x / 4096.0; float acc_y mpu.accel_y / 4096.0; float acc_z mpu.accel_z / 4096.0; float gyro_y mpu.gyro_y / 16.4; // 假设绕Y轴旋转是俯仰角 // 3. 由加速度计算角度弧度再转为度 acc_angle atan2(acc_x, acc_z) * 57.29578f; // 57.29578 180/PI // 4. 互补滤波 g_angle Complementary_Filter(acc_angle, gyro_y, g_angle, dt, 0.98); g_gyro gyro_y; // 记录角速度用于PD控制 }4.2 PID控制器实现实现一个通用的位置式PID控制器。// pid.c #include pid.h void PID_Init(PID_TypeDef *pid, float kp, float ki, float kd, float max_out, float max_iout) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-max_out max_out; pid-max_iout max_iout; pid-integral 0; pid-prev_error 0; } float PID_Calculate(PID_TypeDef *pid, float target, float current) { float error target - current; pid-integral error; // 积分限幅防止积分饱和 if(pid-integral pid-max_iout) pid-integral pid-max_iout; else if(pid-integral -pid-max_iout) pid-integral -pid-max_iout; float derivative error - pid-prev_error; pid-prev_error error; float output pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; // 输出限幅 if(output pid-max_out) output pid-max_out; else if(output -pid-max_out) output -pid-max_out; return output; }4.3 主控制任务调度将所有模块在定时中断中串联起来。假设我们使用TIM4产生10ms中断。// balance_task.c #include balance_task.h #include mpu6050.h #include filter.h #include encoder.h #include pid.h #include motor.h PID_TypeDef pid_angle; // 直立环PD控制器 PID_TypeDef pid_speed; // 速度环PI控制器 float target_angle 0.0; // 直立环目标角度 float target_speed 0.0; // 速度环目标速度通常为0或由遥控给定 float motor_out; // 最终输出给电机的PWM值 void Balance_Init(void) { MPU6050_Init(); // 初始化PID参数这些参数需要实际调试 PID_Init(pid_angle, 200.0, 0.0, 1.2, 1000, 1000); // 直立环P200, D1.2 PID_Init(pid_speed, 10.0, 0.5, 0.0, 100, 1000); // 速度环P10, I0.5 } void Balance_Task(void) { // 此函数在10ms定时中断中调用 // 1. 更新姿态 Attitude_Update(0.01); // dt 10ms 0.01s // 2. 更新编码器速度 Encoder_Calculate_Speed(); float current_speed (g_left_speed g_right_speed) / 2.0; // 取平均速度 // 3. 速度环PI控制计算为平衡速度所需的角度补偿量 float angle_adjust PID_Calculate(pid_speed, target_speed, current_speed); // 4. 直立环PD控制目标角度 平衡点 速度环补偿 float final_target_angle target_angle angle_adjust; motor_out PID_Calculate(pid_angle, final_target_angle, g_angle); // 5. 输出到电机左右电机输出相同用于平衡 Motor_Set_PWM((int16_t)motor_out, (int16_t)motor_out); }在stm32f1xx_it.c的中断服务函数中调用void TIM4_IRQHandler(void) { if(__HAL_TIM_GET_FLAG(htim4, TIM_FLAG_UPDATE) ! RESET) { __HAL_TIM_CLEAR_FLAG(htim4, TIM_FLAG_UPDATE); Balance_Task(); // 核心控制任务 } }5. 系统调试、参数整定与问题排查代码编写完成后距离小车成功站立还有最关键的一步调试。这是一个“理论-实践-观察-调整”的循环过程。5.1 调试准备与安全措施硬件保护调试时务必用东西撑住小车让轮子悬空。防止参数错误导致小车突然猛冲损坏硬件或伤人。调试接口确保串口USART正常工作用于打印关键变量角度、角速度、PWM输出、速度等。可以使用printf重定向或简单的发送函数。参数记录准备一个笔记本或电子表格记录每次调整的参数和对应的现象。5.2 PID参数调试步骤“先直立后速度”调试必须分步进行遵循“先内环后外环”、“先比例后微分积分”的原则。第一步调试直立环PD将速度环的PID参数全部设为0KpKiKd0使其不工作。此时angle_adjust恒为0。手持小车使其倾斜观察串口输出的g_angle值。检查角度符号是否正确前倾为正后仰为负。如果不符在计算acc_angle时取反。先调P比例设定一个较小的Kp如50D0。用手轻轻推动小车它应该会朝你推的反方向转动试图“扶正”自己。如果转动方向错误将Kp改为负数。逐渐增大Kp小车的回复力会变强。直到小车在平衡点附近开始高频振荡“哆嗦”。此时说明P值过大。加入D微分将Kp回调到振荡前的大小然后逐渐加入Kd如从0.5开始。D的作用是抑制振荡。增加Kd振荡应该减弱小车变得更“沉稳”。但Kd过大会引入高频噪声可能引起电机啸叫或抖动需要配合软件低通滤波。关键现象判断当直立环PD参数基本合适时用手使小车偏离平衡点后松开小车应能快速、平稳地回到平衡点且没有持续振荡。在悬空状态下轻轻拨动轮子小车应能抵抗扰动。第二步加入速度环PI保持直立环参数不变。先调速度环P给pid_speed.Kp一个很小的正值如1.0。此时如果你用手轻轻转动一个轮子给小车一个速度小车为了消除这个速度会通过速度环输出一个angle_adjust使直立环的目标角度发生微小改变从而让小车朝相反方向转动来“抵消”你给的速度。测试小车在悬空时的“定住”能力快速拨动一下轮子然后松开看小车能否自己将轮子停回原处。如果不能缓慢增大Kp。加入速度环I如果小车在平衡时轮子会缓慢地朝一个方向持续转动积分饱和现象说明存在静态误差。此时需要加入较小的Ki如0.1让速度环能最终消除这个误差。Ki太大会引起系统超调或振荡。微调同时观察直立稳定性和速度环的纠偏能力对两组参数进行微调。5.3 常见问题与排查清单调试过程中一定会遇到问题下表列出了典型现象及排查思路问题现象可能原因排查步骤小车毫无反应电机不转1. 电机驱动板未供电或使能。2. PWM输出引脚错误或未初始化。3. 控制输出motor_out恒为0。4. 中断未正确触发。1. 检查驱动板电源和使能引脚电平。2. 用示波器或万用表检查PWM引脚是否有输出。3. 打印g_angle和motor_out值看计算逻辑。4. 检查定时器中断配置和优先级。小车朝一个方向猛冲1. 角度极性错误前倾/后仰判断反。2. 电机输出极性错误正转/反转接反。3. PID参数Kp或Ki极大。1. 手持小车观察串口角度值倾斜方向与数值正负应一致。2. 单独测试电机驱动函数确认PWM正负值与转向对应关系。3. 将PID参数暂时设为零进行测试。小车剧烈振荡高频抖动1. 直立环Kp过大。2. 微分项Kd过小或为0。3. 控制周期dt不稳定或过长。4. 传感器数据噪声大。1. 减小Kp。2. 适当增加Kd。3. 确保定时中断周期稳定如10ms检查是否有其他任务阻塞中断。4. 对陀螺仪原始数据或融合后的角度进行低通滤波。小车缓慢来回摆动低频振荡1. 微分项Kd过大引入了相位滞后。2. 速度环Ki过大。3. 机械结构松动。1. 减小Kd。2. 减小速度环Ki。3. 紧固电机、轮子等连接件。角度值漂移严重1. 陀螺仪零漂未校准。2. 互补滤波系数alpha不合适。3. 加速度计受振动干扰大。1. 上电静止一段时间读取陀螺仪输出将其平均值作为零偏在程序中减去。2. 适当增大alpha更信任陀螺仪但会增加长期漂移。3. 考虑使用更复杂的卡尔曼滤波或对加速度计数据进行滑动平均。响应遥控指令迟钝1. 速度环PID参数过小。2. 控制输出被限幅。3. 遥控指令处理有延迟。1. 适当增大速度环Kp。2. 检查pid_speed.max_out是否太小限制了角度补偿量。3. 确保遥控数据接收和处理在控制周期内完成。5.4 进阶调试技巧数据可视化如果条件允许可以将STM32的传感器数据、中间变量通过串口发送到上位机如SerialChart、PlotJuggler或自己写的Python脚本绘制曲线直观观察系统响应。参数固化调试出稳定参数后可以将其存储在STM32的Flash中避免每次上电重新设置。加入死区在电机输出接近0时设置一个死区如if(abs(pwm) 10) pwm 0可以消除电机启动静摩擦带来的抖动。软件低通滤波对陀螺仪数据g_gyro进行一阶低通滤波可以平滑微分项减少高频噪声的影响。6. 从调试到稳定运行的最佳实践当小车能在你手中稳定平衡后可以考虑以下优化点使其更可靠、更智能。6.1 增加启动与保护机制裸奔的控制循环很脆弱需要增加安全逻辑。// 在Balance_Task()开头或主循环中加入 void Balance_Task(void) { // 1. 上电自检/校准 static uint8_t is_calibrated 0; if(!is_calibrated) { // 例如等待1秒计算陀螺仪零偏 // 或者等待角度稳定在较小范围内 if(/* 校准完成条件 */) { is_calibrated 1; } else { Motor_Set_PWM(0, 0); // 校准期间电机停转 return; } } // 2. 倾角过大保护 if(fabs(g_angle) 45.0) { // 如果倾角超过45度认为即将摔倒 Motor_Set_PWM(0, 0); // 紧急停止 // 可以同时触发声光报警 return; } // 3. 正常控制逻辑... // Attitude_Update(...); etc. }6.2 优化控制周期与实时性固定周期确保Balance_Task在精确的定时中断中执行使用硬件定时器不要用HAL_Delay。中断优先级控制任务中断的优先级应高于其他非实时任务如串口接收。减少计算量在中断服务函数中只做必要的计算和赋值将复杂的调试打印移到主循环中。6.3 扩展功能方向当基础平衡实现后你可以以此为平台添加更多功能遥控控制通过蓝牙如HC-05/06或2.4G模块如NRF24L01接收指令修改target_speed或target_angle实现前后左右移动和转向。循迹/避障增加红外或超声波传感器实现自动巡线或避障。姿态升级将互补滤波替换为卡尔曼滤波获得更优的姿态估计。无线调试通过无线模块将实时数据发送到电脑实现无线参数调试和监控。上位机配置编写一个简单的PC端工具通过串口实时修改PID参数并观察效果极大提升调试效率。平衡小车项目是嵌入式软硬件结合的绝佳练手机会。它迫使你去理解传感器特性、掌握实时控制思想、亲手调试参数并分析系统响应。整个过程的关键在于耐心和系统性的调试方法先确保每个模块单独工作再逐步闭环先调稳一个环再加入下一个环大胆假设小心验证仔细观察现象。当你看到小车颤颤巍巍地第一次自己站稳时那种成就感就是对所有努力的最好回报。接下来试着让它走起来转起来甚至跳个舞你会发现这片天地更加广阔。
返回列表