HC-05蓝牙主从模式实时隔空对时

目录

一、汇承HC-05蓝牙模块介绍

1.蓝牙简介

2.基本参数

3.工作原理

4.与各个设备连接

(1)与MCU之间的连接

(2)与手机之间的连接

(3)与模块之间的连接

5.模块原理图

6.常用AT指令

7.HC-T串口助手测试

二、开发板

三、代码部分

1.获取电脑系统时间

2.蓝牙主机程序

3.蓝牙从机程序

四、实验现象

1.获取时间程序现象

2.蓝牙主机

3.从机接收时间现象


一、汇承HC-05蓝牙模块介绍

1.蓝牙简介

         HC-05 蓝牙串口通信模块,是基于 Bluetooth Specification V2.0 带 EDR 蓝牙协议的数传模块。无线工作频段为 2.4GHz ISM,调制方式是 GFSK。模块最大发射功率为 4dBm,接收灵敏度-85dBm,板载 PCB 天线,可以实现 10 米距离通信。
         模块采用邮票孔封装方式,模块大小 27mm×13mm×2mm,方便客户嵌入应用系统之内,自带 LED 灯,可直观判断蓝牙的连接状态。
         模块采用 CSR 的 BC417 芯片,支持 AT 指令,用户可根据需要更改角色(主、从模式)以及串口波特率、设备名称等参数,使用灵活。

2.基本参数

3.工作原理

如上图所示,HC-05 模块用于代替全双工通信时的物理连线。左边的设备向模块发送串口数据,模块的 RXD 端口收到串口数据后,自动将数据以无线电波的方式发送到空中。右边的模块能自动接收到,并从 TXD 还原最初左边设备所发的串口数据。从右到左也是一样的。
 

4.与各个设备连接

(1)与MCU之间的连接

①:模块与供电系统为 3.3V 的 MCU 连接时,串口交叉连接即可(模块的 RX 接 MCU 的 TX、模块的 TX 接 MCU的 RX)
②:模块与供电系统为 5V 的 MCU 连接时,可在模块的 RX 端串接一个 220R~1K 电阻再接 MCU 的 TX,模块的TX 直接接 MCU 的 RX,无需串接电阻。(注:请先确认所使用的 MCU 把 3.0V 或以上电压认定为高电平,否则需加上 3.3V/5V 电平转换电路)
注:模块的电源为 3.3V,不能接 5V, 5V 的电源必须通过 LDO 降压到 3.3V 后再给模块供电

我是直接连接开发板5V,暂时没出现什么问题

(2)与手机之间的连接

HC-05 可以与安卓手机自带蓝牙连接,通讯测试可以使用安卓串口助手软件,可在汇承官网下载

广州汇承信息科技有限公司https://www.hc01.com/downloads

(3)与模块之间的连接

        设置一个为主机,一个为从机,配对码一致(默认均为 1234),波特率一致,上电即可自动连接。
        HC-05 支持一对一连接。
         在连接模式 CMODE 为 0 时,主机第一次连接后,会自动记忆配对对象,如需连接其他模块, 必须先清除配对记忆。在连接模式 CMODE 为 1 时,主机则不受绑定指令设置地址的约束,可以与其他从机模块连接。

5.模块原理图

6.常用AT指令

7.HC-T串口助手测试

初学者套餐里带有HC-T,插上通电就可以简单测试AT指令。

二、开发板

使用的两块开发板分别为野火STM32F103RCT6MIN开发板、野火STM32F103ZET6核心板双USB款开发板分别作为蓝牙主机和从机

野火STM32F103ZET6

STM32F103RCT6MIN

三、代码部分

1.获取电脑系统时间

软件使用VS2022

#define  _CRT_SECURE_NO_WARNINGS 1


#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

#define MAX_PORTS 32

typedef struct {
    char name[64];
} PortInfo;

void getCurrentTimeString(char* buffer, int len) {
    time_t now = time(NULL);
    struct tm* t = localtime(&now);
    strftime(buffer, len, "T%Y-%m-%d %H:%M:%S\n", t);//获取计算机时间
}

