news 2026/10/14 18:44:12

STM32蓝牙小车实战:HC05+L298N从接线到代码全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32蓝牙小车实战:HC05+L298N从接线到代码全解析

简介:这份资源是面向STM32初学者的蓝牙遥控小车完整工程,基于stm32f103c8t6主控,搭配HC05蓝牙模块与L298N电机驱动,实现前进、后退、左转、右转四项基础动作。作者为寒假课设临时上手所写,代码结构简单、门槛低,适合刚接触STM32、想快速跑通一个能动能控的小项目练手,也可作为课设或入门实验的参考模板。压缩包共60个文件,约1.59MB,包含c与h源码、uvprojx与uvoptx工程文件、o与crf等编译中间产物、map与lst链接列表、axf可执行文件,以及一张接线示意图,覆盖从源码到编译输出的完整Keil工程目录。资源已有6138人学习下载,热度较高。读者可据此了解蓝牙串口通信、GPIO控制与电机驱动的配合方式,并参考接线图完成硬件连接;若将L298N换成L9110,还可省去与STM32共地的麻烦,便于快速复现与二次修改。

1. 从一堆散件到能跑的车:这套 STM32 蓝牙小车方案到底值不值得复现

很多人第一次做 STM32 蓝牙小车,都是被某宝上一堆散件逼疯的:STM32F103C8T6 最小系统板、HC05 蓝牙模块、L298N 电机驱动、四驱底盘、一堆杜邦线,买回来发现串口不通、电机不转、蓝牙连不上,最后只能吃灰。这套「STM32F103C8T6 + HC05 + L298N」的组合,本质上是把「单片机串口通信 + 电机驱动控制 + 蓝牙无线指令」三条链路串起来的最小可行系统。它不追求 PID 闭环、不追求 ROS 建图,就是让你用一块十几块钱的核心板,通过手机蓝牙发几个字符,控制小车前进后退转弯。适合谁?适合刚学完 GPIO 和串口、想找个能动的项目练手的人,也适合需要快速验证电机驱动逻辑的嵌入式从业者。你拿到的不只是代码,而是一条从硬件接线到指令解析的完整链路,坑基本都集中在串口配置和电源地上,后面我会一条条拆开讲。

2. 硬件链路拆解:从 HC05 到 L298N 的电气连接与供电逻辑

2.1 为什么选 STM32F103C8T6 而不是 Arduino

STM32F103C8T6 是 Cortex-M3 内核,72MHz 主频,64KB Flash,20KB SRAM,价格常年压在十元出头。相比 Arduino UNO 的 8 位 AVR,它的定时器资源更富裕,PWM 输出更稳,串口收发用中断或 DMA 都行。做蓝牙小车最核心的外设是 USART 和 TIM,C8T6 有 3 个 USART、4 个定时器,足够同时跑蓝牙通信和四路 PWM。选它的另一个理由是生态:标准外设库和 HAL 库资料都多,出问题搜得到。Arduino 虽然上手快,但一旦你想从「能跑」过渡到「跑得稳」,比如加编码器测速、加 OLED 显示,C8T6 的余量明显更大。常见做法是用 HAL 库配 USART1 接 HC05,TIM2 或 TIM3 出四路 PWM 给 L298N。

2.2 HC05 与 STM32 的串口接线:交叉、共地、电平

HC05 是蓝牙串口透传模块,默认波特率 9600,数据位 8,停止位 1,无校验。它和 STM32 之间走的是 UART,接线必须交叉:HC05 的 TXD 接 STM32 的 RX(PA10),HC05 的 RXD 接 STM32 的 TX(PA9)。VCC 接 5V,GND 必须和 STM32 的 GND 共地。这里有个血泪经验:HC05 的 RXD 是 3.3V 电平,STM32 的 TX 输出也是 3.3V,直接接没问题;但如果你用 5V 的单片机,中间要加电平转换。另外 HC05 的 EN/KEY 引脚悬空就是透传模式,拉高进 AT 模式,调波特率时才用。

// USART1 初始化,波特率 9600,对应 HC05 默认透传速率 void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 9600; // 必须与 HC05 一致 huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; // 收发都要开 huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } }

这段代码里BaudRate是第一个要核对的地方,HC05 出厂 9600,你代码写 115200 就是收不到数据。Mode必须开UART_MODE_TX_RX,只开 TX 就只能发不能收。HwFlowCtl关掉,HC05 不支持硬件流控。初始化失败会进Error_Handler,实际调试时可以先在里面点个灯,确认串口配置有没有过。

