STM32实战:基于STM32F103的蓝牙HC-05模块与手机APP通信

该文章已生成可运行项目,

一、前言

1.1 技术背景

蓝牙技术作为短距离无线通信的主流方案,在物联网、智能家居、工业控制等领域应用广泛。HC-05是一款经典的蓝牙串口透传模块,支持蓝牙2.0+EDR协议,具有成本低、配置简单、稳定性好等特点,非常适合与STM32等嵌入式平台配合使用。

在嵌入式开发中,通过蓝牙实现设备与手机的无线通信,可以大大简化人机交互界面设计,无需复杂的屏幕和按键,仅通过手机APP即可完成参数配置、数据监控、远程控制等功能。

1.2 本文目标

通过本教程,你将学会:

  • HC-05蓝牙模块的工作原理和配置方法
  • STM32F103与HC-05的硬件连接设计
  • 使用USART实现串口通信
  • 编写手机APP进行蓝牙数据收发
  • 实现完整的双向通信系统

适合读者:

  • 有一定STM32基础的嵌入式开发者
  • 希望学习无线通信的物联网爱好者
  • 需要实现手机与单片机通信的项目开发者

1.3 技术栈

组件型号/版本说明
主控芯片STM32F103C8T6ARM Cortex-M3内核
蓝牙模块HC-05蓝牙2.0+EDR串口模块
开发环境Keil MDK 5嵌入式开发IDE
串口工具XCOM V2.0串口调试助手
手机APP蓝牙串口助手安卓蓝牙调试工具

二、环境准备

2.1 硬件准备

核心硬件清单:

  • STM32F103C8T6最小系统板 × 1
  • HC-05蓝牙模块 × 1
  • USB转TTL模块(CH340/CP2102)× 1
  • 杜邦线若干
  • 安卓手机 × 1

硬件连接示意图:

STM32F103C8T6          HC-05蓝牙模块
┌─────────────┐        ┌─────────────┐
│             │        │             │
│    PA9  ────┼────────┼────>  RX    │
│    PA10 <───┼────────┼────  TX     │
│    3.3V ────┼────────┼────  VCC    │
│    GND  ────┼────────┼────  GND    │
│             │        │             │
└─────────────┘        └─────────────┘

引脚定义:

  • PA9 (USART1_TX) → HC-05 RX
  • PA10 (USART1_RX) → HC-05 TX
  • 3.3V → HC-05 VCC
  • GND → HC-05 GND

⚠️ 注意: HC-05的RX引脚需要连接STM32的TX,TX连接RX,即交叉连接。

2.2 软件环境

Keil MDK 5 安装配置:

  1. 下载并安装 Keil MDK-ARM 5.38
  2. 安装 STM32F1xx 系列 Device Family Pack
  3. 配置 ST-Link 调试器驱动

串口调试工具:

  • 推荐:XCOM V2.0、SSCOM、Putty
  • 波特率设置:9600(HC-05默认)

手机APP:

  • 蓝牙串口助手(各大应用商店可下载)
  • 或自行开发APP(可选)

三、HC-05模块详解

3.1 HC-05模块介绍

HC-05是一款主从一体蓝牙串口模块,主要特性:

  • 蓝牙协议:Bluetooth V2.0+EDR
  • 工作频率:2.4GHz ISM频段
  • 通信距离:10米(空旷环境)
  • 串口波特率:4800~1382400bps(默认9600)
  • 工作电压:3.3V~5V(推荐3.3V)
  • 配对密码:1234(默认)

引脚定义:

引脚名称功能说明
1VCC电源正极(3.3V~5V)
2GND电源地
3TXD串口发送(TTL电平)
4RXD串口接收(TTL电平)
5STATE连接状态指示(未连接低电平,连接高电平)
6EN使能引脚(高电平进入AT模式)

3.2 工作模式

HC-05有两种工作模式:

1. 通信模式(默认)

  • 模块上电后自动进入
  • LED快速闪烁(约2Hz)
  • 等待蓝牙设备连接
  • 连接成功后LED常亮

2. AT命令模式

  • 用于配置模块参数
  • 上电前将EN引脚接高电平
  • LED慢速闪烁(约1Hz)
  • 波特率固定为38400

3.3 常用AT指令

进入AT模式后,可通过串口发送以下指令:

指令功能返回
AT测试通信OK
AT+NAME=XXX设置蓝牙名称OK
AT+PIN=1234设置配对密码OK
AT+BAUD=4设置波特率9600OK9600
AT+ROLE=0设置为从机模式OK
AT+ADDR?查询模块地址+ADDR:xxxx

