蓝牙模块 HC-05

蓝牙模块 HC-05

接口定义

模块指示灯

AT模式和串口透传模式

AT指令集

详见HC-05的中文指令集文档,网盘

两个蓝牙模块相互连接


配置蓝牙A为主机,指定地址连接;蓝牙B为从机。

蓝牙A、B进入AT模式。
设置B配对码,AT+PSWD=“1234”。
设置B从模式,AT+ROLE=0。
查询B地址,AT+ADDR,得到B地址21:13:508222。
设置A配对码,AT+PSWD=“1234”。(配对码要一样)
设置A主模式,AT+ROLE=1。
设置A指定地址连接,AT+CMODE=0。
A绑定B,AT+BIND=21:13:508222。
重新上电,连接成功后HC-05上的指示灯将进入2s周期的快速双闪。

代码实现

/**
******************************************************************************
* @file main.c
* @author fire
* @version V1.0
* @date 2013-xx-xx
* @brief
******************************************************************************
* @attention
*
* 实验平台:野火 F103 STM32 开发板
* 论坛 :http://www.firebbs.cn
* 淘宝 :https://fire-stm32.taobao.com
*
******************************************************************************
*/

#include "main.h"
#include "stm32f1xx.h"
#include "./usart/bsp_debug_usart.h"
#include <stdlib.h>
#include <string.h>
#include "./led/bsp_led.h"
#include "./hc05/bsp_hc05.h"
#include "./usart/bsp_usart_blt.h"

extern ReceiveData DEBUG_USART_ReceiveData;
extern ReceiveData BLT_USART_ReceiveData;
extern UART_HandleTypeDef UartHandle;
extern UART_HandleTypeDef BLT_UartHandle;


/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
char hc05_name[30]="HC05_SLAVE";
char hc05_nameCMD[40];
uint16_t delay_5s = 0;

/* 配置系统时钟为72 MHz */
SystemClock_Config();


/* 初始化USART1 配置模式为 115200 8-N-1 */
DEBUG_USART_Config();
BLT_USART_Config();
LED_GPIO_Config();
printf("\r\n 蓝牙模块-AT指令测试例程 \r\n");

/* HC05蓝牙模块初始化:GPIO 和 USART3 配置模式为 38400 8-N-1 接收中断 */
if(HC05_Init() == 0)
{
HC05_INFO("HC05模块检测正常。");
}
else
{
HC05_ERROR("HC05模块检测不正常,请检查模块与开发板的连接,然后复位开发板重新测试。");
while(1);
}

/*各种命令测试演示,默认不显示。
*在bsp_hc05.h文件把HC05_DEBUG_ON 宏设置为1,
*即可通过串口调试助手接收调试信息*/

HC05_Send_CMD("AT+VERSION?\r\n",1);

HC05_Send_CMD("AT+ADDR?\r\n",1);

HC05_Send_CMD("AT+UART?\r\n",1);

HC05_Send_CMD("AT+CMODE?\r\n",1);

HC05_Send_CMD("AT+STATE?\r\n",1);

HC05_Send_CMD("AT+ROLE=0\r\n",1);

/*初始化SPP规范*/
HC05_Send_CMD("AT+INIT\r\n",1);
HC05_Send_CMD("AT+CLASS=0\r\n",1);
HC05_Send_CMD("AT+INQM=1,9,48\r\n",1);

/*设置模块名字*/
sprintf(hc05_nameCMD,"AT+NAME=%s\r\n",hc05_name);
HC05_Send_CMD(hc05_nameCMD,1);

HC05_INFO("本模块名字为:%s ,模块已准备就绪。",hc05_name);

delay_5s = HAL_GetTick();
while (1)
{
//每5秒检查一次是否连接
if( ( HAL_GetTick() - delay_5s ) % ( 5000 + uwTickFreq ) == 0 )
{
if( ! IS_HC05_CONNECTED() )
{
HC05_Send_CMD("AT+INQ\r\n",1);//模块在查询状态,才能容易被其它设备搜索到
Usart_SendString( UartHandle, "蓝牙尚未连接。请用手机打开蓝牙调试助手搜索连接蓝牙\r\n" );
}
else
{
Usart_SendString( UartHandle, "蓝牙已连接。发送“LED”可控制翻转LED灯\r\n" );
}
}

TransData_CtrlLED_Test(); //处理接收到的数据
}
}