2.3 L298N 的供电与使能:别让电机把单片机拖死

L298N 是双 H 桥驱动,能同时驱动两路直流电机,每路最大 2A。它有三个电源相关端子:12V 输入(接电池正极)、GND(接电池负极和单片机 GND)、5V 输出(如果板载稳压使能,可以给单片机供电)。这里最容易翻车的是共地:L298N 的 GND 必须和 STM32 的 GND 连在一起,否则 PWM 信号没有参考地,电机会乱转或者根本不转。使能端 ENA、ENB 接 STM32 的 PWM 输出引脚,IN1~IN4 接普通 GPIO 控制方向。

L298N 引脚接 STM32 引脚作用
ENAPA0 (TIM2_CH1)左电机调速 PWM
IN1PB0左电机方向 1
IN2PB1左电机方向 2
ENBPA1 (TIM2_CH2)右电机调速 PWM
IN3PB10右电机方向 1
IN4PB11右电机方向 2
GNDGND共地,必须接

供电策略上,我一般会这样分:电池 7.4V 或 12V 直接进 L298N 的 12V 端子,L298N 板载 5V 输出给 STM32 核心板供电,HC05 也从这路 5V 取电。注意 L298N 的 5V 输出电流有限,如果核心板带 OLED 或者多个传感器,最好单独给单片机供 5V,只共地不共电源。电机启动瞬间电流冲击大,电池容量不够会导致单片机复位,现象就是小车一动就重启,这个坑后面避坑章节会细说。

3. 代码落地:PWM 调速、串口中断收指令与运动状态机

3.1 TIM2 四路 PWM 配置与占空比映射

STM32F103C8T6 的 TIM2 是通用定时器,CH1~CH4 分别对应 PA0、PA1、PA2、PA3。做小车只需要两路 PWM 控制左右电机速度,但配置逻辑一样。定时器频率计算公式:PWM 频率 = 72MHz / ((Prescaler+1) * (Period+1))。我一般设 Prescaler=71,Period=999,得到 1kHz 的 PWM,电机驱动听不到啸叫,调速线性度也够。

// TIM2 PWM 初始化,1kHz,占空比由 CCR 决定 void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz 计数时钟 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 999; // 1MHz/1000=1kHz PWM 频率 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_Base_Init(&htim2); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig); HAL_TIM_PWM_Init(&htim2); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; // 初始占空比 0,电机不转 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2); HAL_TIM_MspPostInit(&htim2); // 底层 GPIO 复用配置 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); }

Prescaler和Period决定了 PWM 频率,改这两个值就能改频率。Pulse是 CCR 值,范围 0~999,对应占空比 0~100%。HAL_TIM_PWM_Start必须调用,否则引脚没有波形输出。实际调速时用__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, speed)动态改占空比,speed 传 0~999。

3.2 串口中断接收与指令解析

HC05 收到手机发来的字符后,通过 USART1 传给 STM32。用中断接收最省 CPU,每收到一个字节进一次回调。指令协议我一般定成单字符:'F' 前进,'B' 后退,'L' 左转,'R' 右转,'S' 停止。简单直接,手机端用蓝牙串口助手发字符就行。

uint8_t rx_byte; // 单字节接收缓冲 // 串口接收完成回调,每收到一个字节触发 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { switch (rx_byte) { case 'F': car_forward(); break; case 'B': car_backward(); break; case 'L': car_left(); break; case 'R': car_right(); break; case 'S': car_stop(); break; default: car_stop(); break; // 未知指令停车,安全兜底 } // 重新开启接收,否则只能收一次 HAL_UART_Receive_IT(&huart1, &rx_byte, 1); } }

回调里rx_byte是全局变量,中断收到后立刻解析。HAL_UART_Receive_IT必须在回调末尾重新调用,不然下一次接收不会触发。default分支停车是安全设计,防止干扰字符导致小车乱跑。如果你要传多字节指令,比如 "SPEED:500",就得加环形缓冲和帧头帧尾解析,那是进阶做法,新手先用单字符跑通。

3.3 运动控制函数与状态机

前进的逻辑是左电机 IN1=1、IN2=0,右电机 IN3=1、IN4=0,同时 ENA、ENB 给 PWM。后退反过来。转弯是左右轮差速,比如左转时左轮停或反转,右轮正转。