波特率设置对照表:

参数波特率
11200
22400
34800
49600
519200
638400
757600
8115200

四、STM32程序设计

4.1 工程创建与配置

在Keil MDK中创建新工程:

  1. 选择器件:STM32F103C8
  2. 添加启动文件:startup_stm32f10x_md.s
  3. 配置CMSIS和StdPeriph库

时钟配置:

  • 系统时钟:72MHz(使用外部8MHz晶振)
  • APB2总线时钟:72MHz
  • USART1时钟:72MHz

4.2 USART1初始化

📄 创建文件:usart1.c

/**
 * @file usart1.c
 * @brief USART1驱动程序 - 用于与HC-05蓝牙模块通信
 * 
 * 功能:
 * - 初始化USART1,配置波特率9600
 * - 实现数据发送和接收
 * - 支持中断接收模式
 * - 提供环形缓冲区管理
 */

#include "usart1.h"
#include "stm32f10x.h"
#include <string.h>
#include <stdio.h>

/* 环形缓冲区大小 */
#define RX_BUFFER_SIZE 256
#define TX_BUFFER_SIZE 256

/* 接收缓冲区 */
static volatile uint8_t rx_buffer[RX_BUFFER_SIZE];
static volatile uint16_t rx_write_index = 0;
static volatile uint16_t rx_read_index = 0;

/* 发送缓冲区 */
static volatile uint8_t tx_buffer[TX_BUFFER_SIZE];
static volatile uint16_t tx_write_index = 0;
static volatile uint16_t tx_read_index = 0;

/* 接收完成标志 */
static volatile uint8_t rx_complete_flag = 0;

/**
 * @brief 初始化USART1
 * 
 * 配置参数:
 * - 波特率:9600 bps(与HC-05默认波特率匹配)
 * - 数据位:8位
 * - 停止位:1位
 * - 校验位:无
 * - 流控制:无
 * - 工作模式:全双工
 */
void USART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    
    /* 1. 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    /* 2. 配置GPIO引脚 */
    // PA9 - USART1_TX (复用推挽输出)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // PA10 - USART1_RX (浮空输入)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    /* 3. 配置USART参数 */
    USART_InitStruct.USART_BaudRate = 9600;              // 波特率9600
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;  // 8位数据
    USART_InitStruct.USART_StopBits = USART_StopBits_1;  // 1位停止位
    USART_InitStruct.USART_Parity = USART_Parity_No;     // 无校验
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  // 收发模式
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART1, &USART_InitStruct);
    
    /* 4. 配置中断 */
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);  // 使能接收中断
    USART_ITConfig(USART1, USART_IT_TXE, ENABLE);   // 使能发送中断
    
    /* 5. 配置NVIC */
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    
    /* 6. 使能USART */
    USART_Cmd(USART1, ENABLE);
    
    /* 7. 初始化缓冲区 */
    rx_write_index = 0;
    rx_read_index = 0;
    tx_write_index = 0;
    tx_read_index = 0;
    rx_complete_flag = 0;
}

/**
 * @brief 发送单个字符
 * @param ch 要发送的字符
 */
void USART1_SendChar(uint8_t ch)
{
    // 等待发送缓冲区为空
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
}

/**
 * @brief 发送字符串
 * @param str 要发送的字符串
 */
void USART1_SendString(const char *str)
{
    while (*str) {
        USART1_SendChar(*str++);
    }
}

/**
 * @brief 发送数据缓冲区
 * @param data 数据指针
 * @param len 数据长度
 */
void USART1_SendData(const uint8_t *data, uint16_t len)
{
    for (uint16_t i = 0; i < len; i++) {
        USART1_SendChar(data[i]);
    }
}

/**
 * @brief 检查接收缓冲区是否有数据
 * @return 可读数据长度
 */
uint16_t USART1_GetRxDataLength(void)
{
    if (rx_write_index >= rx_read_index) {
        return rx_write_index - rx_read_index;
    } else {
        return RX_BUFFER_SIZE - rx_read_index + rx_write_index;
    }
}

/**
 * @brief 从接收缓冲区读取数据
 * @param data 数据存储缓冲区
 * @param len 要读取的最大长度
 * @return 实际读取的字节数
 */
