第一部分*********************!!!!!!!!蓝牙模块HC-05
蓝牙概述
蓝牙(Bluetooth)是一种用于无线通信的技术标准,允许设备在短距离内进行数据交换和通信。它是由爱立信(Ericsson)公司在1994年推出的,以取代传统的有线连接方式,使设备之间能够实现低功耗、低成本的数据传输和通信。
蓝牙作为一种小范围无线连接技术,具有低功耗、低成本、方便快捷的特点,被广泛应用于无线耳机、智能手表、无线遥控等场景,是实现无线通信的主流技术之一。
蓝牙协议各个版本
HC-05模块简介
HC-05参数
HC-05工作模式
HC-05角色
1. Master(主角色)——查询周围 SPP 蓝牙从设备,并主动发起连接,从而建立主、从蓝牙设备间的透明数据传输通道。
2. Slave(从角色)——被动连接;
3. Slave-Loop(回环角色)——被动连接,接收远程蓝牙主设备数据并将数据原样返回给远程蓝牙主设备;
引脚定义
怎么进入命令响应工作模式(AT指令模式)
用手按着模块上的按键,再给蓝牙模块上电。当蓝牙模块上的灯慢闪的时候就说明进入了配置模式了。
1 按下模块上的按键(PIO11置高)
2 对HC-05重新上电,模块进入AT命令响应工作模式。
3 使用串口助手,配置成波特率38400,数据位8 位,停止位1 位,无校验位,无流控制。
4 串口助手发送字符串“AT\r\n”,正常情况下模块会给出“OK\r\n”,“\r\n”为回车换行,也叫做新行。
HC-05工作流程
AT指令模式配置HC-05模块
重新上电模块,进入自动连接工作模块,连接从模块(也可以使用AT指令,在AT指令模式下连接从模块)。
建立蓝牙连接,LED常量,直接使用串口完成通信
电脑串口助手配置
在配置蓝牙的时候一定要将串口助手按这个要求配置:设置波特率 38400,数据位 8 位,停止位 1 位,无校验位,这是固定操作!
常见指令
常见的指令
首先我们要明确串口通信的条件,我们需要设置8位数据位,1为停止位,无检验,波特率为38400,这样才可以与HC-05进行串口通信,当然这些配置都可以在后续的AT指令中更改。
并且要以文本模式发送指令,编码格式为 utf-8,每个指令后面也要加上换行(‘\r\n’)。
1 第一个测试指令,我们直接通过串口发送“AT”即可,然后就会给我们回复“OK”,用这个没什么意义的指令可以来测试我们是否成功进入到了AT指令模式(其实通过LED的闪烁情况也可以知道)。
2 发送复位指令之后,HC-05进入复位,如果没有按下按钮的话,会默认进入到配对模式。成功后回复“OK”。
恢复的默认配置同上图。发送完成后貌似是自动复位了,也可能不是,反正我的HC-05从原本的AT指令模式变成配对模式了。
而且设备名称也不是上面说的“H-C-2010-06-01”而是“HC-05”,也可能是因为我的是兼容版HC-05,具体的情况以你们自己手上的HC-05为准。
不带参数的话就是回复设备名称,加“OK”,带参数的话就是修改蓝牙名称并且回复“OK”。
默认就是从机,当然我们也可以修改为主机。
这边要注意的是,配对码(密码)需要用英文半角的双引号括起来。
LED和蓝牙模块代码对比1:
实验现象:
代码部分(详解)
hc05.c
#include "hc05.h"
#include "delay.h"//代码片段是一些预处理指令,用于定义宏和一些常量。
#define HC05_DelayMs(t) Delay_Ms(t)//当调用 HC05_DelayMs(t) 时,实际上会调用 Delay_Ms(t) 函数。
#define HC05_GPIO_LOW 0 //这通常用于表示某种特定状态,例如在这里,可能表示低电平或关闭状态。
#define HC05_GPIO_HIGH 1 //它被赋值为1。类似地,这个宏可能表示高电平或打开状态。#if defined (STM32F40_41xxx)//配置输入输出
//用于配置一个GPIO引脚为输出模式。它接受两个参数:port 表示GPIO端口,pin 表示端口上的引脚。
#define HC05_CONFIG_IO_OUTPUT(port, pin) {GPIO_InitTypeDef GPIO_InitStructure; \GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; \GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;\GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; \GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; \GPIO_InitStructure.GPIO_Pin = pin ; \GPIO_Init(port, &GPIO_InitStructure);}
//读取一个GPIO引脚的当前值。它接受两个参数:port 和 pin,分别表示GPIO端口和引脚。
#define HC05_CONFIG_IO_INPUT(port, pin) {GPIO_InitTypeDef GPIO_InitStructure; \GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; \GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; \GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; \GPIO_InitStructure.GPIO_Pin = pin ; \GPIO_Init(port, &GPIO_InitStructure);}
//用于读取一个GPIO引脚的当前值。它接受两个参数:port 和 pin,分别表示GPIO端口和引脚。
#define HC05_IO_GET_VALUE(port, pin) GPIO_ReadInputDataBit(port, pin)//用于设置一个GPIO引脚的输出值。它接受三个参数:port、pin 和 value,分别表示GPIO端口、引脚和要设置的值(HC05_GPIO_HIGH 或 HC05_GPIO_LOW)。
#define HC05_IO_SET(port, pin, value) { if(value == HC05_GPIO_HIGH) \GPIO_SetBits(port, pin); \else \GPIO_ResetBits(port, pin);}
//时钟开启 时钟使能
#define RCC_GPIO_CLOCK_ENABLE(GPIOX) { if (GPIOX == GPIOA) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); \else if (GPIOX == GPIOB) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); \else if (GPIOX == GPIOC) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE); \else if (GPIOX == GPIOD) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE); \else if (GPIOX == GPIOE) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE); \else if (GPIOX == GPIOF) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); \else if (GPIOX == GPIOG) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE); \else printf("gpio clock no enable\r\n"); \}
#endif//调试模式
//如果 HC05_DEBUG_MODE 的值为1,表示处于调试模式,那么 hc05_log 会被定义为 printf 函数,用于打印格式化的日志信息。
#if (HC05_DEBUG_MODE == 1)#define hc05_log(format, ...) printf(format, ##__VA_ARGS__)
#else#define hc05_log(format, ...)
//如果 HC05_DEBUG_MODE 的值不为1,表示不处于调试模式,那么 hc05_log 会被定义为空,即不执行任何操作,这样就可以在不需要输出调试信息时避免编译器产生多余的代码。
#endif/******************************************************************************* @brief 发送一个字符** @param[in] bt : BlueTooth_t结构体指针* @param[in] ch : 要发送的字符** @return 0, 表示成功, 其他值表示失败*函数的作用是通过蓝牙发送一个字符。******************************************************************************/
//函数的声明,指定了函数名 send_char,参数类型为指向 BlueTooth_t 结构的指针 bt 和一个字符 ch,返回类型为 int。
static int send_char(BlueTooth_t* bt, char ch)
{while((bt->uart->SR & 0X40) == 0);bt->uart->DR = (uint16_t)ch;//一旦蓝牙模块准备好发送字符,就会将要发送的字符 ch 写入 bt 结构体中的 uart 成员的 DR(数据寄存器)中。return 0;
}
//这是一个 while 循环,它的条件是等待蓝牙模块准备好发送下一个字符。这里使用了位运算和与操作,检查 bt
//结构体中的 uart 成员的 SR(状态寄存器)的第六位是否为 1,如果不为 1,则表示蓝牙模块还没有准备好
//发送下一个字符,程序会一直在这里循环等待。/******************************************************************************* @brief 发送字符串** @param[in] bt : BlueTooth_t结构体指针* @param[in] str : 字符串** @return 返回具体发送的字节数*用于向蓝牙模块发送字符串数据。******************************************************************************/
static int send_string(BlueTooth_t* bt, const char* str)
{uint32_t i = 0;uint32_t len = strlen(str);for (i = 0; i < len; i++) {send_char(bt, str[i]);}return i;
}/******************************************************************************* @brief 发送AT指令函数** @param[in] bt : BlueTooth_t结构体指针* @param[in] at_cmd : AT指令* @param[in] response : AT指令的回响* @param[in] timeout_ms : 超时时间** @return 0, 表示成功, 其他值表示失败*用于发送 AT 指令并等待响应的函数。以下是对注释的解释:******************************************************************************/
static int send_at_cmd(BlueTooth_t *bt, const char *at_cmd, const char *response, uint32_t timeout_ms)
{ //用于发送 AT 指令并等待响应。它接受一个指向 BlueTooth_t 结构的指针 bt,一个表示要发送的 AT 指令的字符串 at_cmd,//一个表示期望的响应的字符串 response,以及一个表示超时时间的毫秒数 timeout_ms。它返回一个整数值,表示执行结果。bt->rx_buffer_current_cnt = 0;// 这行代码将接收缓冲区的当前计数器置为0,准备接收新的响应数据。memset(bt->rx_buffer, 0, sizeof(bt->rx_buffer));//这行代码用于将接收缓冲区中的所有元素都设置为0,以清除其中的任何残留数据。send_string(bt, at_cmd);//这行代码调用了一个名为 send_string 的函数,用于发送字符串类型的数据至蓝牙模块。send_string(bt, "\r\n");// 这行代码发送一个回车换行符,以结束当前的 AT 指令。timeout_ms = timeout_ms == 0 ? 0xFFFFFFFF : timeout_ms; //timeout_ms为0,表示延时最长时间,0xFFFFFFFF msdo {if (strstr((const char *)bt->rx_buffer, response) != NULL) { //回应信息正确return 0;}HC05_DelayMs(1);}while(timeout_ms--);
//循环条件为 timeout_ms 不为0。在循环内部,检查接收缓冲区中是否包含期望的响应字符串,//如果包含,则返回0表示成功;否则,等待1毫秒,直到超时。if (!timeout_ms) //发送超时return -1;
//如果超时时间为0,即等待时间已经用尽,则返回-1表示发送超时。return -2;
}/******************************************************************************* @brief 使用AT指令对蓝牙模块配置参数,重新复位** @param[in] bt : BlueTooth_t结构体指针** @return 0, 表示初始化成功, 其他值表示失败*通过发送一系列的 AT 指令给蓝牙模块,来执行重置操作,并记录每个指令的执行结果和模块的响应内容。******************************************************************************/
int bt_software_reset(struct blue_tooth *bt)
//这是一个函数定义,它接受一个指向 struct blue_tooth 结构的指针作为参数,并返回一个整数值。
{char tmp_buffer[20];int ret = 0;HC05_IO_SET(bt->port[HC05_EN], bt->pin[HC05_EN], HC05_GPIO_HIGH);//这行代码设置蓝牙模块的使能引脚为高电平,以便激活模块。Delay_Ms(1000);//暂停执行代码1秒钟。memset(tmp_buffer, 0, sizeof(tmp_buffer));//这行代码用于将 tmp_buffer 数组中的所有元素都设置为0,以清除其中的任何残留数据。sprintf(tmp_buffer, "AT");//这行代码将字符串 "AT" 格式化并存储到 tmp_buffer 中,表示发送 AT 指令给蓝牙模块。ret = send_at_cmd(bt, tmp_buffer, "OK", 1000);//这行代码调用了一个名为 send_at_cmd 的函数,该函数用于发送 AT 指令给蓝牙模块,并等待接收到 "OK" 响应,最多等待1秒钟。hc05_log("AT: %d %s\r\n", ret, bt->rx_buffer);//这行代码记录了发送 AT 指令的结果和接收到的响应内容。memset(tmp_buffer, 0, sizeof(tmp_buffer));sprintf(tmp_buffer, "AT+NAME=HC-05");//指令的作用是设置蓝牙模块的名称为 "HC-05"。ret = send_at_cmd(bt, tmp_buffer, "OK", 1000);hc05_log("AT+NAME: %d %s\r\n", ret, bt->rx_buffer);memset(tmp_buffer, 0, sizeof(tmp_buffer));sprintf(tmp_buffer, "AT+ROLE=0");ret = send_at_cmd(bt, tmp_buffer, "OK", 1000);
//调用 send_at_cmd 函数发送命令给蓝牙模块,并等待蓝牙模块返回 "OK"。hc05_log("AT+ROLE: %d %s\r\n", ret, bt->rx_buffer);memset(tmp_buffer, 0, sizeof(tmp_buffer));sprintf(tmp_buffer, "AT+PSWD=\"123456\"");ret = send_at_cmd(bt, tmp_buffer, "OK", 1000);hc05_log("AT+PSWD: %d %s\r\n", ret, bt->rx_buffer);memset(tmp_buffer, 0, sizeof(tmp_buffer));sprintf(tmp_buffer, "AT+UART=38400,0,0");hc05_log("AT+UART: %d %s\r\n", ret, bt->rx_buffer);memset(tmp_buffer, 0, sizeof(tmp_buffer));sprintf(tmp_buffer, "AT+RESET");ret = send_at_cmd(bt, tmp_buffer, "OK", 1000);hc05_log("AT+RESET: %d %s\r\n", ret, bt->rx_buffer);HC05_IO_SET(bt->port[HC05_EN], bt->pin[HC05_EN], HC05_GPIO_LOW);//最后一行代码将蓝牙模块的使能引脚设置为低电平,以便关闭模块。return 0;
}
//通过发送一系列的AT指令给蓝牙模块,来配置其名称、角色、密码、波特率等参数,并通过 hc05_log 函数输出每个AT指令执行的结果。/******************************************************************************* @brief 初始化HC-05模块用到的硬件资源** @param[in] bt : BlueTooth_t结构体指针** @return 无*******************************************************************************/
static void bt_hw_init(BlueTooth_t* bt)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;/* 初始化用大的时钟 */RCC_GPIO_CLOCK_ENABLE(bt->port[HC05_TX]);RCC_GPIO_CLOCK_ENABLE(bt->port[HC05_RX]);RCC_GPIO_CLOCK_ENABLE(bt->port[HC05_EN]);RCC_GPIO_CLOCK_ENABLE(bt->port[HC05_STATE]);/* USART端口配置 */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉GPIO_InitStructure.GPIO_Pin = bt->pin[HC05_TX];GPIO_Init(bt->port[HC05_TX], &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = bt->pin[HC05_RX];GPIO_Init(bt->port[HC05_RX], &GPIO_InitStructure);/* 配置EN、STATUS引脚 */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //EN引脚配置为输出GPIO_InitStructure.GPIO_Pin = bt->pin[HC05_EN];GPIO_Init(bt->port[HC05_EN], &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; //STATUS引脚配置为输入GPIO_InitStructure.GPIO_Pin = bt->pin[HC05_STATE];GPIO_Init(bt->port[HC05_STATE], &GPIO_InitStructure);/* USART 初始化设置 */USART_InitStructure.USART_BaudRate = HC05_UART_BAUD_RATE; //波特率设置USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据位长USART_InitStructure.USART_StopBits = USART_StopBits_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(bt->uart, &USART_InitStructure); //初始化串口USART_Cmd(bt->uart, ENABLE); //使能串口USART_ITConfig(bt->uart, USART_IT_RXNE, ENABLE); //开启串口接受中断/* 串口的NVIC配置 */NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}/******************************************************************************* @brief 初始化HC-05模块** @param[in] bt : BlueTooth_t结构体指针** @return 0, 表示初始化成功, 其他值表示失败*******************************************************************************/
int BT_Init(BlueTooth_t *bt)
{if(!bt)return -1;bt->rx_buffer_current_cnt = 0;bt->rx_buffer_total_length = HC05_RECEIVE_BUFFER_SIZE;memset(bt->rx_buffer, 0, sizeof(bt->rx_buffer));/* 初始化用到的硬件 */bt_hw_init(bt);/* 使用AT指令初始化HC-05模块 */bt_software_reset(bt);return 0;
}/******************************************************************************* @brief 获得蓝牙模块连接的状态** @param[in] bt : BlueTooth_t结构体指针** @return HC05_STATUS_CONNECT, 表示蓝牙模块已连接, * HC05_STATUS_DISCONNECT,其他值表示失败*******************************************************************************/
BT_ConnectStatus_t BT_GetBlueToothStatus(BlueTooth_t *bt)
{uint8_t pin_status = 0;pin_status = HC05_IO_GET_VALUE(bt->port[HC05_STATE], bt->pin[HC05_STATE]);if (pin_status) { //连接return HC05_STATUS_CONNECT;} else {return HC05_STATUS_DISCONNECT;}
}/******************************************************************************* @brief 蓝牙模块缓存区里面是否有数据** @param[in] bt : BlueTooth_t结构体指针** @return true 表示缓存区有数据, * false 表示缓存区没有数据*******************************************************************************/
bool BT_IsReceiveData(BlueTooth_t *bt)
{if (bt->rx_buffer_current_cnt) {return true;} else {return false;}
}/******************************************************************************* @brief 读取蓝牙模块缓存区里面的数据** @param[in] bt : BlueTooth_t结构体指针* @param[in] pReadBuffer : 读缓存区指针* @param[in] u32ReadCount : 读取的数据量** @return 返回读取到的数据*******************************************************************************/
int BT_ReceiveData(BlueTooth_t *bt, uint8_t *pReadBuffer, uint32_t u32ReadCount)
{if (u32ReadCount >= bt->rx_buffer_current_cnt) {memcpy(pReadBuffer, bt->rx_buffer, bt->rx_buffer_current_cnt);bt->rx_buffer_current_cnt = 0;return bt->rx_buffer_current_cnt;} else {memcpy(pReadBuffer, bt->rx_buffer, u32ReadCount);bt->rx_buffer_current_cnt = 0;return u32ReadCount;}}/******************************************************************************* @brief 向蓝牙模块发送数据** @param[in] bt : BlueTooth_t结构体指针* @param[in] pReadBuffer : 写缓存区指针* @param[in] u32ReadCount : 要发送的数据量** @return 返回读取到的数据*******************************************************************************/
int BT_SendData(BlueTooth_t *bt, uint8_t *pWriteBuffer, uint32_t u32WriteCount)
{int i = 0;for (i = 0; i < u32WriteCount; i++) {send_char(bt, pWriteBuffer[i]);}return i;
}
hc05.h
#ifndef __HC05_H
#define __HC05_H#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>#include "delay.h"#define HC05_UART_BAUD_RATE (38400U)
#define HC05_RECEIVE_BUFFER_SIZE (1024U)
#define HC05_DEBUG_MODE 0//与芯片相关的部分 换芯片只需改动此处
#if defined (STM32F40_41xxx)#include "stm32f4xx.h"typedef GPIO_TypeDef* GPIO_Port_t;
typedef USART_TypeDef* UART_t;
//与芯片相关的部分 换芯片只需改动此处#else#error hc05.h: No processor defined!#endiftypedef GPIO_Port_t gpio;//蓝牙的链接状态
typedef enum bt_status
{HC05_STATUS_DISCONNECT = 0,HC05_STATUS_CONNECT = 1,
}BT_ConnectStatus_t;//四个引脚
enum HC05Pin_t
{HC05_TX = 0,HC05_RX = 1,HC05_EN = 2,HC05_STATE = 3,HC05_COUNT //
};//初始化蓝牙结构体
//串口资源收发 引脚
typedef struct blue_tooth{ UART_t uart;GPIO_Port_t port[HC05_COUNT];uint16_t pin[HC05_COUNT];uint32_t rx_buffer_current_cnt;uint32_t rx_buffer_total_length; //缓冲区总的长度char rx_buffer[HC05_RECEIVE_BUFFER_SIZE]; //接受AT指令的缓冲区
}BlueTooth_t;int BT_Init(BlueTooth_t *bt);
BT_ConnectStatus_t BT_GetBlueToothStatus(BlueTooth_t *bt);
bool BT_IsReceiveData(BlueTooth_t *bt);
int BT_ReceiveData(BlueTooth_t *bt, uint8_t *pReadBuffer, uint32_t u32ReadCount);
int BT_SendData(BlueTooth_t *bt, uint8_t *pBuffer, uint32_t u32WriteCount);#endif
main.c
#include "main.h"BlueTooth_t gBL;
int main(void)
{BT_ConnectStatus_t s = 0;char buffer[100];int i = 0;char rx_buffer[100];Delay_Init();Debug_Init(115200);printf("BL\r\n");/* 初始化串口2对应时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);/* 串口2对应引脚复用映射 */GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);gBL.uart = USART2;gBL.port[HC05_TX] = GPIOA;gBL.pin[HC05_TX] = GPIO_Pin_2;gBL.port[HC05_RX] = GPIOA;gBL.pin[HC05_RX] = GPIO_Pin_3;gBL.port[HC05_EN] = GPIOA;gBL.pin[HC05_EN] = GPIO_Pin_4;gBL.port[HC05_STATE] = GPIOA;gBL.pin[HC05_STATE] = GPIO_Pin_5;BT_Init(&gBL);while (1) {s = BT_GetBlueToothStatus(&gBL); //判断蓝牙是否连接if (s) {printf("蓝牙连接\r\n");sprintf(buffer, "i: %d\r\n", i++);BT_SendData(&gBL, buffer, strlen(buffer));if (BT_IsReceiveData(&gBL)) {memset(rx_buffer, 0, sizeof(rx_buffer));BT_ReceiveData(&gBL, rx_buffer, 100);printf("rx_buffer: %s\r\n", rx_buffer);}}Delay_Ms(500);}
}void USART2_IRQHandler(void)
{uint16_t ret = 0;if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {ret= USART2->DR;gBL.rx_buffer[gBL.rx_buffer_current_cnt % sizeof(gBL.rx_buffer)] = ret;gBL.rx_buffer_current_cnt++;}}