蓝牙4.0
概述
蓝牙是一种支持设备短距离通信(一般10m内)的无线电技术,能在包括移动电话、无线耳机、笔记本电脑、相关外设等众多设备之间进行无线信息交换。利用蓝牙技术,能够有效地简化移动通信终端设备之间的通信。从蓝牙4.0开始有两个分支,经典4.0和BLE4.0,经典4.0就是传统的3.0蓝牙升级而成,向下兼容。而BLE4.0是一个新的分支,不向下兼容。BLE是Bluetooth Low Energy低功耗蓝牙的缩写,顾名思义,其功耗较低。
本人使用的蓝牙模块型号为DX-BT05采用美国TI公司CC2541芯片,配置256Kb空间,遵循V4.0 BLE蓝牙规范。支持AT指令,用户可根据需要更改串口波特率、设备名称、配对密码等参数,可使用串口与单片机进行通信。
DX-BT05接线图
从上图中可以看到,蓝牙模块充当了一个“桥梁”的作用,用于转发和接收手机的数据,图中蓝牙模块的state引脚是一个状态引脚,当手机或者是其他蓝牙设备连接上蓝牙后这个引脚便会输出低电平,将开发板的一个I/O引脚配置为中断引脚下降沿触发,当有手机连接上了蓝牙,便会产生中断,以此来做一些需要的操作,也可以不用连接此引脚。
AT指令模式
当蓝牙没有连接手机或者是其他蓝牙设备的时候,此时蓝牙模块的模式为AT指令模式,此模式是用于设置蓝牙一些参数的,如软件版本号、蓝牙地址,蓝牙名等,如下所示为AT指令的一部分:
例子:
发送:
AT+NAME\r\n ——查询模块设备名
返回:
+NAME=BT05\r\n ——返回模块设备名为:“BT05”
实验程序
下面程序将用手机通过蓝牙进行来控制开发板的LED亮灭,注意程序一开始需要设置蓝牙的一些参数(AT指令模式),所以一开始不能连接手机,需要等待3到4秒钟,在微信小程序搜索蓝牙串口,打开GPS和蓝牙便可以找到相应的设备名进行连接。注意程序的串口1用于打印调试信息,串口3用于连接蓝牙。当手机与蓝牙连接上后便会进入数据透传模式,这时候开发板和手机便可以通信了,蓝牙模块的指示灯也会从闪烁变为长亮。
#include "stm32f4xx.h"
#include <stdio.h>
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
USART_InitTypeDef USART_InitStructure;
#define PBout(n) *((volatile uint32_t *)(0x42000000+(GPIOB_BASE+0x14-0x40000000)*32+(n)*4))
#define PEout(n) *((volatile uint32_t *)(0x42000000+(GPIOE_BASE+0x14-0x40000000)*32+(n)*4))
#define PFout(n) *((volatile uint32_t *)(0x42000000+(GPIOF_BASE+0x14-0x40000000)*32+(n)*4))
#define PEin(n) *((volatile uint32_t *)(0x42000000+(GPIOE_BASE+0x10-0x40000000)*32+(n)*4))
#pragma import(__use_no_semihosting_swi)
struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;
int fputc(int ch, FILE *f)
{
USART_SendData(USART1,ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return ch;
}
void _sys_exit(int return_code) {
label: goto label; /* endless loop */
}
void delay_us(uint32_t n)
{
SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器后才能取配置
SysTick->LOAD = 21*n-1; // 填写计数值,就是我们的延时时间
SysTick->VAL = 0; // 清空标志位
SysTick->CTRL = 1; // 选中21MHz的时钟源,并开始让系统定时器工作
while ((SysTick->CTRL & 0x10000)==0);//等待计数完毕
SysTick->CTRL = 0; // 不再使用就关闭系统定时器
}
void delay_ms(uint32_t n)
{
while(n--)
{
SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器后才能取配置
SysTick->LOAD = 21000-1; // 填写计数值,就是我们的延时时间
SysTick->VAL = 0; // 清空标志位
SysTick->CTRL = 1; // 选中21MHz的时钟源,并开始让系统定时器工作
while ((SysTick->CTRL & 0x10000)==0);//等待计数完毕
}
SysTick->CTRL = 0; // 不再使用就关闭系统定时器
}
void usart1_init(uint32_t baud)
{
//打开端口A硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE );
//打开串口1的硬件时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE );
//配置PA9和PA10引脚,为AF模式(复用功能模式)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;//指定第9根引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF ;//配置为复用功能模式
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;//配置引脚的响应时间=1/100MHz .
//从高电平切换到低电平1/100MHz,速度越快,功耗会越高
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP ;//推挽的输出模式,增加输出电流和灌电流的能力
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不使能内部上下拉电阻
GPIO_Init(GPIOA ,&GPIO_InitStructure);
//将PA9和PA10的功能进行指定为串口1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//配置串口1的参数:波特率、数据位、校验位、停止位、流控制
USART_InitStructure.USART_BaudRate = baud;//波特率
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(USART1, &USART_InitStructure);
//使能串口1的接收中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//使能串口1工作
USART_Cmd(USART1, ENABLE);
}
void usart3_init(uint32_t baud)
{
//打开端口B硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE );
//打开串口3的硬件时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE );
//配置PB10和PB11引脚,为AF模式(复用功能模式)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;//指定第10 11根引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF ;//配置为复用功能模式
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;//配置引脚的响应时间=1/100MHz .
//从高电平切换到低电平1/100MHz,速度越快,功耗会越高
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP ;//推挽的输出模式,增加输出电流和灌电流的能力
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不使能内部上下拉电阻
GPIO_Init(GPIOB ,&GPIO_InitStructure);
//将PB10和PB11的功能进行指定为串口1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3);
//配置串口1的参数:波特率、数据位、校验位、停止位、流控制
USART_InitStructure.USART_BaudRate = baud;//波特率
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(USART3, &USART_InitStructure);
//使能串口3的接收中断
USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//使能串口3工作
USART_Cmd(USART3, ENABLE);
}
void usart3_send_str(char *str)
{
char *p = str;
while(p && (*p!='\0'))
{
USART_SendData(USART3,*p);
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET);
p++;
}
}
void ble_set_config(void)
{
//设置好模块的名字
usart3_send_str("AT+NAMEDingH\r\n");
delay_ms(500);
//获取模块的地址信息,因为通过手机搜索的时候,
usart3_send_str("AT+LADDR\r\n");
delay_ms(500); //重新启动模块
usart3_send_str("AT+RESET\r\n");
delay_ms(2000);
}
int main(void )
{
uint16_t d;
//打开端口E的硬件时钟,等同于对端口E供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE );
//打开端口F的硬件时钟,等同于对端口F供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE );
//初始化对应端口的引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9| GPIO_Pin_10;//指定第9,10根引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT ;//配置为输出模式
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;//配置引脚的响应时间=1/100MHz .
//从高电平切换到低电平1/100MHz,速度越快,功耗会越高
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP ;//推挽的输出模式,增加输出电流和灌电流的能力
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不使能内部上下拉电阻
GPIO_Init(GPIOF ,&GPIO_InitStructure);
//初始化对应端口的引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13| GPIO_Pin_14;//指定第13,14根引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT ;//配置为输出模式
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;//配置引脚的响应时间=1/100MHz .
//从高电平切换到低电平1/100MHz,速度越快,功耗会越高
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP ;//推挽的输出模式,增加输出电流和灌电流的能力
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;//不使能内部上下拉电阻
GPIO_Init(GPIOE ,&GPIO_InitStructure);
PFout(9)=1;
PFout(10)=1;
PEout(13)=1;
PEout(14)=1;
//初始化串口1的波特率为115200bps
usart1_init(115200);
//串口3的波特率为9600bps,因为蓝牙模块默认使用该波特率
usart3_init(9600);
delay_ms(100);
printf("This is Bluetooth test\r\n");
ble_set_config();
while(1)
{
}
return 0 ;
}
void USART1_IRQHandler(void)
{
uint16_t d;
//检测是否接收到数据
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
{
d = USART_ReceiveData(USART1);
if(d == '5')PFout(9)=0;
if(d == '6')PFout(9)=1;
//清空标志位,告诉CPU当前数据接收完毕,可以接收新的数据
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
void USART3_IRQHandler(void)
{
uint16_t d;
//检测是否接收到数据
if(USART_GetITStatus(USART3,USART_IT_RXNE)==SET)
{
d = USART_ReceiveData(USART3);
USART_SendData(USART1,d);//将接收到串口3数据发送给串口1,这样就可以知道串口3接收到了什么数据了,不然不好查看
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
//手机发送的数据
if(d == '0')PFout(9)=0;
if(d == 'a')PFout(9)=1;
if(d == '1')PFout(10)=0;
if(d == 'b')PFout(10)=1;
if(d == '2')PEout(13)=0;
if(d == 'c')PEout(13)=1;
if(d == '3')PEout(14)=0;
if(d == 'e')PEout(14)=1;
//清空标志位,告诉CPU当前数据接收完毕,可以接收新的数据
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
}
}
输出结果:
总结
1.蓝牙模块使用的工作电压和波特率需要查看厂商提供的技术手册查看,不过本人的蓝牙模块写的是3.3V的工作电压,实际用5V才能工作;
2.如果设置的波特率没错但是输出的信息还是为乱码的话,需要检查晶振的设置频率是否与实际的相符;
3.每个AT指令的末尾需要加上回车换行(\r\n);
蓝牙模块资料下载
链接:https://pan.baidu.com/s/1CJwuQfLejhboEfeaXJTOzg
提取码:a2qq
复制这段内容后打开百度网盘手机App,操作更方便哦