uint16_t USART1_ReadData(uint8_t *data, uint16_t len)
{
    uint16_t available = USART1_GetRxDataLength();
    uint16_t to_read = (len < available) ? len : available;
    
    for (uint16_t i = 0; i < to_read; i++) {
        data[i] = rx_buffer[rx_read_index];
        rx_read_index = (rx_read_index + 1) % RX_BUFFER_SIZE;
    }
    
    return to_read;
}

/**
 * @brief 读取一行数据(以\n或\r\n结尾)
 * @param line 行缓冲区
 * @param max_len 最大长度
 * @return 实际读取长度,0表示无完整行
 */
uint16_t USART1_ReadLine(char *line, uint16_t max_len)
{
    uint16_t available = USART1_GetRxDataLength();
    uint16_t i = 0;
    uint16_t temp_read_index = rx_read_index;
    
    // 查找换行符
    for (i = 0; i < available && i < max_len - 1; i++) {
        char ch = rx_buffer[temp_read_index];
        temp_read_index = (temp_read_index + 1) % RX_BUFFER_SIZE;
        
        if (ch == '\n') {
            // 找到换行符,读取整行
            uint16_t line_len = i + 1;
            USART1_ReadData((uint8_t *)line, line_len);
            line[line_len] = '\0';
            
            // 处理\r\n情况
            if (line_len > 1 && line[line_len - 2] == '\r') {
                line[line_len - 2] = '\0';
            } else {
                line[line_len - 1] = '\0';
            }
            
            return strlen(line);
        }
    }
    
    return 0;  // 未找到完整行
}

/**
 * @brief 清空接收缓冲区
 */
void USART1_ClearRxBuffer(void)
{
    rx_write_index = 0;
    rx_read_index = 0;
}

/**
 * @brief USART1中断服务程序
 * 
 * 处理接收和发送中断:
 * - 接收中断:将数据存入环形缓冲区
 * - 发送中断:从发送缓冲区取出数据发送
 */
void USART1_IRQHandler(void)
{
    // 接收中断处理
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        uint8_t data = USART_ReceiveData(USART1);
        
        // 存入环形缓冲区
        uint16_t next_index = (rx_write_index + 1) % RX_BUFFER_SIZE;
        if (next_index != rx_read_index) {  // 缓冲区未满
            rx_buffer[rx_write_index] = data;
            rx_write_index = next_index;
        }
        
        // 检查是否为命令结束符
        if (data == '\n' || data == '\r') {
            rx_complete_flag = 1;
        }
    }
    
    // 发送中断处理(可选,用于中断发送模式)
    if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) {
        if (tx_read_index != tx_write_index) {
            USART_SendData(USART1, tx_buffer[tx_read_index]);
            tx_read_index = (tx_read_index + 1) % TX_BUFFER_SIZE;
        } else {
            USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
        }
    }
}

/**
 * @brief 重定向printf到USART1
 * @note 需要在编译选项中勾选"Use MicroLIB"
 */
int fputc(int ch, FILE *f)
{
    USART1_SendChar((uint8_t)ch);
    return ch;
}

📄 创建文件:usart1.h

/**
 * @file usart1.h
 * @brief USART1驱动头文件
 */

#ifndef __USART1_H
#define __USART1_H

#include <stdint.h>

/* 函数声明 */
void USART1_Init(void);
void USART1_SendChar(uint8_t ch);
void USART1_SendString(const char *str);
void USART1_SendData(const uint8_t *data, uint16_t len);
uint16_t USART1_GetRxDataLength(void);
uint16_t USART1_ReadData(uint8_t *data, uint16_t len);
uint16_t USART1_ReadLine(char *line, uint16_t max_len);
void USART1_ClearRxBuffer(void);

#endif /* __USART1_H */

4.3 蓝牙通信协议设计

设计简单的文本协议,便于手机APP解析:

命令格式:

命令格式:<命令字>:<参数>\r\n
示例:
LED:ON\r\n      - 打开LED
LED:OFF\r\n     - 关闭LED
PWM:50\r\n      - 设置PWM占空比50%
READ:ADC\r\n    - 读取ADC值

响应格式:

响应格式:<状态>:<数据>\r\n

示例:
OK:LED_ON\r\n       - LED已打开
OK:PWM_SET_50\r\n   - PWM设置成功
DATA:ADC_2048\r\n   - ADC值为2048
ERR:UNKNOWN_CMD\r\n - 未知命令

📄 创建文件:bluetooth_protocol.c

/**
 * @file bluetooth_protocol.c
 * @brief 蓝牙通信协议处理
 * 
 * 实现命令解析和响应生成
 */