#define SPEED_DEFAULT 600 // 默认占空比 600/999 约 60% void car_forward(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // IN1 左正 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // IN2 左负 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET); // IN3 右正 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET); // IN4 右负 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, SPEED_DEFAULT); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, SPEED_DEFAULT); } void car_stop(void) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 0); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_10 | GPIO_PIN_11, GPIO_PIN_RESET); }

SPEED_DEFAULT是调速关键,太小电机不转,太大起步猛冲。实际调的时候从 400 开始试,能转起来再往上加。car_stop里先把 PWM 置 0 再拉低方向引脚,避免停车瞬间电机反电动势冲击。状态机层面,如果你要加按键切换模式,可以用一个car_state枚举记录当前运动状态,避免重复指令导致抖动。

4. 避坑与排查:蓝牙连不上、电机不转、小车一冲就复位

4.1 现象:手机搜不到 HC05 或配对失败

原因通常有三个:HC05 没供电、EN/KEY 引脚被拉高进了 AT 模式、或者模块已经配对过其他设备。先量 VCC 和 GND 之间是不是 5V,再看 KEY 引脚是不是悬空。如果之前进过 AT 模式,断电重启后 KEY 悬空就回透传。手机端配对密码默认 1234 或 0000,配对后要用蓝牙串口助手连,不是连上系统蓝牙就行。

4.2 现象:串口能收数据但电机不转

先看 L298N 的电源指示灯亮不亮,不亮就是 12V 没接或电池没电。再看 ENA、ENB 有没有 PWM 波形,用示波器或者万用表测电压,占空比 60% 时电压大概 3V 左右。如果 PWM 有但电机不转,查 IN1~IN4 的方向组合,前进必须是 1-0 和 1-0,全 0 或全 1 都是刹车状态。还有一个隐蔽坑:L298N 的 5V 使能跳帽如果没插,板载稳压不工作,单片机可能没供电。

4.3 现象:小车一动就复位或蓝牙断连

这是电源问题,不是代码问题。电机启动瞬间电流能到 1A 以上,如果电池容量小或者线细,电压会被拉低到单片机复位阈值以下。解决方法是给单片机单独供电,或者电池并联一个大电容(1000uF 以上)。另外 HC05 对电压波动敏感,和电机共用一路 5V 时容易被拉挂,最好从 L298N 的 5V 取电给 HC05,单片机用另一路稳压。

4.4 现象:PWM 调速时电机啸叫或抖动

PWM 频率太低(比如 100Hz)电机会听到明显啸叫,太高(比如 20kHz)L298N 响应跟不上。1kHz~5kHz 是比较舒服的区间。抖动通常是占空比太低,电机在启动阈值附近,把 SPEED_DEFAULT 调高 100~200 就能解决。如果还抖,检查电源纹波,加滤波电容。

4.5 现象:蓝牙指令延迟大或丢指令

HC05 默认波特率 9600,单字符指令延迟在几十毫秒,正常。如果延迟明显,看手机端是不是开了「自动发送」导致指令堆积。丢指令一般是串口中断里处理时间太长,比如在回调里加了延时或打印,把解析逻辑简化,回调里只置标志位,主循环处理。

5. 进阶技巧:用 DMA 收指令、加编码器测速与指令协议升级

把单字符指令跑通之后,下一步通常是让通信更稳、控制更细。我一般会先上 DMA 接收:USART1 的 RX 用 DMA 循环模式,数据直接搬到缓冲区,CPU 不用每字节进中断。配置时把HAL_UART_Receive_DMA调一次,DMA 设成 circular,然后在主循环里查缓冲区有没有新数据。这样即使手机端连续发指令,也不会因为中断频繁而丢包。

uint8_t dma_rx_buf[32]; // DMA 接收缓冲 // DMA 接收启动,循环模式,不需要每次重开 void uart_dma_start(void) { HAL_UART_Receive_DMA(&huart1, dma_rx_buf, sizeof(dma_rx_buf)); } // 主循环里解析,找到有效指令就执行并清缓冲 void parse_dma_cmd(void) { for (uint8_t i = 0; i < sizeof(dma_rx_buf); i++) { if (dma_rx_buf[i] == 'F') { car_forward(); dma_rx_buf[i] = 0; } else if (dma_rx_buf[i] == 'S') { car_stop(); dma_rx_buf[i] = 0; } // 其他指令同理 } }

