实现蓝牙与手机通信

一、蓝牙引脚定义

二、知识点补充

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通信三要素

  • 波特率一致

  • 数据格式一致

  • 物理连接正确

相关推荐
禾从道19 小时前
「杂想」未来的AI电子设备和胡思乱想。
人工智能·智能手机·创业创新·小米·豆包手机
南山电子nscn2 天前
长晶高PSRR LDO产品在高速相机与手机摄像头中的应用分析
数码相机·智能手机·ldo
wanhengidc2 天前
互联网 高端科技 云手机
科技·智能手机
weixin_478796342 天前
航空接头.
智能手机·硬件工程·射频工程
TheNextByte13 天前
如何从红米手机恢复已删除的音乐文件?
智能手机
千里马学框架3 天前
重学SurfaceFlinger之Layer显示区域bounds计算剖析
android·智能手机·sf·安卓framework开发·layer·surfaceflinger·车载开发
专业开发者3 天前
一款可穿戴设备如何同时与多部智能手机或平板电脑建立连接
物联网·智能手机·电脑
wanhengidc3 天前
巨 椰 云手机 性能稳定
运维·服务器·arm开发·智能手机·云计算
造火箭3 天前
普通手机使用Open-AutoGLM 感受豆包AI 手机的体验
人工智能·智能手机