#include "bluetooth_protocol.h"
#include "usart1.h"
#include "led_control.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* 命令缓冲区 */
static char cmd_buffer[CMD_BUFFER_SIZE];

/**
 * @brief 初始化蓝牙协议处理
 */
void BT_Protocol_Init(void)
{
    memset(cmd_buffer, 0, sizeof(cmd_buffer));
}

/**
 * @brief 解析并执行命令
 * @param cmd 命令字符串
 * @return 执行结果
 */
static BT_Status ExecuteCommand(const char *cmd)
{
    char cmd_type[16] = {0};
    char cmd_param[32] = {0};
    
    // 解析命令格式:TYPE:PARAM
    const char *delimiter = strchr(cmd, ':');
    if (delimiter == NULL) {
        return BT_ERR_INVALID_FORMAT;
    }
    
    // 提取命令类型
    size_t type_len = delimiter - cmd;
    if (type_len >= sizeof(cmd_type)) {
        return BT_ERR_INVALID_FORMAT;
    }
    strncpy(cmd_type, cmd, type_len);
    cmd_type[type_len] = '\0';
    
    // 提取参数
    strncpy(cmd_param, delimiter + 1, sizeof(cmd_param) - 1);
    
    // 执行对应命令
    if (strcasecmp(cmd_type, "LED") == 0) {
        return HandleLedCommand(cmd_param);
    }
    else if (strcasecmp(cmd_type, "PWM") == 0) {
        return HandlePwmCommand(cmd_param);
    }
    else if (strcasecmp(cmd_type, "READ") == 0) {
        return HandleReadCommand(cmd_param);
    }
    else if (strcasecmp(cmd_type, "ECHO") == 0) {
        return HandleEchoCommand(cmd_param);
    }
    else {
        return BT_ERR_UNKNOWN_CMD;
    }
}

/**
 * @brief 处理LED命令
 */
static BT_Status HandleLedCommand(const char *param)
{
    if (strcasecmp(param, "ON") == 0) {
        LED_On();
        BT_SendResponse(BT_OK, "LED_ON");
        return BT_OK;
    }
    else if (strcasecmp(param, "OFF") == 0) {
        LED_Off();
        BT_SendResponse(BT_OK, "LED_OFF");
        return BT_OK;
    }
    else if (strcasecmp(param, "TOGGLE") == 0) {
        LED_Toggle();
        BT_SendResponse(BT_OK, "LED_TOGGLE");
        return BT_OK;
    }
    else if (strncasecmp(param, "BLINK", 5) == 0) {
        // 解析闪烁频率 LED:BLINK_500
        int period = atoi(param + 6);
        if (period > 0) {
            LED_SetBlink(period);
            char resp[32];
            snprintf(resp, sizeof(resp), "LED_BLINK_%d", period);
            BT_SendResponse(BT_OK, resp);
            return BT_OK;
        }
    }
    
    return BT_ERR_INVALID_PARAM;
}

/**
 * @brief 处理PWM命令
 */
static BT_Status HandlePwmCommand(const char *param)
{
    int duty = atoi(param);
    
    if (duty < 0 || duty > 100) {
        return BT_ERR_INVALID_PARAM;
    }
    
    PWM_SetDutyCycle(duty);
    
    char resp[32];
    snprintf(resp, sizeof(resp), "PWM_SET_%d", duty);
    BT_SendResponse(BT_OK, resp);
    
    return BT_OK;
}

/**
 * @brief 处理读取命令
 */
static BT_Status HandleReadCommand(const char *param)
{
    if (strcasecmp(param, "ADC") == 0) {
        uint16_t adc_value = ADC_ReadChannel(0);
        char resp[32];
        snprintf(resp, sizeof(resp), "ADC_%d", adc_value);
        BT_SendResponse(BT_DATA, resp);
        return BT_OK;
    }
    else if (strcasecmp(param, "TEMP") == 0) {
        float temperature = ReadTemperature();
        char resp[32];
        snprintf(resp, sizeof(resp), "TEMP_%.1f", temperature);
        BT_SendResponse(BT_DATA, resp);
        return BT_OK;
    }
    else if (strcasecmp(param, "STATUS") == 0) {
        char resp[64];
        snprintf(resp, sizeof(resp), "LED_%s_PWM_%d", 
                 LED_GetState() ? "ON" : "OFF",
                 PWM_GetDutyCycle());
        BT_SendResponse(BT_DATA, resp);
        return BT_OK;
    }
    
    return BT_ERR_INVALID_PARAM;
}