/**
* @brief 处理串口数据控制LED灯
* @param 无
* @retval 无
*/
void TransData_CtrlLED_Test(void)
{
/* 处理调试串口接收到的串口助手数据 */
if(DEBUG_USART_ReceiveData.receive_data_flag == 1)
{
DEBUG_USART_ReceiveData.uart_buff[DEBUG_USART_ReceiveData.datanum] = 0;
//在这里可以自己定义想要接收的字符串然后处理
//这里接收到串口调试助手发来的 “LED”就会把板子上面的灯取反一次
if( strstr((char *)DEBUG_USART_ReceiveData.uart_buff,"LED") == (char *)DEBUG_USART_ReceiveData.uart_buff)
{
LED1_TOGGLE;
LED2_TOGGLE;
LED3_TOGGLE;
}

//如果手机蓝牙连接了模块,则发送到手机端显示数据
if( IS_HC05_CONNECTED() )
{
BLT_KEY_LOW;
Usart_SendStr_length(BLT_UartHandle, DEBUG_USART_ReceiveData.uart_buff, DEBUG_USART_ReceiveData.datanum);
}
//如果蓝牙没有被连接,如果数据是以AT开头的,就把KEY置高,设置蓝牙模块
else if( strstr((char *)DEBUG_USART_ReceiveData.uart_buff,"AT") == (char *)DEBUG_USART_ReceiveData.uart_buff )
{
BLT_KEY_HIGHT;
Usart_SendStr_length(BLT_UartHandle, DEBUG_USART_ReceiveData.uart_buff, DEBUG_USART_ReceiveData.datanum);
Usart_SendString(BLT_UartHandle,"\r\n");
BLT_KEY_LOW;
}

//串口助手显示接收到的数据
Usart_SendString( UartHandle, "\r\nrecv USART1 data:\r\n" );
Usart_SendString( UartHandle, DEBUG_USART_ReceiveData.uart_buff );
Usart_SendString( UartHandle, "\r\n" );

//清零调试串口数据缓存
DEBUG_USART_ReceiveData.receive_data_flag = 0; //接收数据标志清零
DEBUG_USART_ReceiveData.datanum = 0;
}

/* 处理蓝牙串口接收到的蓝牙数据 */
if(BLT_USART_ReceiveData.receive_data_flag == 1)
{
BLT_USART_ReceiveData.uart_buff[BLT_USART_ReceiveData.datanum] = '\0';
BLT_USART_ReceiveData.datanum = 0;
//在这里可以自己定义想要接收的字符串然后处理
//这里接收到手机蓝牙发来的 “LED”就会把板子上面的LED灯取反一次
if( strstr((char *)BLT_USART_ReceiveData.uart_buff,"LED") == (char*)BLT_USART_ReceiveData.uart_buff )
{
LED1_TOGGLE;
LED2_TOGGLE;
LED3_TOGGLE;
}

//串口助手显示接收到的数据
Usart_SendString( UartHandle, "\r\nrecv HC-05 data:\r\n" );
Usart_SendString( UartHandle, BLT_USART_ReceiveData.uart_buff );
Usart_SendString( UartHandle, "\r\n" );

//清零蓝牙串口数据缓存
BLT_USART_ReceiveData.receive_data_flag = 0; //接收数据标志清零
BLT_USART_ReceiveData.datanum = 0;
}
}

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

/**
******************************************************************************
* @file bsp_hc05.c
* @author fire
* @version V1.0
* @date 2015-xx-xx
* @brief HC05驱动
******************************************************************************
* @attention
*
* 实验平台:野火 F103 STM32 核心板
* 论坛 :http://www.firebbs.cn
* 淘宝 :https://fire-stm32.taobao.com
*
******************************************************************************
*/

#include "./hc05/bsp_hc05.h"
#include "./usart/bsp_usart_blt.h"
#include <string.h>
#include <stdio.h>
#include "./usart/bsp_debug_usart.h"



BLTDev bltDevList; //蓝牙设备列表,用于保存蓝牙设备数据
extern UART_HandleTypeDef BLT_UartHandle;
extern ReceiveData BLT_USART_ReceiveData;


/**
* @brief 初始化GPIO及检测HC05模块
* @param 无
* @retval HC05状态,0 正常,非0异常
*/
uint8_t HC05_Init(void)
{
uint8_t i;

HC05_GPIO_Config();//GPIO配置

BLT_USART_Config();//串口配置

for(i=0;i<BLTDEV_MAX_NUM;i++)
{
sprintf(bltDevList.unpraseAddr[i]," ");
sprintf(bltDevList.name[i]," ");
}
bltDevList.num = 0;

return HC05_Send_CMD("AT\r\n",1);
}

