解码串口通信与蓝牙模块
串口通信原理与应用
串行通信与并行通信
- 串行通信:数据逐位按顺序传输
- 适合远距离传输(可达数十米以上)
- 抗干扰能力强
- 布线简单,成本低
- 并行通信:多个数据位同时传输
- 适合短距离传输(通常在数米内)
- 传输速率高
- 抗干扰能力弱,易受串扰影响
通信方向分类
-
单工:数据只能单向传输(如广播)

-
半双工:数据可双向传输,但不能同时进行(如对讲机)

-
全双工:数据可同时双向传输(如电话)

同步方式分类
- 同步通信:
- 通过时钟信号或同步字符实现严格同步
- 数据以帧为单位连续传输
- 传输效率高,适合大数据量传输
- 对时钟同步要求严格,硬件复杂
- 异步通信:
- 通过起始位和停止位实现字符级同步
- 数据以字符为单位独立传输
- 硬件简单,抗干扰能力强
- 传输效率较低,有额外开销
串口硬件接口
-
常用接口类型:
- DB-9(9针串口):常用引脚包括TXD、RXD、GND

- 4针串口:TXD、RXD、GND、VCC,通过CH340芯片转为USB
-
硬件连接:
- 有线通信:使用CH340转换芯片,UART1跳线帽1-3、2-4短接
- 无线通信:传感器TX连接MCU RX,传感器RX连接MCU TX,UART1跳线帽3-5、4-6短接

串口通信参数
-
字符格式(数据帧):
进行串口通信的时候,需要通信双方在协议层规定好传输的数据包(字符帧)的格式,字符帧由起始位、数据位、校验位、停止位组成。

- 最常用格式:1起始位 + 8数据位 + 无校验 + 1停止位(8N1)
-
通信速率:
- 波特率:单位时间内传输的码元数量(bps)
- 比特率:单位时间内传输的二进制位数(bits/s)
- 常用波特率:9600、19200、38400、57600、115200 bps
计算示例:
- 格式:1起始位 + 8数据位 + 1停止位 = 10位/字符
- 波特率9600 bps时,每秒可传输:9600/10 = 960个字符
STM32串口外设
- USART:通用同步异步收发器(支持同步和异步)
- UART:通用异步收发器(仅异步)
- STM32F407ZET6提供:4个USART + 2个UART

串口通信程序设计
核心步骤:时钟使能→GPIO复用配置→GPIO参数初始化→UART参数配置→中断配置(可选)→UART使能
UART初始化函数
/**
* @brief 配置USART外设参数并初始化
* @param baud 波特率设置,单位bps(如9600、115200)
* @retval None
* @note 本函数配置GPIO复用、串口参数、中断使能等
* 使用前需确保系统时钟正确配置
* 引脚复用:PA9->USART1_TX, PA10->USART1_RX
*/
void USART_Config(u32 baud)
{
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 1. 使能GPIOA时钟(引脚PA9、PA10所在端口)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 2. 使能USART1时钟(USART1挂载在APB2总线)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 3. 配置引脚复用功能
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); // PA9复用为USART1_TX
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); // PA10复用为USART1_RX
// 4. 配置GPIO引脚参数
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // 输出速度
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉电阻
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; // 同时配置两个引脚
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 5. 配置USART参数
USART_InitStructure.USART_BaudRate = baud; // 波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发使能
USART_Init(USART1, &USART_InitStructure);
// 6. 配置USART接收中断
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStructure);
// 7. 使能接收中断(RXNE:准备好读取接收到的数据)
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 8. 使能USART外设
USART_Cmd(USART1, ENABLE);
}
波特率计算(寄存器开发需要)
-
框图