/**
 * @brief 处理回显命令(测试用)
 */
static BT_Status HandleEchoCommand(const char *param)
{
    BT_SendResponse(BT_OK, param);
    return BT_OK;
}

/**
 * @brief 发送响应
 * @param status 状态码
 * @param data 数据内容
 */
void BT_SendResponse(BT_Status status, const char *data)
{
    char response[128];
    const char *status_str;
    
    switch (status) {
        case BT_OK:
            status_str = "OK";
            break;
        case BT_DATA:
            status_str = "DATA";
            break;
        case BT_ERR_UNKNOWN_CMD:
            status_str = "ERR:UNKNOWN_CMD";
            break;
        case BT_ERR_INVALID_PARAM:
            status_str = "ERR:INVALID_PARAM";
            break;
        case BT_ERR_INVALID_FORMAT:
            status_str = "ERR:INVALID_FORMAT";
            break;
        default:
            status_str = "ERR:UNKNOWN";
            break;
    }
    
    if (data != NULL && strlen(data) > 0) {
        snprintf(response, sizeof(response), "%s:%s\r\n", status_str, data);
    } else {
        snprintf(response, sizeof(response), "%s\r\n", status_str);
    }
    
    USART1_SendString(response);
}

/**
 * @brief 协议处理主循环
 * @note 在主循环中调用
 */
void BT_Protocol_Process(void)
{
    char line[CMD_BUFFER_SIZE];
    
    // 读取完整命令行
    if (USART1_ReadLine(line, sizeof(line)) > 0) {
        // 执行命令
        BT_Status status = ExecuteCommand(line);
        
        // 如果执行失败,发送错误响应
        if (status != BT_OK && status != BT_DATA) {
            BT_SendResponse(status, NULL);
        }
    }
}

📄 创建文件:bluetooth_protocol.h

/**
 * @file bluetooth_protocol.h
 * @brief 蓝牙通信协议头文件
 */

#ifndef __BLUETOOTH_PROTOCOL_H
#define __BLUETOOTH_PROTOCOL_H

#include <stdint.h>

/* 缓冲区大小 */
#define CMD_BUFFER_SIZE 128

/* 状态码 */
typedef enum {
    BT_OK = 0,              // 执行成功
    BT_DATA,                // 数据响应
    BT_ERR_UNKNOWN_CMD,     // 未知命令
    BT_ERR_INVALID_PARAM,   // 参数错误
    BT_ERR_INVALID_FORMAT   // 格式错误
} BT_Status;

/* 函数声明 */
void BT_Protocol_Init(void);
void BT_Protocol_Process(void);
void BT_SendResponse(BT_Status status, const char *data);

/* 命令处理函数(内部使用) */
static BT_Status ExecuteCommand(const char *cmd);
static BT_Status HandleLedCommand(const char *param);
static BT_Status HandlePwmCommand(const char *param);
static BT_Status HandleReadCommand(const char *param);
static BT_Status HandleEchoCommand(const char *param);

#endif /* __BLUETOOTH_PROTOCOL_H */

4.4 LED和PWM控制

📄 创建文件:led_control.c

/**
 * @file led_control.c
 * @brief LED和PWM控制驱动
 */

#include "led_control.h"
#include "stm32f10x.h"
#include "stm32f10x_tim.h"

static volatile uint8_t led_state = 0;
static volatile uint16_t pwm_duty = 0;
static volatile uint16_t blink_period = 0;
static volatile uint32_t blink_counter = 0;

/**
 * @brief 初始化LED控制
 * 使用PA0控制LED,TIM2_CH1输出PWM
 */
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_OCInitTypeDef TIM_OCInitStruct;
    
    /* 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    /* 配置PA0为复用推挽输出(TIM2_CH1) */
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    /* 配置TIM2 */
    // 72MHz / 72 / 1000 = 1kHz PWM频率
    TIM_TimeBaseStruct.TIM_Period = 999;        // 自动重装载值
    TIM_TimeBaseStruct.TIM_Prescaler = 71;      // 预分频器
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
    
    /* 配置PWM模式 */
    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse = 0;             // 初始占空比0
    TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OCInitStruct);
    
    TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM2, ENABLE);
    
    /* 启动TIM2 */
    TIM_Cmd(TIM2, ENABLE);
    
    led_state = 0;
    pwm_duty = 0;
    blink_period = 0;
}

