用逐飞STC32G库做个智能小车:编码器测速、PWM调电机、ADC读电池电压(完整项目)
从零构建STC32G智能小车编码器测速、PWM电机控制与电池监测实战1. 项目规划与硬件选型智能小车作为嵌入式学习的经典项目能系统性地融合传感器采集、电机控制、电源管理等核心技能。我们选择逐飞科技的STC32G12K128开源库作为开发基础这款国产MCU以高性价比和丰富外设著称特别适合作为教育级机器人控制核心。硬件配置清单主控板STC32G12K128最小系统板核心工作频率24MHz电机驱动TB6612FNG双路H桥模块支持3A峰值电流运动部件TT马达减速箱减速比1:48配合AB相编码器每转390脉冲电源系统18650锂电池组7.4V配合AMS1117-5.0稳压模块调试工具逐飞科技配套的USB转串口调试器注意编码器建议选择光电式而非霍尔式前者在低速测量时具有更好的分辨率。电机驱动电压需与电池匹配过高的电压可能损坏驱动芯片。2. 开发环境搭建与基础配置2.1 工具链准备首先需要安装Keil C51开发环境建议uVision V5.25以上版本并导入逐飞提供的设备支持包。库文件安装步骤如下下载逐飞STC32G库的压缩包最新版本v2.3解压后将zf_device文件夹复制到工程目录在Keil中添加以下头文件路径./zf_device ./zf_device/inc在工程选项的Target标签页中设置Memory Model为LargeCode Rom Size为Large2.2 基础引脚分配根据硬件连接定义引脚映射// 电机控制引脚 #define MOTOR_L_PWM PWMB_CH1_P20 // 左电机PWM #define MOTOR_L_DIR P55 // 左电机方向 #define MOTOR_R_PWM PWMB_CH2_P21 // 右电机PWM #define MOTOR_R_DIR P54 // 右电机方向 // 编码器接口 #define ENCODER_L_A P34 // 左编码器A相(脉冲输入) #define ENCODER_L_B P35 // 左编码器B相(方向判断) #define ENCODER_R_A P12 // 右编码器A相 #define ENCODER_R_B P13 // 右编码器B相 // 电池电压检测 #define BATTERY_ADC ADC_P10 // 分压后接入P1.03. 电机控制系统实现3.1 PWM驱动配置TB6612FNG驱动芯片需要PWM信号和方向信号协同工作。我们使用STC32G的PWMB模块生成16kHz的PWM信号避免电机产生可闻噪声void Motor_Init(void) { // 初始化PWM通道频率16kHz初始占空比0% pwm_init(MOTOR_L_PWM, 16000, 0); pwm_init(MOTOR_R_PWM, 16000, 0); // 配置方向控制引脚为推挽输出 gpio_mode(MOTOR_L_DIR, GPO_PP); gpio_mode(MOTOR_R_DIR, GPO_PP); }速度控制函数void Set_Motor_Speed(uint8 motor, int16 speed) { speed constrain(speed, -1000, 1000); // 限制在-1000~1000范围 if(motor LEFT_MOTOR) { gpio_set(MOTOR_L_DIR, speed 0 ? 1 : 0); pwm_duty(MOTOR_L_PWM, abs(speed)*10); // 映射到0-10000 } else { gpio_set(MOTOR_R_DIR, speed 0 ? 1 : 0); pwm_duty(MOTOR_R_PWM, abs(speed)*10); } }3.2 编码器速度测量STC32G的定时器脉冲计数功能非常适合编码器测量。每个编码器需要占用一个定时器资源void Encoder_Init(void) { // 初始化定时器为外部计数模式 ctimer_count_init(CTIM0_P34); // 左编码器 ctimer_count_init(CTIM1_P12); // 右编码器 // 配置方向检测引脚为输入 gpio_mode(ENCODER_L_B, GPI_PULLUP); gpio_mode(ENCODER_R_B, GPI_PULLUP); } int16 Get_Encoder_Speed(uint8 encoder) { static int16 last_count[2] {0}; int16 current_count, speed; if(encoder LEFT_ENCODER) { current_count ctimer_count_read(CTIM0_P34); speed (gpio_get(ENCODER_L_B) ? 1 : -1) * (current_count - last_count[0]); last_count[0] current_count; } else { current_count ctimer_count_read(CTIM1_P12); speed (gpio_get(ENCODER_R_B) ? 1 : -1) * (current_count - last_count[1]); last_count[1] current_count; } ctimer_count_clean(encoder ? CTIM1_P12 : CTIM0_P34); return speed; // 返回脉冲数/采样周期 }实际应用中需要建立定时中断定期调用速度计算函数。建议采样周期设置为50-100ms过短会导致数据波动过大。4. 电源管理与系统监控4.1 电池电压检测通过电阻分压将电池电压降至ADC量程范围内0-3.3V使用12位ADC模式提高测量精度#define VOLTAGE_DIV_RATIO 0.5f // 分压比 #define ADC_REF_VOLTAGE 3.3f // 参考电压 float Get_Battery_Voltage(void) { uint16 adc_value adc_once(BATTERY_ADC, ADC_12BIT); return (adc_value / 4095.0f) * ADC_REF_VOLTAGE / VOLTAGE_DIV_RATIO; }电压保护策略当检测电压低于6.8V时触发低压报警电压低于6.5V时强制进入保护模式并停止电机通过LED闪烁频率反映当前电量状态4.2 系统状态监控建立定时器中断服务程序周期性更新系统状态void TM4_Isr() interrupt 20 { static uint16 counter 0; TIM4_CLEAR_FLAG; // 每50ms执行一次 if(counter 50) { counter 0; g_system.battery_voltage Get_Battery_Voltage(); g_system.left_speed Get_Encoder_Speed(LEFT_ENCODER); g_system.right_speed Get_Encoder_Speed(RIGHT_ENCODER); // 低压保护处理 if(g_system.battery_voltage 6.5f) { Set_Motor_Speed(LEFT_MOTOR, 0); Set_Motor_Speed(RIGHT_MOTOR, 0); g_system.low_power_flag 1; } } }5. 运动控制算法实现5.1 差分驱动模型智能小车采用差分驱动方式通过左右轮速差实现转向。建立运动学模型线速度 V (V_left V_right)/2 角速度 ω (V_right - V_left)/L (L为轮距)速度转换函数void Convert_Speed_To_Motor(float linear, float angular) { float L 0.15f; // 轮距15cm float left (linear - angular*L/2) * 1000; // 转换为控制量 float right (linear angular*L/2) * 1000; Set_Motor_Speed(LEFT_MOTOR, (int16)left); Set_Motor_Speed(RIGHT_MOTOR, (int16)right); }5.2 PID速度控制实现简单的增量式PID控制器提高速度稳定性typedef struct { float kp, ki, kd; float last_error; float integral; } PID_Controller; int16 PID_Calculate(PID_Controller* pid, float target, float current) { float error target - current; pid-integral error; float derivative error - pid-last_error; pid-last_error error; return (int16)(pid-kp * error pid-ki * pid-integral pid-kd * derivative); } // 初始化PID参数 PID_Controller left_pid {0.8f, 0.05f, 0.2f, 0, 0}; PID_Controller right_pid {0.8f, 0.05f, 0.2f, 0, 0}; void Motor_Control_Update(void) { static uint32 last_time 0; if(Get_System_Tick() - last_time 100) return; // 100ms控制周期 last_time Get_System_Tick(); int16 left_out PID_Calculate(left_pid, g_target.left_speed, g_system.left_speed); int16 right_out PID_Calculate(right_pid, g_target.right_speed, g_system.right_speed); Set_Motor_Speed(LEFT_MOTOR, left_out); Set_Motor_Speed(RIGHT_MOTOR, right_out); }6. 调试技巧与性能优化6.1 实时数据可视化利用逐飞库内置的printf函数和VOFA工具实现数据可视化void Send_Debug_Data(void) { printf(BAT:%.2f,L:%d,R:%d\r\n, g_system.battery_voltage, g_system.left_speed, g_system.right_speed); }VOFA配置参数波特率115200数据协议FireWater通道配置3个float型变量6.2 资源优化建议STC32G资源有限需特别注意定时器分配TIM0左编码器计数TIM1右编码器计数TIM4系统定时器1ms中断PWM资源使用PWMB组驱动两个电机剩余PWMA通道可用于舵机控制ADC通道除了电池检测还可预留1-2路用于扩展传感器6.3 常见问题排查电机不转检查TB6612的VM引脚电压7-12V测量PWM输出引脚是否有波形确认STBY引脚已拉高编码器计数异常检查编码器供电是否稳定通常需要5V确认脉冲信号线已接入定时器专用引脚在中断服务程序中及时清除计数标志ADC读数波动大在ADC输入引脚添加0.1uF滤波电容适当降低ADC时钟分频系数软件端采用滑动平均滤波在项目开发过程中建议先逐个模块验证功能再逐步集成。例如先单独测试PWM驱动电机再添加编码器反馈最后实现闭环控制。这种渐进式开发方法能有效隔离问题提高调试效率。