int tryOpenPort(const char* portName) {
    HANDLE h = CreateFileA(portName, GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
    if (h == INVALID_HANDLE_VALUE) return 0;
    CloseHandle(h);
    return 1;
}

HANDLE openSerialPort(const char* portName) {
    HANDLE hSerial = CreateFileA(portName, GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
    if (hSerial == INVALID_HANDLE_VALUE) return NULL;

    DCB dcb = { 0 };  // 创建 DCB 结构体并清零
    dcb.DCBlength = sizeof(dcb);  // 设置 DCB 结构体长度
    GetCommState(hSerial, &dcb);  // 获取当前串口配置
    dcb.BaudRate = CBR_115200;  // 设置波特率为 115200
    dcb.ByteSize = 8;  // 数据位 8 位
    dcb.StopBits = ONESTOPBIT;  // 停止位 1 位
    dcb.Parity = NOPARITY;  // 无奇偶校验位
    SetCommState(hSerial, &dcb);  // 应用新的串口配置


    COMMTIMEOUTS timeouts = { 0 };
    timeouts.WriteTotalTimeoutConstant = 50;
    SetCommTimeouts(hSerial, &timeouts);

    return hSerial;
}

int listAvailablePorts(PortInfo* ports) {
    int count = 0;
    for (int i = 1; i <= 30; i++) {
        char name[64];
        sprintf(name, "\\\\.\\COM%d", i);
        if (tryOpenPort(name)) {
            strcpy(ports[count].name, name);
            count++;
        }
    }
    return count;
}

int main() {
    PortInfo ports[MAX_PORTS];
    int portCount = listAvailablePorts(ports);

    if (portCount == 0) {
        printf("未发现任何可用串口。\n");
        return 1;
    }

    printf(" 发现以下串口:\n");
    for (int i = 0; i < portCount; i++) {
        printf(" [%d] %s\n", i + 1, ports[i].name);
    }

    int choice = 0;
    printf("请选择串口编号(1-%d):", portCount);
    scanf("%d", &choice);

    if (choice < 1 || choice > portCount) {
        printf("输入编号无效。\n");
        return 1;
    }

    const char* selectedPort = ports[choice - 1].name;
    printf("选择串口:%s\n", selectedPort);

    HANDLE hSerial = NULL;

    while (1) {
        if (!hSerial || hSerial == INVALID_HANDLE_VALUE) {
            hSerial = openSerialPort(selectedPort);
            if (!hSerial) {
                printf("串口打开失败,5秒后重试...\n");
                Sleep(5000);
                continue;
            }
            printf("串口连接成功,开始发送时间。\n");
        }

        char timeStr[64];
        getCurrentTimeString(timeStr, sizeof(timeStr));

        DWORD bytesWritten;
        BOOL success = WriteFile(hSerial, timeStr, strlen(timeStr), &bytesWritten, NULL);
        if (!success || bytesWritten == 0) {
            printf("写入失败,串口可能已断开。\n");
            CloseHandle(hSerial);
            hSerial = NULL;
            continue;
        }

        printf("已发送:%s", timeStr);
        Sleep(1000);
    }

    if (hSerial) CloseHandle(hSerial);
    return 0;
}

运行代码现象如下,先选择要使用的串口,我这个电脑不使用单片机时为COM1,使用单片机时为COM3,传输时间需要使用作为主机的串口。

2.蓝牙主机程序

void CheckConnect_LinkHC05_Test(void)
{
	static uint8_t last_connected = 0;  // 记录上一次是否连接

	if( (last_connected==1) && ( !IS_HC05_CONNECTED() ) )
	{
		HC05_INFO("蓝牙连接已断开\r\n");
		last_connected=0;	//	重置状态
	}
	if( !IS_HC05_CONNECTED() ) // 判断蓝牙是否未连接,IS_HC05_CONNECTED 是用户自定义的状态检测函数
	{
		Usart_SendString(USART1, "蓝牙尚未连接\r\n"); // 通过串口提示未连接

		if(hc05_role == 1) // 如果当前蓝牙模块为主机模式
		{
			HC05_INFO("正在扫描蓝牙设备..."); // 打印扫描提示信息

			/* 搜索蓝牙模块,并尝试连接 */
			if( linkHC05() == 0 ) // linkHC05 函数负责扫描并连接设备,返回0表示连接成功
			{
				while( !IS_HC05_CONNECTED() ); // 等待连接状态变为已连接(INT引脚拉低等方式)
			}
		}
		else // 当前为从机模式
		{
			HC05_INFO("请搜索连接蓝牙..."); // 提示等待主机连接
			HC05_Send_CMD("AT+INQ\r\n", 1); // 发送 AT 命令让 HC-05 进入查询状态,便于被搜索
		}
	}
	else // 蓝牙已连接
	{
		if(last_connected == 0) // 只在首次连接时打印一次信息
		{
			HC05_INFO("主机蓝牙已连接上从机\r\n"); // 显示连接成功信息
//			HC05_INFO("发送指令1获取蓝牙工作状态\r\n"); // 提示用户可发送数据

//			sprintf(sendData, "<%s> 蓝牙已连接从机,发送指令1获取蓝牙工作状态\r\n", hc05_name); // 格式化字符串,包含设备名
//			HC05_SendString(sendData); // 通过蓝牙发送数据给对方

			last_connected = 1; // 设置标志位,防止重复提示
		}
		
			// 如果收到了串口时间字符串,就转发给蓝牙从机
		if (USART_RX_FLAG)
		{
			USART_RX_FLAG = 0;  // 清除标志位
			
			if(USART_RX_BUF[0] == 'T'&& strlen(USART_RX_BUF)>10)
			{
				HC05_INFO("转发时间字符串至从机:\r\n");
//				HC05_INFO(USART_RX_BUF[20]); // 调试打印

				HC05_SendString(USART_RX_BUF); // 通过蓝牙发送时间字符串
				HC05_INFO("时间已通过蓝牙发送\r\n");
			}
			else
			{
				HC05_INFO("接收到非法数据,已丢弃\r\n");
			}
		}
		
	}
}


/**
  * @brief  检查蓝牙接收缓冲区、处理接收数据
  * @param  无
  * @retval 无
  */
void CheckRecvBltBuff_Test(void)
{
	char* redata;
	uint16_t len;
	
	if( IS_HC05_CONNECTED())
	{
		uint16_t linelen;
		
		redata = get_rebuff(&len);
		linelen = get_line(linebuff, redata,len);
		if(linelen < 200 && linelen !=0)
		{
 			if(strcmp(redata, "1") == 0) {
				LED1_ON;
				HC05_SendString("LED1_ON_OK\r\n");
//				HC05_Send_CMD("AT+STATE?\r\n",1);	//返回值为connected,表示是连接状态
			 } 
			else if(strcmp(redata,"master") == 0)
			{
				HC05_Send_CMD("AT+DISC\r\n",1);
				Delay_ms(5000);
				Switch_HC05Mode_Test();
			

				LED1_ON;
				HC05_SendString("LED1_OFF_OK\r\n");
			}
		else
		{
			/*这里只演示显示单行的数据,如果想显示完整的数据,可直接使用redata数组*/
			HC05_INFO("Receive:\r\n%s",linebuff);
		}
			
		/*处理数据后,清空接收蓝牙模块数据的缓冲区*/
		clean_rebuff();
		}
	}
	else
	{
//        Usart_SendString( USART1, "\r\n蓝牙未连接。接收到蓝牙返回数据:\r\n" );
//        redata = get_rebuff(&len); 
//        Usart_SendString( USART1, (uint8_t *)redata );
//        Usart_SendString( USART1, "\r\n\r\n" );		
	}
}

代码从野火蓝牙综合示例程序移植而来,修改了部分程序,上述程序是其中最主要的两段程序,以此作为主机来发送从系统获取到的时间

3.蓝牙从机程序

蓝牙从机部分驱动程序

/**
  * @brief  检查蓝牙连接
  *         作为主机时,搜索蓝牙并连接名字含有“HC05”的蓝牙模块
  *         作为从机时,连接后通过蓝牙模块发送字符串
  * @param  无
  * @retval 无
  */
void CheckConnect_LinkHC05_Test(void)
{
	static uint8_t last_connected = 0;  // 记录上一次是否连接

	if( (last_connected==1) && ( !IS_HC05_CONNECTED() ) )
	{
		HC05_INFO("蓝牙连接已断开\r\n");
		last_connected=0;	//	重置状态
	}
	if( !IS_HC05_CONNECTED() ) // 判断蓝牙是否未连接,IS_HC05_CONNECTED 是用户自定义的状态检测函数
	{
		Usart_SendString(USART1, "蓝牙尚未连接\r\n"); // 通过串口提示未连接

		if(hc05_role == 1) // 如果当前蓝牙模块为主机模式
		{
			HC05_INFO("正在扫描蓝牙设备..."); // 打印扫描提示信息

			/* 搜索蓝牙模块,并尝试连接 */
			if( linkHC05() == 0 ) // linkHC05 函数负责扫描并连接设备,返回0表示连接成功
			{
				while( !IS_HC05_CONNECTED() ); // 等待连接状态变为已连接(INT引脚拉低等方式)
			}
		}
		else // 当前为从机模式
		{
			HC05_INFO("请搜索连接蓝牙..."); // 提示等待主机连接
			HC05_Send_CMD("AT+INQ\r\n", 1); // 发送 AT 命令让 HC-05 进入查询状态,便于被搜索
		}
	}
	else // 蓝牙已连接
	{
		if(last_connected == 0) // 只在首次连接时打印一次信息
		{
			HC05_INFO("从机蓝牙已连接上主机\r\n"); // 显示连接成功信息
//			HC05_INFO("手机发送指令1\r\n"); // 提示用户可发送数据

//			sprintf(sendData, "<%s> 蓝牙从机已连接,发送指令1获取蓝牙工作状态\r\n", hc05_name); // 格式化字符串,包含设备名
//			HC05_SendString(sendData); // 通过蓝牙发送数据给对方

			last_connected = 1; // 设置标志位,防止重复提示
		}
		
	}
}

/**
  * @brief  检查蓝牙接收缓冲区、处理接收数据
  * @param  无
  * @retval 无
  */
void CheckRecvBltBuff_Test(void)
{
	char* redata;
	uint16_t len;
	char buff[256] = "/";
	char filename[64];  // 存储接收的文件名
	
	if( IS_HC05_CONNECTED())
	{
		uint16_t linelen;
		
		redata = get_rebuff(&len);
		linelen = get_line(linebuff, redata,len);
		
		if(linelen < 200 && linelen !=0)
		{
 			if(strcmp(redata, "1") == 0) {
				LED1_ON;
				HC05_SendString("LED1_ON_OK\r\n");
			 }
			else if(strcmp(redata,"2") == 0)
			{
				LED1_OFF;
				HC05_SendString("LED1_OFF_OK\r\n");
			}
		else
		{
			/*这里只演示显示单行的数据,如果想显示完整的数据,可直接使用redata数组*/
			HC05_INFO("Receive:\r\n%s",linebuff);
		}
			
		/*处理数据后,清空接收蓝牙模块数据的缓冲区*/
		clean_rebuff();
		}
	}
	else
	{
//        Usart_SendString( USART1, "\r\n蓝牙未连接。接收到蓝牙返回数据:\r\n" );
//        redata = get_rebuff(&len); 
//        Usart_SendString( USART1, (uint8_t *)redata );
//        Usart_SendString( USART1, "\r\n\r\n" );		
	}
}

蓝牙从机主程序main.c

#include "stm32f10x.h"
#include <string.h>
#include <stdlib.h>
#include "bsp_spi_sdcard.h"	
#include "ff.h"
#include "./delay/delay.h"
#include "./sys/sys.h"
#include "./DS3231/DS3231.h"
#include "./MyI2C/MyI2C.h"
#include "./Timer/Timer.h"
#include "./led/bsp_led.h"
#include "./FAT/FAT.h"
#include "./OLED/OLED.h"
#include "./data_change/data_change.h"
#include "./Serial/Serial.h"
#include "./Key/bsp_key.h"  
#include "./XYCheck/XYCheck.h"
#include "./HC05/HC05.h"
#include "./HC05/HC05_USART.h"
#include "./dwt_delay/core_delay.h"   
#include "./systick/bsp_SysTick.h" 
#include "./lcd/bsp_ili9341_lcd.h"

uint8_t DataBit;
uint8_t data =0x00;

uint8_t RxData;


extern uint16_t lcdid;
char time_str[20];
char date_str[20];

extern int hc05_inquery_connect;	//每3s检查蓝牙是否连接
extern int hc05_check_recvbuff; //每500ms检查蓝牙是否连接


void oled_show(void);
void Display_Time_Date(void);

char timeStr[32];

void Display_DS3231_Time(void);
void parse_and_set_time(char *str);


int main(void)
{

	

	Timer_Init();
	SysTick_Init();		 /* SysTick 10ms中断初始化 */
	MyI2C1_Init();
	DS3231_Init();			//DS3231初始化
	LED_GPIO_Config();
	Serial_Init();
	OLED_Init();
	Key_GPIO_Config();	/* 按键端口初始化 */
	Hc05_USART_Config();
	CPU_TS_TmrInit();	/* 延时函数初始化 */
//	ILI9341_Init ();  //屏幕初始化


//	Set_DS3231_Time(25,4,2,12,00,00,3);//设置时间为2024年10月29日15时00分00秒星期2,下载一次之后,需要注释掉重新下载
//	Get_DS3231_Time();	//获取DS3231的时间	


	if(HC05_Init() ==0 )
	 {
		HC05_INFO("HC05模块检测正常");
	 }
	else
	 {	
		HC05_ERROR("HC05模块不正常,请重新测试");
		 while(1);
	 }
	 
	 /*复位、恢复默认状态*/
	HC05_Send_CMD("AT+RESET\r\n",1);	//复位指令发送完成之后,需要一定时间HC05才会接受下一条指令	
	HC05_Send_CMD("AT+ORGL\r\n",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=1\r\n",1);
	HC05_Send_CMD("AT+PSWD=9632\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);
	 
	 

	 
		
	while(1)
	{
		if(1 == hc05_inquery_connect)
		{
			hc05_inquery_connect = 0;	//清零标志位
			CheckConnect_LinkHC05_Test();
		}
		
		//连接后每个一段时间检查接受缓冲区
		if(1 == hc05_check_recvbuff)
		{
			hc05_check_recvbuff = 0;	//			清零标志位
			CheckRecvBltBuff_Test();
		
		}
		
		if (USART_RX_FLAG)
		{
			USART_RX_FLAG = 0;  // 清除标志位

    // 判断是否为设置时间指令
    if (USART_RX_BUF[0] == 'T')
    {
		 int year, month, day, hour, min, sec;
		
        int parsed = sscanf(USART_RX_BUF, "T%4d-%2d-%2d %2d:%2d:%2d",
            &year, &month, &day, &hour, &min, &sec);

        if (parsed == 6)  // 确保成功解析了6个字段
        {
            Set_DS3231_Time(year % 100, month, day, hour, min, sec, 0);  // 设置时间
			HC05_INFO("SetTime_OK\r\n");
//            HC05_SendString("SetTime_OK\r\n");
            HC05_INFO("已设置时间为:\r\n");
//            HC05_INFO(USART_RX_BUF);
			HC05_SendString(USART_RX_BUF);
			
		Display_DS3231_Time();
        Delay_ms(980);  // 每秒刷新一次
        }
        else
        {
            HC05_SendString("SetTime_ERROR\r\n");
            HC05_INFO("时间格式错误,未设置\r\n");
        }
    }

	}


	}

}



void Display_DS3231_Time(void)
{
	
		Get_DS3231_Time();  // 更新 calendar 结构体
  		sprintf(timeStr, "%02d-%02d-%02d", calendar.w_year+2000, calendar.w_month, calendar.w_date);
		OLED_ShowString(1, 3, timeStr);

		sprintf(timeStr, "%02d:%02d:%02d", calendar.hour, calendar.min, calendar.sec);
		OLED_ShowString(3, 3, timeStr);

}






四、实验现象

1.获取时间程序现象

2.蓝牙主机

蓝牙助手连接蓝牙主机,发送master之后,然后再断开蓝牙,或者按键KEY1,切换主从模式,上电默认为从模式。串口助手显示连接上从机之后,需要断开串口,获取时间的程序和串口助手所使用的串口为一个串口号,不能同时使用

3.从机接收时间现象

蓝牙从机显示接收到了来自主机时间,接收到时间同时也会设置到DS3231模块,后续也可以用OLED屏显示,上述程序暂时没有加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值