void LED_On(void)
{
    blink_period = 0;  // 停止闪烁
    PWM_SetDutyCycle(100);
    led_state = 1;
}

void LED_Off(void)
{
    blink_period = 0;  // 停止闪烁
    PWM_SetDutyCycle(0);
    led_state = 0;
}

void LED_Toggle(void)
{
    if (led_state) {
        LED_Off();
    } else {
        LED_On();
    }
}

void LED_SetBlink(uint16_t period_ms)
{
    blink_period = period_ms;
    blink_counter = 0;
}

void PWM_SetDutyCycle(uint16_t duty)
{
    if (duty > 100) duty = 100;
    
    pwm_duty = duty;
    uint16_t pulse = (duty * 1000) / 100;  // 转换为TIM脉冲值
    TIM_SetCompare1(TIM2, pulse);
    
    led_state = (duty > 0) ? 1 : 0;
}

uint16_t PWM_GetDutyCycle(void)
{
    return pwm_duty;
}

uint8_t LED_GetState(void)
{
    return led_state;
}

/**
 * @brief LED闪烁处理(在1ms定时器中调用)
 */
void LED_Blink_Handler(void)
{
    if (blink_period == 0) return;
    
    blink_counter++;
    if (blink_counter >= blink_period) {
        blink_counter = 0;
        LED_Toggle();
    }
}

📄 创建文件:led_control.h

/**
 * @file led_control.h
 * @brief LED控制头文件
 */

#ifndef __LED_CONTROL_H
#define __LED_CONTROL_H

#include <stdint.h>

void LED_Init(void);
void LED_On(void);
void LED_Off(void);
void LED_Toggle(void);
void LED_SetBlink(uint16_t period_ms);
void LED_Blink_Handler(void);

void PWM_SetDutyCycle(uint16_t duty);
uint16_t PWM_GetDutyCycle(void);
uint8_t LED_GetState(void);

#endif /* __LED_CONTROL_H */

4.5 主程序

📄 创建文件:main.c

/**
 * @file main.c
 * @brief 蓝牙通信主程序
 * 
 * 功能:
 * - 初始化系统时钟和外设
 * - 建立蓝牙通信连接
 * - 处理蓝牙命令
 * - 支持LED控制、PWM调节、数据读取
 */

#include "stm32f10x.h"
#include "usart1.h"
#include "bluetooth_protocol.h"
#include "led_control.h"
#include <stdio.h>

/* 系统滴答定时器计数 */
static volatile uint32_t sys_tick_count = 0;

/**
 * @brief 系统时钟配置
 * 配置为72MHz,使用外部8MHz晶振
 */
void SystemClock_Config(void)
{
    // 已在启动文件中配置,此处可添加额外配置
}

/**
 * @brief 滴答定时器初始化
 * 用于产生1ms时基
 */
void SysTick_Init(void)
{
    // 配置SysTick为1ms中断
    // 72MHz / 8 = 9MHz,9000个计数 = 1ms
    SysTick_Config(SystemCoreClock / 1000);
}

/**
 * @brief 滴答定时器中断服务程序
 */
void SysTick_Handler(void)
{
    sys_tick_count++;
    
    // 每1ms调用LED闪烁处理
    LED_Blink_Handler();
}

/**
 * @brief 获取系统运行时间(毫秒)
 */
uint32_t GetSysTick(void)
{
    return sys_tick_count;
}

/**
 * @brief 延时函数(毫秒)
 */
void Delay_ms(uint32_t ms)
{
    uint32_t start = GetSysTick();
    while ((GetSysTick() - start) < ms);
}

/**
 * @brief 主函数
 */
int main(void)
{
    /* 初始化系统 */
    SystemClock_Config();
    SysTick_Init();
    
    /* 初始化外设 */
    LED_Init();
    USART1_Init();
    BT_Protocol_Init();
    
    /* 发送启动消息 */
    Delay_ms(100);  // 等待HC-05启动
    USART1_SendString("\r\n");
    USART1_SendString("================================\r\n");
    USART1_SendString("  STM32 Bluetooth Control System\r\n");
    USART1_SendString("  Version 1.0\r\n");
    USART1_SendString("================================\r\n");
    USART1_SendString("Ready for connection...\r\n");
    
    /* 主循环 */
    while (1) {
        /* 处理蓝牙协议 */
        BT_Protocol_Process();
        
        /* 可添加其他后台任务 */
        // 例如:传感器数据采集、状态上报等
    }
}

