一、蓝牙引脚定义



二、知识点补充
1. 什么是串口通信(UART)
蓝牙模块 ↔ STM32
TXD → RXD (蓝牙发送,STM32接收)
RXD ← TXD (蓝牙接收,STM32发送)
-
TXD:Transmit Data(发送数据)
-
RXD:Receive Data(接收数据)
-
就像两个人说话:一个说(TX),一个听(RX)
2. 数据格式(9600-8-N-1)

-
9600:波特率,每秒传输9600个bit
-
8:每个字节8个数据位
-
N:No Parity,无校验位
-
1:1个停止位
3.如何接收数据
1.流程图

2. 代码详解 - 接收部分
cpp
// bluetooth.c - 接收数据详解
// 全局变量:保存用户注册的回调函数
static Bluetooth_RecvCallback userCallback = NULL;
/**
* UART5中断服务函数
* 这是STM32硬件自动调用的,当有数据到达时
*/
void UART5_IRQHandler(void)
{
uint8_t receivedData;
// 步骤1:检查是否是"接收中断"(数据来了)
// USART_IT_RXNE = Receive register Not Empty(接收寄存器非空)
if (USART_GetITStatus(UART5, USART_IT_RXNE) != RESET)
{
// 步骤2:清除中断标志(告诉硬件:我收到了,可以准备下一个)
USART_ClearITPendingBit(UART5, USART_IT_RXNE);
// 步骤3:从UART数据寄存器读取1个字节
// 这就像从邮箱里拿信
receivedData = USART_ReceiveData(UART5) & 0xFF;
// & 0xFF 确保只取低8位
// 步骤4:如果用户注册了回调函数,就调用它
if (userCallback != NULL)
{
// 把收到的数据传给用户的函数处理
userCallback(receivedData);
}
}
}
3. 通俗理解:快递员送包裹
cpp
// 想象场景:
// 1. 蓝牙模块 = 快递员
// 2. UART5 = 你家门铃
// 3. 中断函数 = 你开门接快递
// 4. 回调函数 = 你处理快递(拆开看是什么)
void UART5_IRQHandler(void) // 相当于门铃响了
{
// 检查是不是快递到了(不是外卖,不是送水的)
if (USART_GetITStatus(UART5, USART_IT_RXNE) != RESET)
{
// 开门签收快递
USART_ClearITPendingBit(UART5, USART_IT_RXNE);
// 拿到快递包裹
receivedData = USART_ReceiveData(UART5);
// 看看是谁的快递(回调函数)
if (userCallback != NULL)
{
// 把包裹交给主人处理
userCallback(receivedData);
}
}
}
4.发送数据的过程(轮询方式)
1.流程图