DMA 缓冲开 32 字节,够存一串指令。解析时逐字节扫,执行完把该字节清 0,避免重复执行。注意 DMA 循环模式下缓冲区会一直被覆盖,解析要及时,主循环别加长延时。

再进一步是加编码器测速。STM32F103C8T6 的 TIM3 和 TIM4 可以配成编码器接口模式,读电机轴上的 AB 相编码器脉冲,算出实际转速。有了转速就能做简单的闭环:目标速度 600,实测 400,就往上加 PWM。这一步不需要复杂 PID,比例控制就能让小车走直线。我一般会把编码器读数每 100ms 更新一次,和蓝牙指令的目标速度做差,动态调 CCR。

指令协议也可以从单字符升级成带参数的帧,比如#F,600#表示前进速度 600。解析时找帧头#和帧尾#,中间按逗号分割。这样手机端可以发调速指令,不用改代码重新烧录。协议升级后,DMA 缓冲要开大一点,解析函数加状态机,防止半帧数据被误执行。

从那以后我每次调小车,都强制先单独测三样:串口自发自收确认 HC05 通、PWM 引脚单独测波形确认 L298N 通、电池带电机测电压确认不掉压。这三步过了再联调,能省掉大半排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 18:43:35

F28027真实温度闭环控制:ADC校准+PT100线性化+PID实时调参

简介&#xff1a;本资源是面向嵌入式初学者与DSP开发实践者的TMS320F28027单片机综合实验项目&#xff0c;聚焦温度感知与基础外设控制两大核心能力训练&#xff0c;适用于工业控制、电机驱动等实时系统入门学习。压缩包共293个文件&#xff0c;以42个C源码&#xff08;含温度采…

作者头像 李华
网站建设 2026/10/14 18:43:33

家电品牌出海GEO优化公司哪家好:从技术能力到交付体系怎么选

AI 搜索改变了消费者了解家电产品的路径&#xff0c;品牌能否被准确理解、引用和推荐&#xff0c;越来越依赖系统化的 GEO 建设。产品参数、认证范围、安装条件与售后承诺&#xff0c;都要在目标市场形成清晰、统一的答案资产。 本次重点关注广拓时代CantoTimes的家电出海服务方…

作者头像 李华
网站建设 2026/10/14 18:43:20

OSGB转3DTiles实战:解决Cesium模型偏移、纹理丢失与加载卡顿

简介&#xff1a;这是一款专为倾斜摄影三维建模工程师与WebGIS开发人员设计的OSGB格式批量转3DTile的命令行工具&#xff0c;解决传统倾斜模型难以直接在Cesium等Web三维平台高效加载与渲染的痛点&#xff0c;适用于城市级实景三维项目交付、BIMGIS融合应用及轻量化发布场景。资…

作者头像 李华
网站建设 2026/10/14 18:43:15

PMP计算题全攻略:挣值管理、三点估算与关键路径一次讲透

PMP考试里最让人头疼的&#xff0c;往往不是那些长篇大论的场景题&#xff0c;而是夹杂在题干里需要动笔算一算的计算题。我见过不少考生&#xff0c;前面敏捷题做得行云流水&#xff0c;一到计算题就卡壳&#xff0c;不是因为题有多难&#xff0c;而是因为公式太多、场景太杂&…

作者头像 李华
网站建设 2026/10/14 18:43:10

Viwepager 分页组件完全指南:从状态管理、接口适配到避坑调试

简介&#xff1a;这是一份基于Unity3D的图片轮播项目源码与工程配置包&#xff0c;适合想学习UI交互、动画过渡及资源管理的初中级开发者。压缩包内共405个文件&#xff0c;以PNG/JPG图片素材、C#脚本、Unity资源&#xff08;asset/unity/prefab&#xff09;和meta配置文件为主…

作者头像 李华
网站建设 2026/10/14 18:43:07

微信小程序组件按需注入未通过?HBuilderX配置与排坑实战

很多用HBuilderX开发微信小程序的朋友&#xff0c;应该都遇到过这个提示&#xff1a;在微信开发者工具的“本地设置”里勾选“启动组件按需注入”&#xff0c;或者在代码里配置了lazyCodeLoading&#xff0c;结果却提示或表现为“启用组件按需注入未通过”。我当时第一次遇到时…

作者头像 李华