五、手机APP配置

5.1 推荐APP

安卓平台:

  • 蓝牙串口助手(推荐)
  • 蓝牙调试器
  • Serial Bluetooth Terminal

iOS平台:

  • Bluetooth Serial
  • LightBlue

5.2 连接步骤

  1. 开启手机蓝牙

    • 进入手机设置 → 蓝牙
    • 打开蓝牙开关
  2. 搜索设备

    • 在可用设备列表中找到HC-05
    • 默认名称通常为"HC-05"或自定义名称
  3. 配对连接

    • 点击HC-05进行配对
    • 输入配对密码:1234(默认)
    • 等待连接成功
  4. 打开串口助手APP

    • 选择已配对的HC-05设备
    • 设置波特率:9600
    • 点击连接

5.3 测试命令

连接成功后,在APP中发送以下命令测试:

测试命令:
ECHO:Hello\r\n      → 应收到:OK:Hello
LED:ON\r\n         → 应收到:OK:LED_ON,LED灯亮
LED:OFF\r\n        → 应收到:OK:LED_OFF,LED灯灭
LED:TOGGLE\r\n     → 应收到:OK:LED_TOGGLE,LED状态翻转
PWM:50\r\n         → 应收到:OK:PWM_SET_50,LED亮度50%
READ:STATUS\r\n     → 应收到:DATA:LED_X_PWM_X

六、系统测试

6.1 硬件测试

测试步骤:

  1. 电源测试

    • 使用万用表测量3.3V和GND之间电压
    • 正常范围:3.2V~3.4V
  2. 串口通信测试

    • 使用USB转TTL连接HC-05
    • 进入AT模式,发送AT指令测试
    • 应返回OK
  3. 蓝牙连接测试

    • 手机搜索并配对HC-05
    • 观察HC-05状态LED
    • 未连接:快速闪烁
    • 已连接:常亮

6.2 软件测试

功能测试清单:

测试项测试方法预期结果
串口初始化上电观察无错误,正常启动
命令解析发送ECHO:test返回OK:test
LED控制发送LED:ON/OFFLED相应亮灭
PWM调节发送PWM:0~100LED亮度渐变
状态读取发送READ:STATUS返回当前状态
错误处理发送无效命令返回错误信息

6.3 调试技巧

使用串口助手调试:

  1. 连接USB转TTL到STM32的USART1
  2. 打开XCOM串口助手
  3. 设置波特率9600,数据位8,停止位1
  4. 观察收发数据

常见问题排查:

问题可能原因解决方法
无响应波特率不匹配检查HC-05波特率设置
乱码接线错误检查TX/RX是否交叉连接
无法配对密码错误使用默认密码1234
连接不稳定电源不足确保供电稳定,增加滤波电容

七、故障排查与问题解决

7.1 通信故障

问题1:手机无法搜索到HC-05

现象:

  • 手机蓝牙搜索不到设备
  • HC-05 LED正常闪烁

原因分析:

  • HC-05未进入可发现模式
  • 蓝牙名称被修改
  • 模块故障

解决方案:

  1. 检查模块状态

    • 确认HC-05上电正常(LED闪烁)
    • 测量VCC电压是否在3.3V~5V范围
  2. 恢复默认设置

    进入AT模式:
    1. 将EN引脚接高电平
    2. 重新上电
    3. 发送:AT+ORGL\r\n  (恢复出厂设置)
    4. 发送:AT+NAME=HC-05\r\n  (设置名称)
    5. 发送:AT+PIN=1234\r\n  (设置密码)
    
  3. 验证修复

    • 重新上电进入通信模式
    • 手机再次搜索

问题2:连接后无数据传输

现象:

  • 手机显示已连接
  • 发送命令无响应

原因分析:

  • 波特率不匹配
  • 接线错误
  • STM32程序未运行

解决方案:

  1. 检查波特率设置

    // 确认USART1初始化波特率
    USART_InitStruct.USART_BaudRate = 9600;  // 必须与HC-05一致
    
  2. 验证接线

    STM32 PA9(TX) → HC-05 RX
    STM32 PA10(RX) → HC-05 TX
    
  3. 使用串口助手测试

    • 连接USB转TTL到HC-05
    • 手机连接HC-05
    • 在手机和电脑串口助手之间发送数据
    • 验证HC-05透传功能正常

7.2 程序故障

问题3:程序卡死或异常复位

