概述
藍牙是一種支持設備短距離通信(一般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,操作更方便哦