用STM32CubeIDE和L298N从零构建蓝牙遥控+红外循迹智能小车实战指南
第一次拿到STM32开发板时,看着密密麻麻的引脚和陌生的开发环境,我完全不知道如何让它控制一个小车跑起来。经过三个月的摸索和五次硬件烧毁的教训,终于总结出这套保姆级教程。本文将带你从零开始,用最基础的硬件(STM32F103C8T6核心板、L298N电机驱动、HC-05蓝牙模块和五路红外传感器)搭建一个既能蓝牙遥控又能自动循迹的智能小车。
1. 硬件准备与电路设计
1.1 物料清单与选型建议
在开始前,请准备好以下硬件(总成本约150元):
主控模块:
- STM32F103C8T6最小系统板(蓝色药丸板)
- 推荐理由:性价比高,社区资源丰富,64KB Flash完全够用
运动系统:
- L298N电机驱动模块(带散热片版本)
- TT减速电机(6V/200RPM) + 车轮套件
- 18650电池盒(两节串联)
感知系统:
- 五路红外循迹传感器(TCRT5000)
- 注意:选择带数字量输出的版本,避免额外设计比较器电路
通信模块:
- HC-05蓝牙模块(建议买已刷好AT固件的版本)
- 注意区分主从模式,我们这里使用从机模式
其他:
- 万用板+铜柱车架
- 杜邦线(建议用20cm公对公+公对母组合)
- 开关、扎带等辅助材料
1.2 电路连接详解
电路连接是初学者最容易出错的部分,这里给出经过验证的可靠接法:
| 模块 | 引脚连接 | 注意事项 |
|---|---|---|
| L298N | ENA→PA0, IN1→PA1, IN2→PA2 | 左侧电机控制线 |
| ENB→PA3, IN3→PA4, IN4→PA5 | 右侧电机控制线 | |
| HC-05蓝牙 | RX→PB10, TX→PB11 | 需接3.3V,5V会烧毁模块 |
| 红外传感器 | 左1→PC4, 左2→PC5 | 数字输出直接接GPIO |
| 中→PB0, 右1→PB1, 右2→PB2 | 建议加上10K上拉电阻 |
关键提示:电机驱动电源与单片机电源必须共地!这是导致80%控制失效问题的根源。建议先用USB供电调试,待基本功能验证后再切换为电池供电。
2. STM32CubeIDE工程配置
2.1 基础工程创建
- 打开STM32CubeIDE,选择"Start new STM32 project"
- 在MCU选择器中输入"STM32F103C8",选择Tx系列
- 配置时钟树:
- HSE选择Crystal/Ceramic Resonator
- 将HCLK设置为72MHz(最大值)
- 系统核心中启用Serial Wire调试接口
2.2 关键外设配置
定时器PWM配置(电机控制):
// TIM2 Channel1→PA0 (左电机PWM) // TIM2 Channel2→PA1 (右电机PWM) htim2.Instance = TIM2; htim2.Init.Prescaler = 71; // 1MHz计数频率 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 999; // 1kHz PWM频率 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;串口配置(蓝牙通信):
// USART3 PB10/PB11 huart3.Instance = USART3; huart3.Init.BaudRate = 9600; huart3.Init.WordLength = UART_WORDLENGTH_8B; huart3.Init.StopBits = UART_STOPBITS_1; huart3.Init.Parity = UART_PARITY_NONE; huart3.Init.Mode = UART_MODE_TX_RX; huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;GPIO配置(红外传感器):
- 将PC4、PC5、PB0-PB2设置为输入模式
- 建议启用内部上拉(GPIO_PULLUP)
3. 核心代码实现
3.1 电机驱动模块化编程
在Core/Src创建motor.c文件,实现以下关键函数:
// 电机初始化 void Motor_Init(void) { HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 左电机PWM HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); // 右电机PWM } // 通用电机控制函数 void Motor_Control(uint8_t motor, int16_t speed) { speed = (speed > 100) ? 100 : ((speed < -100) ? -100 : speed); if(motor == LEFT_MOTOR) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, abs(speed)*10); HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, (speed > 0) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, (speed > 0) ? GPIO_PIN_RESET : GPIO_PIN_SET); } else { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, abs(speed)*10); HAL_GPIO_WritePin(IN3_GPIO_Port, IN3_Pin, (speed > 0) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(IN4_GPIO_Port, IN4_Pin, (speed > 0) ? GPIO_PIN_RESET : GPIO_PIN_SET); } }3.2 蓝牙指令解析
在main.c中添加蓝牙回调处理:
uint8_t bluetooth_rx_data[1]; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart == &huart3) { switch(bluetooth_rx_data[0]) { case 'F': // 前进 Motor_Control(LEFT_MOTOR, 70); Motor_Control(RIGHT_MOTOR, 70); break; case 'L': // 左转 Motor_Control(LEFT_MOTOR, -40); Motor_Control(RIGHT_MOTOR, 70); break; case 'S': // 停止 Motor_Control(LEFT_MOTOR, 0); Motor_Control(RIGHT_MOTOR, 0); break; // 其他指令... } HAL_UART_Receive_IT(&huart3, bluetooth_rx_data, 1); } }3.3 红外循迹算法优化
传统if-else判断方式在复杂路径下效果不佳,建议采用状态机实现:
typedef enum { TRACK_LOST, // 丢失路径 TRACK_STRAIGHT, // 直行 TRACK_LEFT_CURVE, // 左弯道 TRACK_RIGHT_CURVE // 右弯道 } TrackState; TrackState track_detect(void) { uint8_t sensor_val = 0; sensor_val |= (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4) << 4); sensor_val |= (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5) << 3); sensor_val |= (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) << 2); sensor_val |= (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) << 1); sensor_val |= HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2); switch(sensor_val) { case 0b11100: return TRACK_LEFT_CURVE; case 0b11000: return TRACK_LEFT_CURVE; case 0b00111: return TRACK_RIGHT_CURVE; case 0b00011: return TRACK_RIGHT_CURVE; case 0b00100: return TRACK_STRAIGHT; default: return TRACK_LOST; } }4. 系统整合与调试技巧
4.1 多模式切换实现
在main.h中定义工作模式枚举:
typedef enum { MODE_MANUAL, // 蓝牙遥控模式 MODE_AUTO_TRACK, // 自动循迹模式 MODE_IDLE // 空闲模式 } WorkMode; extern WorkMode current_mode;在main.c的while循环中实现模式调度:
while (1) { switch(current_mode) { case MODE_MANUAL: // 蓝牙指令已在中断处理 break; case MODE_AUTO_TRACK: switch(track_detect()) { case TRACK_STRAIGHT: Motor_Control(LEFT_MOTOR, 60); Motor_Control(RIGHT_MOTOR, 60); break; case TRACK_LEFT_CURVE: Motor_Control(LEFT_MOTOR, 30); Motor_Control(RIGHT_MOTOR, 70); break; // 其他状态处理... } break; } HAL_Delay(50); // 控制循环频率 }4.2 常见问题排查指南
电机不转:
- 检查L298N使能跳线帽是否接好
- 用万用表测量电机端口是否有电压输出
- 尝试直接给电机供电排除电机本身故障
蓝牙无法连接:
- 确认模块已进入配对模式(LED快闪)
- 手机端尝试使用"蓝牙串口"APP
- 检查TX/RX是否接反
循迹不准确:
- 调节传感器上的电位器改变灵敏度
- 在白色背景上用黑色胶带测试传感器输出
- 检查供电电压是否稳定(建议5V单独供电)
4.3 性能优化建议
PID控制算法:在motor.c中实现简单的PID控制器,让小车运动更平滑
typedef struct { float Kp, Ki, Kd; float integral; float prev_error; } PID_Controller; float PID_Update(PID_Controller* pid, float error) { pid->integral += error; float derivative = error - pid->prev_error; pid->prev_error = error; return pid->Kp*error + pid->Ki*pid->integral + pid->Kd*derivative; }电池管理:添加电压检测功能,当电压低于6.5V时自动减速
void Battery_Check(void) { HAL_ADC_Start(&hadc1); if(HAL_ADC_GetValue(&hadc1) < BATTERY_LOW) { Motor_Control(LEFT_MOTOR, 0); Motor_Control(RIGHT_MOTOR, 0); } }无线升级:通过蓝牙实现固件更新(需配合Bootloader)
完成基础功能后,可以尝试添加超声波避障、OLED状态显示等扩展功能。这个项目的真正价值不在于小车本身,而在于掌握如何将多个功能模块有机整合成一个完整系统的方法论。