/**
* @brief 向HC05写入命令,不检查模块的响应
* @param command ,要发送的命令
* @param arg,命令参数,为0时不带参数,若command也为0时,发送"AT"命令
* @retval 无
*/
void writeCommand(const char *command, const char *arg)
{
char str_buf[50];

BLT_KEY_HIGHT;
hc05_delay_ms(10);

if (arg && arg[0] != 0)
sprintf(str_buf,"AT+%s%s\r\n",command,arg);
else if (command && command[0] != 0)
{
sprintf(str_buf,"AT+%s\r\n",command);
}
else
sprintf(str_buf,"AT\r\n");

HC05_DEBUG("CMD send:%s",str_buf);

Usart_SendString(BLT_UartHandle,(uint8_t *)str_buf);

}

/**
* @brief 向HC05模块发送命令并检查OK。只适用于具有OK应答的命令,最长等待5s直到收到OK
* @param cmd:命令的完整字符串,需要加\r\n。
* @param clean 命令结束后是否清除接收缓冲区,1 清除,0 不清除
* @template 复位命令: HC05_Send_CMD("AT+RESET\r\n",1);
* @retval 0,设置成功;其他,设置失败.
*/
uint8_t HC05_Send_CMD(char* cmd,uint8_t clean)
{
uint8_t retry=3;
uint32_t i;
uint16_t len;
char * redata;

while(retry--)
{
BLT_KEY_HIGHT;
Usart_SendString(BLT_UartHandle,(uint8_t *)cmd);
i=200; //初始化i,最长等待5秒
hc05_delay_ms(10); //

do
{
if(BLT_USART_ReceiveData.receive_data_flag == 1)
{
BLT_USART_ReceiveData.uart_buff[BLT_USART_ReceiveData.datanum] = '\0';
redata = get_rebuff(&len);
if(len>0)
{
if(strstr(redata,"OK"))
{
HC05_DEBUG("send CMD: %s",cmd); //打印发送的蓝牙指令和返回信息

HC05_DEBUG("recv back: %s",redata);

if(clean==1)
clean_rebuff();
// BLT_KEY_LOW;

return 0; //AT指令成功
}
}
}

hc05_delay_ms(10);

}while( --i ); //继续等待

HC05_DEBUG("send CMD: %s",cmd); //打印发送的蓝牙指令和返回信息
HC05_DEBUG("recv back: %s",redata);
HC05_DEBUG("HC05 send CMD fail %d times", retry); //提示失败重试

}

HC05_DEBUG("HC05 send CMD fail ");

if(clean==1)
clean_rebuff();

return 1; //AT指令失败
}


/**
* @brief 向HC05模块发送命令并检查OK。只适用于具有OK应答的命令,等待固定时间
* @param cmd:命令的完整字符串,需要加\r\n。
* @param clean 命令结束后是否清除接收缓冲区,1 清除,0 不清除
* @param delayms 等待时间,单位ms
* @template 查询命令: HC05_Send_CMD_Wait("AT+INQ\r\n",0, 5000);
* @retval 0,设置成功;其他,设置失败.
*/
uint8_t HC05_Send_CMD_Wait(char* cmd, uint8_t clean, uint32_t delayms) //Debug
{
uint16_t len;
char * redata;

BLT_KEY_HIGHT;
Usart_SendString(BLT_UartHandle,(uint8_t *)cmd);

hc05_delay_ms(delayms); //固定延时

redata = get_rebuff(&len);
if(len>0)
{
redata[len] = '\0';
if(redata[0]!=0)
{
HC05_DEBUG("send CMD: %s",cmd);

HC05_DEBUG("receive %s",redata);
}
if(strstr(redata,"OK"))
{

if(clean==1)
clean_rebuff();
BLT_KEY_LOW;
return 0; //AT指令成功
}
}

BLT_KEY_LOW;
HC05_DEBUG("HC05 send CMD fail ");

if(clean==1)
clean_rebuff();

return 1; //AT指令失败
}


/**
* @brief 使用HC05透传字符串数据
* @param str,要传输的字符串
* @retval 无
*/
void HC05_SendString(char* str)
{
BLT_KEY_LOW;
Usart_SendString(BLT_UartHandle,(uint8_t *)str);
}