2. 代码详解 - 发送部分
cpp
// bluetooth.c - 发送数据详解
/**
* 发送一个字节
* 这是"轮询"方式:不断检查状态
*/
void Bluetooth_SendByte(uint8_t data)
{
// 步骤1:等待发送缓冲区为空
// 想象:等前一个人写完信,你才能用桌子
// USART_FLAG_TXE = Transmit register Empty(发送寄存器空)
while (USART_GetFlagStatus(UART5, USART_FLAG_TXE) == RESET)
{
// 如果缓冲区忙,就循环等待
// 这里可以加超时保护
}
// 步骤2:把数据放入发送寄存器
// 就像把信投进邮筒
USART_SendData(UART5, data);
// 步骤3:等待发送完成
// 想象:等待邮差把信取走
// USART_FLAG_TC = Transmission Complete(传输完成)
while (USART_GetFlagStatus(UART5, USART_FLAG_TC) == RESET)
{
// 等待数据完全发出
// 这里可以加超时保护
}
// 现在数据已经从TXD引脚发出去了!
}
/**
* 发送字符串(挨个发送每个字符)
*/
void Bluetooth_SendString(const char *str)
{
if (str == NULL) return;
// 遍历字符串的每个字符
while (*str != '\0') // \0是字符串结束标志
{
// 发送当前字符
Bluetooth_SendByte((uint8_t)(*str));
// 指针移动到下一个字符
str++;
}
}
3. 通俗理解:写信寄信
cpp
// 想象场景:
// 1. USART发送寄存器 = 你的写字桌
// 2. 发送数据 = 写信
// 3. TXD引脚 = 邮筒
void Bluetooth_SendByte(uint8_t data)
{
// 步骤1:看看桌子上有没有东西(别人在用吗?)
while (USART_GetFlagStatus(UART5, USART_FLAG_TXE) == RESET)
{
// 如果桌子上有信,就等着...
// 相当于:while(桌子不空) { 等待; }
}
// 步骤2:桌子上空了,开始写信
USART_SendData(UART5, data);
// 相当于:把写好的信放在桌子上
// 步骤3:等待邮差来取信
while (USART_GetFlagStatus(UART5, USART_FLAG_TC) == RESET)
{
// 邮差还没来取信,等着...
// 相当于:while(信还在桌上) { 等待; }
}
// 步骤4:邮差取走了信(数据发出去了)
}
三、代码实现
1. bluetooth.h
cpp
#ifndef __BLUETOOTH_H
#define __BLUETOOTH_H
#include "stm32f10x.h"
#include <stdint.h>
// 蓝牙模块串口配置 - UART5
#define BLUETOOTH_USART UART5
#define BLUETOOTH_USART_CLK RCC_APB1Periph_UART5
#define BLUETOOTH_USART_IRQn UART5_IRQn
#define BLUETOOTH_USART_IRQHandler UART5_IRQHandler
// UART5 GPIO配置
// TX: PC12, RX: PD2
#define BLUETOOTH_TX_GPIO GPIOC
#define BLUETOOTH_TX_PIN GPIO_Pin_12
#define BLUETOOTH_TX_GPIO_CLK RCC_APB2Periph_GPIOC
#define BLUETOOTH_TX_PIN_SOURCE GPIO_PinSource12
#define BLUETOOTH_RX_GPIO GPIOD
#define BLUETOOTH_RX_PIN GPIO_Pin_2
#define BLUETOOTH_RX_GPIO_CLK RCC_APB2Periph_GPIOD
#define BLUETOOTH_RX_PIN_SOURCE GPIO_PinSource2
// 波特率配置
#define BLUETOOTH_BAUDRATE 9600
// 回调函数类型定义
typedef void (*Bluetooth_RecvCallback)(uint8_t data);
// 蓝牙模块初始化状态
typedef enum {
BLUETOOTH_OK = 0,
BLUETOOTH_ERROR = 1
} Bluetooth_Status;
// 函数声明
Bluetooth_Status Bluetooth_Init(Bluetooth_RecvCallback recvCallback);
void Bluetooth_SendByte(uint8_t data);
void Bluetooth_SendString(const char *str);
void Bluetooth_DeInit(void);
#endif /* __BLUETOOTH_H */
2. bluetooth.c
cpp
#include "bluetooth.h"
#include <string.h>
// 静态变量声明
static Bluetooth_RecvCallback userCallback = NULL;
/**
* @brief 蓝牙模块初始化
* @param recvCallback: 数据接收回调函数指针
* @retval Bluetooth_Status: 初始化状态
*/
Bluetooth_Status Bluetooth_Init(Bluetooth_RecvCallback recvCallback)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 保存回调函数
if (recvCallback == NULL)
{
return BLUETOOTH_ERROR;
}
userCallback = recvCallback;
// 1. 使能UART5和GPIO时钟
// UART5时钟在APB1上
RCC_APB1PeriphClockCmd(BLUETOOTH_USART_CLK, ENABLE);
// GPIO时钟在APB2上
RCC_APB2PeriphClockCmd(BLUETOOTH_TX_GPIO_CLK | BLUETOOTH_RX_GPIO_CLK, ENABLE);
// 2. 配置UART5 TX引脚(PC12)为复用推挽输出
GPIO_InitStructure.GPIO_Pin = BLUETOOTH_TX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BLUETOOTH_TX_GPIO, &GPIO_InitStructure);
// 3. 配置UART5 RX引脚(PD2)为浮空输入
GPIO_InitStructure.GPIO_Pin = BLUETOOTH_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BLUETOOTH_RX_GPIO, &GPIO_InitStructure);
// 4. 配置UART5参数
USART_InitStructure.USART_BaudRate = BLUETOOTH_BAUDRATE; // 9600波特率
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(BLUETOOTH_USART, &USART_InitStructure);
// 5. 使能UART5接收中断
USART_ITConfig(BLUETOOTH_USART, USART_IT_RXNE, ENABLE);
// 6. 配置UART5中断优先级
NVIC_InitStructure.NVIC_IRQChannel = BLUETOOTH_USART_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 7. 使能UART5
USART_Cmd(BLUETOOTH_USART, ENABLE);
return BLUETOOTH_OK;
}
/**
* @brief 发送单个字节数据到蓝牙模块
* @param data: 要发送的字节数据
* @retval 无
*/
void Bluetooth_SendByte(uint8_t data)
{
// 等待发送缓冲区为空
while (USART_GetFlagStatus(BLUETOOTH_USART, USART_FLAG_TXE) == RESET)
{
// 超时处理(可选)
}
// 发送数据
USART_SendData(BLUETOOTH_USART, data);
// 等待发送完成
while (USART_GetFlagStatus(BLUETOOTH_USART, USART_FLAG_TC) == RESET)
{
// 超时处理(可选)
}
}
/**
* @brief 发送字符串到蓝牙模块
* @param str: 要发送的字符串(以\0结尾)
* @retval 无
*/
void Bluetooth_SendString(const char *str)
{
if (str == NULL)
return;
// 遍历字符串,发送每个字符
while (*str != '\0')
{
Bluetooth_SendByte((uint8_t)(*str));
str++;
}
}
/**
* @brief UART5中断服务函数
* @param 无
* @retval 无
*/
void BLUETOOTH_USART_IRQHandler(void)
{
uint8_t receivedData;
// 检查是否是接收中断
if (USART_GetITStatus(BLUETOOTH_USART, USART_IT_RXNE) != RESET)
{
// 清除接收中断标志
USART_ClearITPendingBit(BLUETOOTH_USART, USART_IT_RXNE);
// 读取接收到的数据
receivedData = USART_ReceiveData(BLUETOOTH_USART) & 0xFF;
// 调用用户注册的回调函数
if (userCallback != NULL)
{
userCallback(receivedData);
}
}
}
/**
* @brief 蓝牙模块反初始化
* @param 无
* @retval 无
*/
void Bluetooth_DeInit(void)
{
// 禁用UART5
USART_Cmd(BLUETOOTH_USART, DISABLE);
// 禁用UART5中断
USART_ITConfig(BLUETOOTH_USART, USART_IT_RXNE, DISABLE);
// 清除回调函数
userCallback = NULL;
}
3.main.c
cpp
#include "stm32f10x.h"
#include "bluetooth.h"
#include <string.h>
#include "Delay.h"
/**
* @brief 蓝牙数据接收回调函数
* @param data: 接收到的字节数据
* @retval 无
*/
void Bluetooth_ReceiveCallback(uint8_t data)
{
// 功能1: 回显接收到的数据给手机
Bluetooth_SendByte(data);
// 功能2: 可以根据接收到的数据控制外设(后续扩展)
// 例如:
// if (data == '1') {
// // 打开LED
// GPIO_SetBits(GPIOC, GPIO_Pin_13);
// } else if (data == '0') {
// // 关闭LED
// GPIO_ResetBits(GPIOC, GPIO_Pin_13);
// }
// 功能3: 可以在这里添加数据解析逻辑
// 例如接收特定指令控制其他外设
}
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
Bluetooth_Status bluetoothStatus;
// 注意:请确保在你的工程中已经初始化了系统时钟
// 初始化蓝牙模块,注册回调函数
bluetoothStatus = Bluetooth_Init(Bluetooth_ReceiveCallback);
if (bluetoothStatus != BLUETOOTH_OK)
{
// 蓝牙初始化失败,可以在这里处理错误
// 例如闪烁LED提示错误
while (1);
}
// 蓝牙初始化成功后,向手机发送提示信息
Bluetooth_SendString("Bluetooth init success! Ready to recv data...\r\n");
// 主循环
while (1)
{
// 降低CPU占用率,每100ms检查一次
// 注意:蓝牙数据接收在中断中处理,不在这里
Delay_ms(100);
// 可以在这里添加其他需要周期性执行的任务
// 例如:读取传感器数据并通过蓝牙发送
}
}
// 以下为错误处理函数(标准库需要)
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1)
{
}
}
#endif
四、 常见问题排查
问题1:收不到数据
-
检查接线:蓝牙TXD → STM32 PD2(UART5_RX)
-
检查波特率:双方必须都是9600
-
检查中断是否使能
问题2:发送数据手机收不到
-
检查接线:STM32 PC12 → 蓝牙RXD
-
检查手机蓝牙是否连接
-
检查手机APP是否正确
问题3:数据乱码
-
检查串口参数:8-N-1(8位数据,无校验,1停止位)
-
检查系统时钟:STM32必须是72MHz
五、核心概念总结
中断 vs 轮询
-
中断:数据来了硬件自动通知你(效率高)
-
轮询:你不断检查有没有数据(简单但耗CPU)
回调函数
-
你把处理函数"注册"给我
-
我收到数据时"回调"你的函数
-
实现模块解耦:蓝牙模块不关心数据怎么处理
UART通信三要素
-
波特率一致
-
数据格式一致
-
物理连接正确