现象:

  • 程序运行一段时间后停止
  • 看门狗复位

原因分析:

  • 中断处理不当
  • 缓冲区溢出
  • 堆栈溢出

解决方案:

  1. 优化中断处理

    // 中断服务程序应尽量简短
    void USART1_IRQHandler(void)
    {
        if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
            uint8_t data = USART_ReceiveData(USART1);
            // 仅做数据存储,不处理逻辑
            if ((rx_write_index + 1) % RX_BUFFER_SIZE != rx_read_index) {
                rx_buffer[rx_write_index] = data;
                rx_write_index = (rx_write_index + 1) % RX_BUFFER_SIZE;
            }
        }
    }
    
  2. 添加看门狗保护

    // 初始化独立看门狗
    void IWDG_Init(void)
    {
        IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
        IWDG_SetPrescaler(IWDG_Prescaler_64);  // 64分频
        IWDG_SetReload(625);  // 约1秒超时
        IWDG_ReloadCounter();
        IWDG_Enable();
    }
    
    // 主循环中喂狗
    while (1) {
        BT_Protocol_Process();
        IWDG_ReloadCounter();  // 喂狗
    }
    

7.3 性能优化

优化1:提高通信稳定性
// 添加数据校验
void BT_SendPacket(const uint8_t *data, uint16_t len)
{
    uint8_t checksum = 0;
    
    // 发送帧头
    USART1_SendChar(0xAA);
    USART1_SendChar(0x55);
    
    // 发送长度
    USART1_SendChar((len >> 8) & 0xFF);
    USART1_SendChar(len & 0xFF);
    
    // 发送数据并计算校验
    for (uint16_t i = 0; i < len; i++) {
        USART1_SendChar(data[i]);
        checksum ^= data[i];
    }
    
    // 发送校验
    USART1_SendChar(checksum);
}
优化2:降低功耗
// 使用低功耗模式
void EnterSleepMode(void)
{
    // 等待当前传输完成
    while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    
    // 进入睡眠模式
    __WFI();
}

// 使用中断唤醒
void USART1_IRQHandler(void)
{
    // 接收中断唤醒CPU
    if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
        // 处理数据...
    }
}

八、扩展应用

8.1 添加传感器数据采集

// 读取温度传感器
float ReadTemperature(void)
{
    // 假设使用DS18B20
    // 实现温度读取代码
    return temperature;
}

// 在命令处理中添加
if (strcasecmp(param, "TEMP") == 0) {
    float temp = ReadTemperature();
    char resp[32];
    snprintf(resp, sizeof(resp), "TEMP_%.1f", temp);
    BT_SendResponse(BT_DATA, resp);
    return BT_OK;
}

8.2 实现数据上报

// 定时上报数据
void DataReport_Task(void)
{
    static uint32_t last_report_time = 0;
    uint32_t current_time = GetSysTick();
    
    // 每5秒上报一次
    if (current_time - last_report_time >= 5000) {
        last_report_time = current_time;
        
        char report[128];
        snprintf(report, sizeof(report), 
                 "REPORT:TIME_%lu,TEMP_%.1f,ADC_%d\r\n",
                 current_time / 1000,
                 ReadTemperature(),
                 ADC_ReadChannel(0));
        
        USART1_SendString(report);
    }
}

九、总结

9.1 核心知识点回顾

通过本教程,我们学习了:

  1. HC-05蓝牙模块工作原理

    • 主从一体工作模式
    • AT指令配置方法
    • 串口透传机制
  2. STM32 USART通信

    • USART初始化配置
    • 中断接收和发送
    • 环形缓冲区管理
  3. 蓝牙通信协议设计

    • 文本协议格式
    • 命令解析和响应
    • 错误处理机制
  4. 手机APP交互

    • 蓝牙设备配对
    • 串口调试工具使用
    • 双向数据传输

9.2 扩展学习方向

  • 蓝牙BLE:学习低功耗蓝牙技术
  • WiFi通信:ESP8266/ESP32模块
  • 自定义APP开发:使用Android Studio开发专属APP
  • 云平台接入:将数据上传至阿里云/腾讯云

9.3 学习资源

官方文档:

开源项目:


附录

A. 物料清单(BOM)

序号名称型号数量备注
1单片机STM32F103C8T61最小系统板
2蓝牙模块HC-051带底板
3USB转TTLCH3401调试使用
4LED3mm红发1状态指示
5电阻1kΩ1LED限流
6杜邦线母对母若干连接线
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LCG元

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值