别再让电机乱转了!用STM32F103的TIM3和ULN2003A实现精准PWM调速(附完整代码)

张开发
2026/4/21 17:47:05 15 分钟阅读

分享文章

别再让电机乱转了!用STM32F103的TIM3和ULN2003A实现精准PWM调速(附完整代码)
从零构建STM32F103的PWM电机控制系统TIM3与ULN2003A深度解析第一次尝试用STM32控制直流电机时我盯着纹丝不动的电机转子发呆了半小时。开发板上LED正常闪烁示波器也能检测到PWM波形但电机就像被施了定身咒。直到发现ULN2003A模块上一个不起眼的指示灯才恍然大悟——这个看似简单的驱动电路藏着不少初学者容易踩的坑。本文将带你从硬件原理到代码实现完整走通STM32F103的PWM电机控制流程特别聚焦那些手册上不会明确标注的实战细节。1. 硬件设计为什么你的电机不转1.1 ULN2003A的逆向逻辑陷阱ULN2003A作为经典的达林顿管阵列其最反直觉的特性就是输入输出电平反向。当你在输入端施加高电平时输出端实际会给出低电平。这种设计源于其内部NPN三极管结构// 典型错误接线示例电机不会转 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_SET); // 以为这样会启动电机正确的电机连接方式应该是电机正极直接连接电源5V或3.3V电机负极接ULN2003A输出端ULN2003A输入端接STM32的PWM信号关键提示用万用表测量时当ULN2003A输出有效即电机该转动时其输出引脚对地电压应接近0V而非电源电压。1.2 TIM3引脚重映射的隐藏成本STM32F103的TIM3通道2默认对应PA7引脚但很多开发板为了布线方便会启用完全重映射Full Remap到PC7。这个过程中有几个易错点配置项标准映射完全重映射常见错误定时器时钟开启开启忘记使能APB1时钟GPIO模式复用推挽复用推挽配置为普通输出AFIO重映射不需要必须开启漏掉GPIO_PinRemapConfig外设时钟仅TIM3TIM3AFIO忘记开启AFIO时钟// 正确的完全重映射初始化代码片段 __HAL_RCC_AFIO_CLK_ENABLE(); // 容易被遗忘的AFIO时钟 __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; // 不是GPIO_MODE_OUTPUT_PP! HAL_GPIO_Init(GPIOC, GPIO_InitStruct); GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);2. PWM配置模式1与模式2的抉择2.1 两种PWM模式的核心差异TIM3支持PWM模式1和模式2这两种模式在电机控制中会产生完全相反的效果PWM模式1计数器CNT CCR时输出有效电平计数器CNT ≥ CCR时输出无效电平适合常规的高电平有效场景PWM模式2计数器CNT CCR时输出无效电平计数器CNT ≥ CCR时输出有效电平配合ULN2003A的反向特性使用更合理// 推荐配合ULN2003A的配置组合 TIM_OC_InitStruct.OCMode TIM_OCMODE_PWM2; // 使用模式2 TIM_OC_InitStruct.OCPolarity TIM_OCPOLARITY_HIGH; // 输出极性高2.2 占空比计算的实用公式实际项目中我们需要将直观的转速百分比转换为寄存器值。假设ARR自动重装载值 899PSC预分频系数 79期望占空比为D%则CCR寄存器值应设置为CCR (100 - D) * ARR / 100这种反向计算是因为我们使用了PWM模式2ULN2003A的反向特性。例如要实现60%占空比uint16_t duty_cycle 60; // 60% uint16_t ccr_value (100 - duty_cycle) * 899 / 100; __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, ccr_value);3. 完整代码实现与调试技巧3.1 模块化工程结构建议将电机控制逻辑封装为独立模块/motor_control ├── motor.c ├── motor.h └── pwm_config.cmotor.h中定义关键接口typedef enum { MOTOR_STOP 0, MOTOR_SPEED_LOW 30, // 30% MOTOR_SPEED_MEDIUM 60, MOTOR_SPEED_HIGH 90 } MotorSpeed; void Motor_Init(void); void Motor_SetSpeed(MotorSpeed speed);3.2 按键调速的防抖处理原始示例中的按键扫描可能过于简单实际应用需要加入按键去抖硬件或软件长按加速/减速功能速度渐变效果// 改进的按键处理示例 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { static uint32_t last_tick 0; if (HAL_GetTick() - last_tick 50) return; // 50ms防抖 if (GPIO_Pin KEY0_Pin) { current_speed (current_speed 10) % 100; // 步进加速 } else if (GPIO_Pin KEY1_Pin) { current_speed (current_speed - 10) % 100; // 步进减速 } Motor_SetSpeed(current_speed); last_tick HAL_GetTick(); }4. 进阶优化从能用到好用4.1 加入软启动保护突然的全速启动可能导致电流冲击添加软启动函数void Motor_SoftStart(MotorSpeed target, uint16_t duration_ms) { uint16_t steps duration_ms / 10; uint16_t increment target / steps; for (int i0; isteps; i) { Motor_SetSpeed(i * increment); HAL_Delay(10); } Motor_SetSpeed(target); }4.2 利用定时器中断实现速度闭环通过编码器或霍尔传感器反馈可以实现简单的PID控制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { // 假设TIM2用于速度检测 static int16_t last_count 0; int16_t current_count __HAL_TIM_GET_COUNTER(htim2); int16_t actual_speed current_count - last_count; // 简单P控制 int16_t error target_speed - actual_speed; current_ccr error * KP / 100; __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, current_ccr); last_count current_count; } }4.3 功耗与散热考量长时间运行时需要注意ULN2003A的导通电阻约1Ω驱动500mA电流时会产生0.5W功耗小型电机堵转电流可能是正常工作电流的5-10倍建议添加电流检测和保护电路// 简单的过流保护实现 if (__HAL_ADC_GET_VALUE(hadc1) CURRENT_THRESHOLD) { Motor_SetSpeed(MOTOR_STOP); HAL_GPIO_WritePin(LED_ALARM_GPIO_Port, LED_ALARM_Pin, GPIO_PIN_SET); }在完成第一个可用的PWM电机控制原型后我习惯用热像仪检查各芯片温度分布这往往能发现数据手册上不会提及的实战问题。特别是当电机负载突变时ULN2003A的温升曲线能直观反映系统的工作状态。

更多文章