-
寄存器关键位
- 状态寄存器 (USART_SR)
- 位 7 TXE:发送数据寄存器为空
- 硬件置 1:TDR 数据传输到移位寄存器
- 软件清零:向 USART_DR 写入数据
- 置 1 时若 TXEIE=1 触发中断
- 位 6 TC:发送完成
- 硬件置 1:帧发送完成且 TXE=1
- 软件清零:读 SR 后写 DR,或直接写 0(多缓冲区推荐)
- 置 1 时若 TCIE=1 触发中断
- 位 5 RXNE:读取数据寄存器不为空
- 硬件置 1:移位寄存器数据传输到 RDR
- 软件清零:读取 USART_DR 数据
- 置 1 时若 RXNEIE=1 触发中断
- 位 4 IDLE:检测到空闲线路
- 硬件置 1:检测到空闲帧
- 软件清零:读 SR 后读 DR
- 置 1 时若 IDLEIE=1 触发中断
- 位 3 ORE:上溢错误
- 硬件置 1:RXNE=1 时新数据又传入移位寄存器
- 软件清零:读 SR 后读 DR
- 多缓冲区下 EIE=1 时触发中断
- 位 0 PE:奇偶校验错误
- 硬件置 1:接收数据奇偶校验失败
- 软件清零:读 SR 后读 / 写 DR(需等待 RXNE=1)
- 置 1 时若 PEIE=1 触发中断
- 位 7 TXE:发送数据寄存器为空
- 数据寄存器 (USART_DR)
- 位 8:0 DR [8:0]:收发数据存储位
- 写操作:存入待发送数据(TDR 功能)
- 读操作:读取接收数据(RDR 功能)
- 奇偶校验使能时,MSB 位为奇偶校验位(发送时覆盖、接收时读取)
- 位 8:0 DR [8:0]:收发数据存储位
- 波特率寄存器 (USART_BRR)
- 位 15:4 DIV_Mantissa [11:0]:USARTDIV 除数尾数
- 配合小数位配置波特率,公式:USARTDIV=16×baudfPCLK(16 倍过采样)
- 位 3:0 DIV_Fraction [3:0]:USARTDIV 除数小数
- 8 倍过采样时(OVER8=1),DIV_Fraction [3] 必须置 0
- 位 15:4 DIV_Mantissa [11:0]:USARTDIV 除数尾数
- 控制寄存器 1 (USART_CR1)
- 位 15 OVER8:过采样模式
- 0:16 倍过采样
- 1:8 倍过采样
- 位 13 UE:USART 使能
- 0:关闭 USART 预分频器和输出,降低功耗
- 1:使能 USART 功能
- 位 12 M:字长选择
- 0:1 起始位 + 8 数据位 + n 停止位
- 1:1 起始位 + 9 数据位 + n 停止位
- 数据传输过程中禁止修改
- 位 10 PCE:奇偶校验控制使能
- 0:禁止奇偶校验
- 1:使能奇偶校验(发送插入、接收校验)
- 位 9 PS:奇偶校验选择(PCE=1 时有效)
- 0:偶校验
- 1:奇校验
- 位 7 TXEIE:TXE 中断使能
- 0:禁止 TXE 中断
- 1:允许 TXE 中断
- 位 5 RXNEIE:RXNE 中断使能
- 0:禁止 RXNE/ORE 中断
- 1:允许 RXNE/ORE 中断
- 位 3 TE:发送器使能
- 0:关闭发送器
- 1:开启发送器,置 1 后有 1 位时间延迟
- 位 2 RE:接收器使能
- 0:关闭接收器
- 1:开启接收器,开始搜索起始位
- 位 15 OVER8:过采样模式
- 控制寄存器 2 (USART_CR2)
- 位 13:12 STOP [1:0]:停止位配置
- 00:1 个停止位
- 01:0.5 个停止位(UART4/5 不支持)
- 10:2 个停止位
- 11:1.5 个停止位(UART4/5 不支持)
- 位 14 LINEN:LIN 模式使能
- 0:禁止 LIN 模式
- 1:使能 LIN 模式,支持断路检测与发送
- 位 3:0 ADD [3:0]:多处理器通信地址
- 配置 USART 节点地址,静音模式下通过地址标记唤醒
- 位 13:12 STOP [1:0]:停止位配置
- 控制寄存器 3 (USART_CR3)
- 位 7 DMAT:发送器 DMA 使能
- 0:禁止发送 DMA
- 1:使能发送 DMA,支持多缓冲区数据发送
- 位 6 DMAR:接收器 DMA 使能
- 0:禁止接收 DMA
- 1:使能接收 DMA,支持多缓冲区数据接收
- 位 3 HDSEL:半双工模式选择
- 0:禁止半双工(双线模式)
- 1:使能半双工(单线模式)
- 位 1 IREN:IrDA 模式使能
- 0:禁止 IrDA 模式
- 1:使能 IrDA 红外通信模式
- 位 0 EIE:错误中断使能(多缓冲区)
- 0:禁止 FE/ORE/NF 错误中断
- 1:允许 FE/ORE/NF 错误中断(需 DMAR=1)
- 位 7 DMAT:发送器 DMA 使能
- 状态寄存器 (USART_SR)
-
过采样
过采样是 USART 异步接收时,以高于波特率的频率对 RX 信号多次采样,靠多数表决或中点采样提升抗噪能力与时钟同步精度,是无时钟线异步通信的关键技术。STM32 USART 支持16 倍(默认,OVER8=0) 和8 倍(OVER8=1) 两种模式,通过 USART_CR1 的 OVER8 位配置
- 16 倍过采样(OVER8=0)
- 采样频率:每个位周期(Tb)采样 16 次,间隔 1/16 Tb。
- 时钟计算:USARTDIV(分频系数) = f_ck/(16×BaudRate),最大波特率 f_ck/16。
- 起始位检测:下降沿触发后,验证前 4 个采样为 1110,后续中点区域连续低电平确认。
- 数据判决:取第 8、9、10 次采样(位中点)多数表决,抗噪与时钟容错更强(约 ±4.5% 偏差)。
- 适用场景:常规 / 高噪声 / 时钟精度一般,优先选此模式。
- 8 倍过采样(OVER8=1)
- 采样频率:每个位周期采样 8 次,间隔 1/8 Tb。
- 时钟计算:USARTDIV(分频系数) = f_ck/(8×BaudRate),最大波特率 f_ck/8(同时钟下比 16 倍快一倍)。
- 起始位检测:下降沿触发,前 4 个采样 1110,第 4 次采样确认起始位。
- 数据判决:取第 4、5、6 次采样多数表决,抗噪与时钟容错略降(约 ±2% 偏差)。
- 适用场景:需超高波特率且噪声可控的场景。
通过设置USARTDIV(分频系数) 设置比特率,即先确定要设置的比特率,代入USARTDIV(分频系数) = f_ck/(8×(2-(OVER8))BaudRate)计算出USARTDIV,写入波特率寄存器USART_BRR;
注意:USARTDIV要分尾数(整数)部分和小数部分分别写入USART_BRR的位 15:4(DIV_Mantissa[11:0]:USARTDIV 的尾数)和位 3:0(DIV_Fraction[3:0]:USARTDIV 的小数)。
- 16 倍过采样(OVER8=0)
数据发送函数
/**
* @brief 通过USART发送字符串
* @param str 要发送的字符串指针,以'\0'结尾
* @retval None
* @note 使用轮询方式等待发送寄存器为空
* 适用于非实时性要求场景
* 中断中不建议调用此函数
*/
void UART_SendString(char *str)
{
// 遍历字符串直到结束符
while (*str != '\0')
{
// 等待发送数据寄存器为空(TXE:Transmit Data Register Empty)
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
// 发送当前字符
USART_SendData(USART1, *str++);
}
// 等待最后一字节发送完成(TC:Transmission Complete)
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
中断服务函数
/**
* @brief USART1中断服务函数,处理接收数据
* @param None
* @retval None
* @note 当USART1接收到数据时触发此中断
*/
void USART1_IRQHandler(void)
{
uint8_t data = 0;
// 检查是否为接收中断(RXNE:Receive Data Register Not Empty)
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
//清除中断标志位
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
// 读取接收到的数据(读取后RXNE自动清零)
data = USART_ReceiveData(USART1);
// 示例:将接收到的数据原样发送回PC(回声功能)
USART_SendData(USART1, data);
// 等待发送完成(可选,确保数据发送完毕)
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
}
串口调试工具(PortHelper.exe)

蓝牙模块原理与应用
无线通信技术对比
| 特性 | BLE(蓝牙低功耗) | Wi-Fi | Zigbee | LoRa |
|---|---|---|---|---|
| 传输距离 | 10-100米 | 30-300米(室内) | 10-100米(室内) | 2-30公里 |
| 数据速率 | 2Mbps(BLE 5.3) | 9.6Gbps(Wi-Fi 6) | 250kbps | 0.3-50kbps |
| 工作频段 | 2.4GHz | 2.4/5/6GHz | 2.4GHz/868/915MHz | Sub-GHz |
| 功耗 | 极低 | 高 | 低 | 极低 |
| 主要应用 | 穿戴设备、IoT | 互联网接入 | 智能家居、工业控制 | 远距离IoT |
蓝牙4.0模块概述

- 蓝牙4.0 BLE模块:
- 低功耗、低成本、小体积
- 采用TI CC2541芯片
- 支持AT指令配置
- 主从一体模式
- 技术参数:
- 协议:Bluetooth 4.0 BLE
- 工作频率:2.4GHz ISM频段
- 调制方式:GFSK(高斯频移键控)
- 传输速率:异步6kbps
- 供电:3.3V DC,50mA
- 灵敏度:≤-84dBm @ 0.1% BER
蓝牙模块状态指示
| 模式 | LED显示 | 模块状态 |
|---|---|---|
| 主模式 | 慢闪(300ms开/关) | 搜索及连接中 |
| 主模式 | 常亮 | 连接已建立 |
| 从模式 | 慢闪(800ms开/关) | 等待配对 |
| 从模式 | 常亮 | 连接已建立 |
蓝牙AT指令配置
/**
* @brief 配置蓝牙模块参数示例
* @param None
* @retval None
* @note AT指令需以\r\n结尾
* 每条指令后需适当延时等待模块响应
* 配置完成后需复位使设置生效
*/
void Bluetooth_ConfigExample(void)
{
// 1. 测试连接(应返回OK)
UART2_SendStr("AT\r\n");
delay_ms(500);
// 2. 修改蓝牙名称(最大20字符)
UART2_SendStr("AT+NAMEYouEmbedded\r\n");
delay_ms(500);
// 3. 修改配对码(4位或6位数字)
UART2_SendStr("AT+PIN123456\r\n");
delay_ms(500);
// 4. 修改波特率(需与MCU配置一致)
UART2_SendStr("AT+BAUD4\r\n"); // 4对应9600bps
delay_ms(500);
// 5. 复位模块使设置生效
UART2_SendStr("AT+RESET\r\n");
delay_ms(1000);
}
常用AT指令表
| 指令 | 功能 | 响应 |
|---|---|---|
| AT | 测试连接 | OK |
| AT+NAME<name> | 设置设备名称 | OK |
| AT+PIN<pin> | 设置配对码 | OK |
| AT+BAUD<n> | 设置波特率(1-8) | OK |
| AT+ROLE<n> | 设置主从模式 | OK |
| AT+RESET | 软件复位 | OK |
| AT+VERSION | 查询版本 | +VERSION:<ver> |
波特率对应表:
- 1: 1200bps
- 2: 2400bps
- 3: 4800bps
- 4: 9600bps(默认)
- 5: 19200bps
- 6: 38400bps
- 7: 57600bps
- 8: 115200bps
蓝牙数据透传
- 透传模式:蓝牙连接建立后自动进入,透明传输串口数据
- 应用场景:手机APP通过蓝牙控制MCU外设
- 数据格式:自定义应用层协议
自定义数据协议设计
协议帧结构设计
/**
* @brief 自定义串口通信协议帧结构
* @note 一帧数据包含:包头 + 数据 + 校验 + 包尾
* 和校验算法:所有字节相加取低8位
*/
// 协议定义
#define PACKET_HEADER 0xAA // 包头标志
#define PACKET_TAIL 0xBB // 包尾标志
#define BUFFER_SIZE 4 // 数据包总长度
// 状态机状态定义
typedef enum {
WAIT_HEADER = 0, // 等待包头
WAIT_DATA, // 等待数据
WAIT_CHECK, // 等待校验
WAIT_TAIL // 等待包尾
} PacketState;
// 数据包结构体
typedef struct {
uint8_t buffer[BUFFER_SIZE]; // 接收缓冲区
PacketState state; // 当前状态
uint8_t index; // 缓冲区索引
} PacketParser;
状态机解析实现
/**
* @brief 状态机方式解析数据包
* @param parser 数据包解析器指针
* @param byte 接收到的单个字节
* @retval bool 解析完成返回true,否则false
* @note 每收到一个字节调用一次
* 校验失败时丢弃整个数据包
* 支持断包、粘包处理
*/
bool ParsePacket(PacketParser* parser, uint8_t byte)
{
switch (parser->state) {
case WAIT_HEADER:
if (byte == PACKET_HEADER) {
parser->buffer[0] = byte;
parser->index = 1;
parser->state = WAIT_DATA;
}
break;
case WAIT_DATA:
parser->buffer[1] = byte; // 数据字节
parser->state = WAIT_CHECK;
break;
case WAIT_CHECK:
parser->buffer[2] = byte; // 校验字节
parser->state = WAIT_TAIL;
break;
case WAIT_TAIL:
if (byte == PACKET_TAIL) {
parser->buffer[3] = byte;
// 计算校验和(包头+数据+包尾)
uint8_t checksum = (parser->buffer[0] +
parser->buffer[1] +
parser->buffer[3]) & 0xFF;
// 校验通过
if (checksum == parser->buffer[2]) {
parser->state = WAIT_HEADER; // 重置状态
return true; // 解析成功
}
}
// 校验失败或包尾错误,重置状态机
parser->state = WAIT_HEADER;
break;
}
return false; // 解析未完成
}
应用层指令定义
/**
* @brief 应用层指令码定义
* @note 指令码为单字节,可扩展至多字节
* 实际应用中可根据需求定义更多指令
*/
typedef enum {
CMD_LED_ON = 0xA1, // 打开LED
CMD_LED_OFF = 0xA2, // 关闭LED
CMD_BEEP_ON = 0xB1, // 打开蜂鸣器
CMD_BEEP_OFF = 0xB2, // 关闭蜂鸣器
CMD_SERVO_SET = 0xC1, // 设置舵机角度
CMD_GET_TEMP = 0xD1, // 获取温度
CMD_GET_HUMI = 0xD2 // 获取湿度
} CommandCode;
指令处理示例
/**
* @brief 处理解析完成的数据包
* @param packet 完整的数据包缓冲区
* @retval None
* @note 根据指令码执行相应操作
* 可扩展为支持参数化指令
*/
void ProcessPacket(uint8_t* packet)
{
uint8_t command = packet[1]; // 数据字节即指令码
switch (command) {
case CMD_LED_ON:
// LED亮
break;
case CMD_LED_OFF:
// LED灭
break;
case CMD_BEEP_ON:
// 蜂鸣器控制代码
break;
case CMD_BEEP_OFF:
// 蜂鸣器控制代码
break;
case CMD_SERVO_SET:
// 舵机角度设置,需解析参数
break;
default:
// 未知指令处理
break;
}
}
完整控灯示例
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"
#include <stdbool.h>
#include <string.h>
/* Private typedef -----------------------------------------------------------*/
// 数据包解析状态枚举
typedef enum {
WAIT_HEADER = 0, // 等待包头
WAIT_DATA, // 等待数据
WAIT_CHECK, // 等待校验
WAIT_TAIL // 等待包尾
} PacketState;
// 数据包解析器结构体
typedef struct {
uint8_t buffer[4]; // 数据包缓冲区(包头/数据/校验/包尾)
PacketState state; // 当前解析状态
uint8_t index; // 缓冲区索引(兼容参考风格)
} PacketParser;
// 指令码枚举
typedef enum {
CMD_LED_ON = 0xA1,
CMD_LED_OFF = 0xA2,
} CmdCode;
/* Private define ------------------------------------------------------------*/
// 系统配置
#define SYS_CLOCK_FREQ_MHZ 21 // SysTick时钟频率(MHz)
#define NVIC_PRIORITY_GROUP NVIC_PriorityGroup_4
// 数据包格式
#define PACKET_HEADER 0xAA
#define PACKET_TAIL 0xBB
#define PACKET_LEN 4
// 硬件引脚
#define LED_PIN GPIO_Pin_9
#define LED_PORT GPIOF
// UART配置
#define UART_PC USART1
#define UART_BLE USART2
#define UART_DEFAULT_BAUDRATE 9600
/* Private macro -------------------------------------------------------------*/
#define CALC_CHECKSUM(header, data, tail) ((header + data + tail) & 0xFF)
#define LED_ON() GPIO_ResetBits(LED_PORT, LED_PIN)
#define LED_OFF() GPIO_SetBits(LED_PORT, LED_PIN)
/* Private variables ---------------------------------------------------------*/
PacketParser g_packet_parser = {0}; // 全局数据包解析器
volatile bool g_packet_parsed_ok = false; // 数据包解析完成标志
/* Private function prototypes -----------------------------------------------*/
static void delay_us(uint32_t nus);
static void delay_ms(uint32_t nms);
static void LED_Init(void);
static void UART_PC_Init(uint32_t baudrate);
static void UART_BLE_Init(uint32_t baudrate);
static void UART_SendString(USART_TypeDef* uart, const char* str);
static void BLE_ConfigDefaultParams(void);
bool ParsePacket(PacketParser* parser, uint8_t byte);
void ProcessPacket(uint8_t* packet);
/* Private functions ---------------------------------------------------------*/
/**
* @brief 微秒级延时函数
* @param nus: 延时时间(us)
* @retval None
*/
static void delay_us(uint32_t nus)
{
if (nus == 0) return;
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
SysTick->LOAD = (uint32_t)(nus * SYS_CLOCK_FREQ_MHZ) - 1;
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
/**
* @brief 毫秒级延时函数
* @param nms: 延时时间(ms)
* @retval None
*/
static void delay_ms(uint32_t nms)
{
while (nms--) {
delay_us(1000);
}
}
/**
* @brief LED初始化
* @param None
* @retval None
*/
static void LED_Init(void)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
gpio_init_struct.GPIO_Pin = LED_PIN;
gpio_init_struct.GPIO_Mode = GPIO_Mode_OUT;
gpio_init_struct.GPIO_OType = GPIO_OType_PP;
gpio_init_struct.GPIO_Speed = GPIO_Speed_100MHz;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(LED_PORT, &gpio_init_struct);
LED_OFF();
}
/**
* @brief 初始化连接PC的UART1
* @param baudrate: 波特率
* @retval None
*/
static void UART_PC_Init(uint32_t baudrate)
{
GPIO_InitTypeDef gpio_init_struct;
USART_InitTypeDef uart_init_struct;
NVIC_InitTypeDef nvic_init_struct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
gpio_init_struct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
gpio_init_struct.GPIO_OType = GPIO_OType_PP;
gpio_init_struct.GPIO_Speed = GPIO_Speed_100MHz;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &gpio_init_struct);
uart_init_struct.USART_BaudRate = baudrate;
uart_init_struct.USART_WordLength = USART_WordLength_8b;
uart_init_struct.USART_StopBits = USART_StopBits_1;
uart_init_struct.USART_Parity = USART_Parity_No;
uart_init_struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
uart_init_struct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(UART_PC, &uart_init_struct);
nvic_init_struct.NVIC_IRQChannel = USART1_IRQn;
nvic_init_struct.NVIC_IRQChannelPreemptionPriority = 1;
nvic_init_struct.NVIC_IRQChannelSubPriority = 0;
nvic_init_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init_struct);
USART_ITConfig(UART_PC, USART_IT_RXNE, ENABLE);
USART_Cmd(UART_PC, ENABLE);
}
/**
* @brief 初始化连接蓝牙的UART2
* @param baudrate: 波特率
* @retval None
*/
static void UART_BLE_Init(uint32_t baudrate)
{
GPIO_InitTypeDef gpio_init_struct;
USART_InitTypeDef uart_init_struct;
NVIC_InitTypeDef nvic_init_struct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
gpio_init_struct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF;
gpio_init_struct.GPIO_OType = GPIO_OType_PP;
gpio_init_struct.GPIO_Speed = GPIO_Speed_100MHz;
gpio_init_struct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &gpio_init_struct);
uart_init_struct.USART_BaudRate = baudrate;
uart_init_struct.USART_WordLength = USART_WordLength_8b;
uart_init_struct.USART_StopBits = USART_StopBits_1;
uart_init_struct.USART_Parity = USART_Parity_No;
uart_init_struct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
uart_init_struct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(UART_BLE, &uart_init_struct);
nvic_init_struct.NVIC_IRQChannel = USART2_IRQn;
nvic_init_struct.NVIC_IRQChannelPreemptionPriority = 1;
nvic_init_struct.NVIC_IRQChannelSubPriority = 1;
nvic_init_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init_struct);
USART_ITConfig(UART_BLE, USART_IT_RXNE, ENABLE);
USART_Cmd(UART_BLE, ENABLE);
}
/**
* @brief UART发送字符串
* @param uart: UART外设指针
* @param str: 要发送的字符串
* @retval None
*/
static void UART_SendString(USART_TypeDef* uart, const char* str)
{
if (str == NULL || uart == NULL) return;
while (*str != '\0') {
while (USART_GetFlagStatus(uart, USART_FLAG_TXE) == RESET);
USART_SendData(uart, (uint8_t)*str++);
}
while (USART_GetFlagStatus(uart, USART_FLAG_TC) == RESET);
}
/**
* @brief 配置蓝牙模块默认参数
* @param None
* @retval None
*/
static void BLE_ConfigDefaultParams(void)
{
UART_SendString(UART_BLE, "AT\r\n");
delay_ms(500);
UART_SendString(UART_BLE, "AT+NAMEYouEmbedded\r\n");
delay_ms(500);
UART_SendString(UART_BLE, "AT+RESET\r\n");
delay_ms(500);
}
/**
* @brief 状态机方式解析数据包
* @param parser 数据包解析器指针
* @param byte 接收到的单个字节
* @retval bool 解析完成返回true,否则false
* @note 每收到一个字节调用一次
* 校验失败时丢弃整个数据包
* 支持断包、粘包处理
*/
bool ParsePacket(PacketParser* parser, uint8_t byte)
{
if (parser == NULL) return false;
switch (parser->state) {
case WAIT_HEADER:
if (byte == PACKET_HEADER) {
parser->buffer[0] = byte;
parser->index = 1;
parser->state = WAIT_DATA;
}
break;
case WAIT_DATA:
parser->buffer[1] = byte;
parser->state = WAIT_CHECK;
break;
case WAIT_CHECK:
parser->buffer[2] = byte;
parser->state = WAIT_TAIL;
break;
case WAIT_TAIL:
if (byte == PACKET_TAIL) {
parser->buffer[3] = byte;
// 计算校验和
uint8_t checksum = CALC_CHECKSUM(parser->buffer[0],
parser->buffer[1],
parser->buffer[3]);
// 校验通过
if (checksum == parser->buffer[2]) {
parser->state = WAIT_HEADER;
return true;
}
}
// 校验失败或包尾错误,重置状态机
parser->state = WAIT_HEADER;
break;
default:
parser->state = WAIT_HEADER;
break;
}
return false;
}
/**
* @brief 处理解析完成的数据包
* @param packet 完整的数据包缓冲区
* @retval None
* @note 根据指令码执行相应操作
* 可扩展为支持参数化指令
*/
void ProcessPacket(uint8_t* packet)
{
if (packet == NULL) return;
uint8_t command = packet[1];
switch (command) {
case CMD_LED_ON:
LED_ON();
break;
case CMD_LED_OFF:
LED_OFF();
break;
default:
break;
}
}
/**
* @brief USART1中断服务函数(PC端UART)
* @param None
* @retval None
*/
void USART1_IRQHandler(void)
{
uint8_t recv_data = 0;
if (USART_GetITStatus(UART_PC, USART_IT_RXNE) == SET)
{
recv_data = USART_ReceiveData(UART_PC);
// 数据回显
while (USART_GetFlagStatus(UART_PC, USART_FLAG_TXE) == RESET);
USART_SendData(UART_PC, recv_data);
USART_ClearITPendingBit(UART_PC, USART_IT_RXNE);
}
}
/**
* @brief USART2中断服务函数(蓝牙模块UART)
* @param None
* @retval None
*/
void USART2_IRQHandler(void)
{
uint8_t recv_data = 0;
if (USART_GetITStatus(UART_BLE, USART_IT_RXNE) == SET)
{
recv_data = USART_ReceiveData(UART_BLE);
// 调用封装的解析函数
if (ParsePacket(&g_packet_parser, recv_data)) {
g_packet_parsed_ok = true;
}
// 转发数据到PC端调试
while (USART_GetFlagStatus(UART_PC, USART_FLAG_TXE) == RESET);
USART_SendData(UART_PC, recv_data);
USART_ClearITPendingBit(UART_BLE, USART_IT_RXNE);
}
}
/**
* @brief 主函数
* @param None
* @retval None
*/
int main(void)
{
// 1.初始化NVIC优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
// 2.初始化解析器
memset(&g_packet_parser, 0, sizeof(PacketParser));
g_packet_parser.state = WAIT_HEADER;
// 3.硬件初始化
LED_Init();
UART_BLE_Init(UART_DEFAULT_BAUDRATE);
UART_PC_Init(UART_DEFAULT_BAUDRATE);
// 4.配置蓝牙模块
BLE_ConfigDefaultParams();
// 主循环
while (1)
{
if (g_packet_parsed_ok)
{
g_packet_parsed_ok = false;
// 调用封装的处理函数
ProcessPacket(g_packet_parser.buffer);
// 清空缓冲区
memset(g_packet_parser.buffer, 0, PACKET_LEN);
}
}
}
注意:手机可以使用蓝牙调试助手app或小程序通过蓝牙模块向MCU发送消息
常见问题与解决方案
串口通信乱码问题
- 可能原因1:波特率不匹配
- 检查MCU与PC端波特率设置是否一致
- 常用波特率:9600、115200
- 可能原因2:时钟配置错误
- STM32F4默认HSE=25MHz,假设实际为8MHz(具体查看产品原理图), 需修改配置:
- 修改
system_stm32f4xx.c第316行:PLL_M从25改为8 - 修改
stm32f4xx.h第123行:HSE_VALUE从25000000改为8000000
- 修改
- STM32F4默认HSE=25MHz,假设实际为8MHz(具体查看产品原理图), 需修改配置:
- 可能原因3:数据格式不一致
- 确认数据位、停止位、校验位设置
- 最常见格式:8位数据、1位停止、无校验
- 可能原因4:硬件连接问题
- 检查TX、RX是否交叉连接(设备TX接MCU RX)
- 确认地线(GND)已连接
- 检查电源电压是否稳定
蓝牙连接失败
- 检查步骤:
- 蓝牙模块供电是否正常
- AT指令是否能正常响应
- 手机蓝牙是否开启并搜索到设备
- 配对密码是否正确
- 模块是否处于可配对状态(LED慢闪)
- 解决方法:
- 使用AT指令恢复出厂设置:
AT+DEFAULT - 重新配置名称、密码、波特率
- 重启模块:
AT+RESET
- 使用AT指令恢复出厂设置:
printf函数重定向
重定向原理
- 问题:嵌入式系统无标准输出设备,printf无法直接使用
- 解决方案:重定向printf到串口,通过串口调试助手查看输出
实现方法
/**
* @brief 重定向printf到USART1
* @param ch 要发送的字符
* @param f 文件指针(未使用)
* @retval int 成功返回字符,失败返回EOF
* @note 需包含stdio.h头文件
* 在Options for Target中勾选Use MicroLIB
* 避免在中断中调用printf
*/
#include <stdio.h>
int fputc(int ch, FILE *f)
{
// 等待发送寄存器为空
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
// 发送字符
USART_SendData(USART1, (uint8_t)ch);
return ch;
}
Keil环境配置
启用MicroLIB(减小代码体积):
- Options for Target → Target → 勾选"Use MicroLIB"
包含标准库:
#include <stdio.h>
#include <string.h>
使用示例:
int value = 123;
float temp = 25.5;
printf("系统启动成功!\r\n");
printf("整数值:%d\r\n", value);
printf("温度值:%.1f℃\r\n", temp);
printf("字符串:%s\r\n", "Hello World");

注意事项
- 避免在中断中使用printf:执行时间长,可能引起系统不稳定
- 格式化字符串占用资源:大量使用可能增加代码体积
- 实时性要求:高实时性场景建议使用直接串口发送函数

浙公网安备 33010602011771号