/**
* @brief 在str中,跳过它前面的prefix字符串,
如str为"abcdefg",prefix为"abc",则调用本函数后返回指向"defg"的指针
* @param str,原字符串
* @param str_length,字符串长度
* @param prefix,要跳过的字符串
* @retval 跳过prefix后的字符串指针
*/
char *skipPrefix(char *str, size_t str_length, const char *prefix)
{

uint16_t prefix_length = strlen(prefix);

if (!str || str_length == 0 || !prefix)
return 0;

if (str_length >= prefix_length && strncmp(str, prefix, prefix_length) == 0)
return str + prefix_length;

return 0;
}

/**
* @brief 从stream中获取一行字符串到line中
* @param line,存储获得行的字符串数组
* @param stream,原字符串数据流
* @param max_size,stream的大小
* @retval line的长度,若stream中没有‘\0’,'\r','\n',则返回0
*/
int get_line(char* line, char* stream ,int max_size)
{
char *p;
int len = 0;
p=stream;
while( *p != '\0' && len < max_size ){
line[len++] = *p;
p++;
if('\n' == *p || '\r'==*p)
break;
}

if(*p != '\0' && *p != '\n' && *p != '\r')
return 0;


line[len] = '\0';
return len;
}

/**
* @brief 替换 rawstr 字符串中的所有 ':'字符
* @param rawstr,源字符串
* @param delimiter,要替换成的字符
*/
void rawReplaceChar(char *rawstr, char delimiter)
{
do
{
rawstr = strchr(rawstr, ':');
if(rawstr) *rawstr = delimiter;
}while(rawstr != NULL);

}



/**
* @brief 扫描周边的蓝牙设备,并存储到设备列表中。
* @retval 是否扫描到设备,0表示扫描到,非0表示没有扫描到
*/
uint8_t parseBltAddr(void)
{
char *redata;
uint16_t len;

//清空蓝牙设备列表
bltDevList.num = 0;

HC05_INFO("正在查询设备列表...");
HC05_Send_CMD("AT+INQ\r\n",0);
// HC05_Send_CMD_Wait("AT+INQ\r\n",0, 5000); //固定延时 5s
redata = get_rebuff(&len);

if(redata[0] != 0 && strstr(redata, "+INQ:") != 0 && strstr(redata, "OK") != 0)
{
char *pstr = strstr(redata, "+INQ:"); //初始化pstr
char *ptmp;
int num, i;

for(num=0; num<BLTDEV_MAX_NUM; ) //这里限制允许扫描到的蓝牙设备数量
{
pstr = strstr(pstr, "+INQ:");
if(pstr != NULL)
{
pstr += 5;
ptmp = strchr(pstr, ','); //蓝牙地址的末尾
*ptmp = '\0'; //字符串结束标志,替换','
HC05_DEBUG("正在解析Str=%s", pstr);

num ++;
HC05_DEBUG("假设该地址有效,num = %d ", num);
if(num == 1)
{
strcpy(bltDevList.unpraseAddr[num-1], pstr); //复制第一个蓝牙地址字符串
HC05_DEBUG("是有效地址,num=%d, Addr: %s\r\n", num, bltDevList.unpraseAddr[num-1]);
}
for(i=0; i<num-1; i++) //比较前面的地址是否重复
{
if( strcmp(pstr, bltDevList.unpraseAddr[i]) == 0 )
{
num --; //这两个地址字符串相等时,表示该地址与解码语句的地址相同,丢弃该重复地址
HC05_DEBUG("是重复地址,num = %d\r\n", num);
break;
}

if(i == num-2)
{
strcpy(bltDevList.unpraseAddr[num-1], pstr); //复制蓝牙地址字符串

HC05_DEBUG("是有效地址,num=%d, Addr: %s\r\n", num, bltDevList.unpraseAddr[num-1]);
}
}

pstr = ptmp + 1; // pstr 跳过'\0'字符
}
else
{
break; //没有检测到"+INQ:"说明扫描完毕退出
}

}

bltDevList.num = num; //保存实际蓝牙地址数目

//打印所有蓝牙地址
HC05_INFO("Addr List (%d):", bltDevList.num);
for(i=0; i< num; i++)
{
HC05_INFO("Addr %d: %s", i, bltDevList.unpraseAddr[i]);
}
HC05_INFO("扫描完毕!\r\n");

clean_rebuff();
HC05_Send_CMD("AT+INQC\r\n",1);//退出前中断查询

return 0;
}

HC05_INFO("没有找到蓝牙设备!\r\n");

clean_rebuff();
HC05_Send_CMD("AT+INQC\r\n",1);//退出前中断查询

return 1;
}

/**
* @brief 获取远程蓝牙设备的名称
* @param bltDev ,蓝牙设备列表指针
* @retval 0获取成功,非0不成功
*/
uint8_t getRemoteDeviceName(void)
{
uint8_t i;
char *redata;
uint16_t len;

char linebuff[50];
uint16_t linelen;
char *p;

char cmdbuff[100];


HC05_DEBUG("device num =%d", bltDevList.num);

for(i=0;i<bltDevList.num;i++)
{
rawReplaceChar(bltDevList.unpraseAddr[i], ','); //地址格式替换':'为','
sprintf(cmdbuff,"AT+RNAME?%s\r\n",bltDevList.unpraseAddr[i]);
HC05_Send_CMD(cmdbuff,0); //发送获取蓝牙名字命令

redata =get_rebuff(&len);
if(redata[0] != 0 && strstr(redata, "OK") != 0)
{

linelen = get_line(linebuff,redata,len);
if(linelen>50 && linelen !=0 ) linebuff[linelen]='\0'; //超长截断

p = skipPrefix(linebuff,linelen,"+RNAME:");
if(p!=0)
{
strcpy(bltDevList.name[i],p);
}

}
else
{
strcpy(bltDevList.name[i], "<名字获取失败>");
HC05_INFO("蓝牙名字获取失败!\r\n");
}

clean_rebuff(); //清空缓存

}

return 0;

}

/**
* @brief 输出蓝牙设备列表
* @param bltDev ,蓝牙设备列表指针
* @retval 无
*/
void printBLTInfo(void)
{
uint8_t i;

#ifdef ENABLE_LCD_DISPLAY
char disp_buff[100];

sprintf(disp_buff," %d device found.",bltDevList.num);
LCD_SetColors(RED,BLACK);
ILI9341_Clear(0,120,240,200);
ILI9341_DispString_EN( 50, 120,disp_buff );
#endif

if(bltDevList.num==0)
{
HC05_INFO("/*******No remote BLT Device or in SLAVE mode********/");
}
else
{
HC05_INFO("扫描到 %d 个蓝牙设备",bltDevList.num);


for(i=0;i<bltDevList.num;i++)
{
HC05_INFO("/*******Device[%d]********/",i);
HC05_INFO("Device Addr: %s",bltDevList.unpraseAddr[i]);
HC05_INFO("Device name: %s",bltDevList.name[i]);

#ifdef ENABLE_LCD_DISPLAY
LCD_SetColors(YELLOW,BLACK);
sprintf(disp_buff," /*******Device[%d]********/",i);
ILI9341_DispString_EN( 5, 140+i*60,disp_buff );

sprintf(disp_buff,"Device Addr: %s",bltDevList.unpraseAddr[i]);
ILI9341_DispString_EN( 5, 160+i*60,disp_buff );

sprintf(disp_buff,"Device name: %s",bltDevList.name[i]);
ILI9341_DispString_EN( 5, 180+i*60,disp_buff );
#endif
}
}

}



/**
* @brief 扫描蓝牙设备,并连接名称中含有"HC05"的设备
* @param 无
* @retval 0获取成功,非0不成功
*/
uint8_t linkHC05(void)
{
uint8_t i=0;
char cmdbuff[100];

parseBltAddr(); //查询蓝牙设备并解析地址
getRemoteDeviceName(); //获取蓝牙设备名字
printBLTInfo();

for(i=0;i<=bltDevList.num;i++)
{
if(strstr(bltDevList.name[i],"HC05") != NULL) //非NULL表示找到有名称部分为HC05的设备
{
HC05_INFO("搜索到远程HC05模块,即将进行配对连接...");

#ifdef ENABLE_LCD_DISPLAY
LCD_SetColors(YELLOW,BLACK);
ILI9341_Clear(0,80,240,20);
ILI9341_DispString_EN( 5, 80,"Found a HC05 ,conecting..." );
#endif

//配对
sprintf(cmdbuff,"AT+PAIR=%s,20\r\n",bltDevList.unpraseAddr[i]);
HC05_Send_CMD(cmdbuff,0);

//连接
sprintf(cmdbuff,"AT+LINK=%s\r\n",bltDevList.unpraseAddr[i]);

return HC05_Send_CMD(cmdbuff,0);
}
}

HC05_INFO("未搜索到名称中含有HC05的设备...");

return 1;

}

posted @ 2025-11-05 14:59  张大帅哥  阅读(14)  评论(0)    收藏  举报