零. 概述
主要介绍下蓝牙协议栈开发板跑传统蓝牙搜索AT指令以及上位机操作步骤,以及原理
一. 声明
本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:
第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。
第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,基于USB的H2等
第三篇:传统蓝牙controller介绍,主要介绍传统蓝牙芯片的介绍,包括射频层(RF),基带层(baseband),链路管理层(LMP)等
第四篇:传统蓝牙host介绍,主要介绍传统蓝牙的协议栈,比如HCI,L2CAP,SDP,RFCOMM,HFP,SPP,HID,AVDTP,AVCTP,A2DP,AVRCP,OBEX,PBAP,MAP等等一系列的协议吧。
第五篇:低功耗蓝牙controller介绍,主要介绍低功耗蓝牙芯片,包括物理层(PHY),链路层(LL)
第六篇:低功耗蓝牙host介绍,低功耗蓝牙协议栈的介绍,包括HCI,L2CAP,ATT,GATT,SM等
第七篇:蓝牙芯片介绍,主要介绍一些蓝牙芯片的初始化流程,基于HCI vendor command的扩展
第八篇:附录,主要介绍以上常用名词的介绍以及一些特殊流程的介绍等。
另外,开发板如下所示,对于想学习蓝牙协议栈的最好人手一套。以便更好的学习蓝牙协议栈,相信我,学完这一套视频你将拥有修改任何协议栈的能力(比如Linux下的bluez,Android下的bluedroid)。
-------------------------------------------------------------------------------------------------------------------------
CSDN学院链接(进入选择你想要学习的课程):https://edu.csdn.net/lecturer/5352?spm=1002.2001.3001.4144
蓝牙交流扣扣群:970324688
Github代码:https://github.com/sj15712795029/bluetooth_stack
入手开发板:https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22329603896.18.5aeb41f973iStr&id=622836061708
蓝牙学习目录:https://blog.csdn.net/XiaoXiaoPengBo/article/details/107727900
--------------------------------------------------------------------------------------------------------------------------
演示视频点击我(!!!!!!!!!!!!!!!!)
二. STM32蓝牙协议栈封装使用AT command实现搜索
使用步骤操作如下:
步骤 1)准备好代码,从github下载下来最新的代码(在上面有介绍Github连接)
步骤 2)连接好硬件(把模组插好,ST-LINK接上,TYPE-C debug先接上,按下按钮可以看到蓝色电源等亮起)
步骤 3)打开Keil工程文件夹下的project\stm32f10x_bb_csr8x11_bt\stm32f10x_bb_csr8x11.uvprojx,然后编译下载
此部分注意几点:
- 下载需要ST-LINK驱动,我已经放在下载资料中的软件工具文件夹中
- STM32 F1的pack要有,我已经放在软件工具文件夹中的MDK下,没有没安装过要安装下,名字如下:
- 下载的debug要选ST-LINK
- 下载的时候要勾选Use micro lib
步骤4)打开串口工具(我用的是XCOM),然后做初始化动作,在发送串口敲BT_START,点击发送,出来以下log就证明初始化通过了,我们就可以来进行搜索动作了,注意一点:不能勾选发送新行,否则会解析错误
步骤5)然后敲BT_INQUIRY就能搜索到设备了
三.使用我们自己写的上位机来实现搜索
步骤跟AT的1)2)3)一样,我们从第四步开始讲解
打开我们工程源码1-BLUETOOTH\mcu_bt_tool\mcu_bt_tool\mcu_bt_tool\bin\Debug中的mcu_bt_tool.exe,当然你也可以直接用VS2010打开工程
步骤 1)打开串口
步骤 2)点击蓝牙开启按钮(此步骤跟AT 命令BT_START一样的效果,就是实现蓝牙初始化)
步骤 3) 等待初始化完成点击搜索按钮,你就发现可以搜索到蓝牙了
另外:使用上位机的时候注意几点:
① mcu_bt_tool.exe你如果想把可执行文件拿到别的路径单独执行,那么必须要把Newtonsoft.Json.dll跟exe放在同一个路径下,因为上位机是跟STM32用json沟通的
② 因为目前搜索是开启的EIR,带RSSI的,所以他会重复性上来同一个设备,我没做根据同一个蓝牙地址做显示过滤,如果有兴趣的人可以加上这一块
四. 串口工具AT command以及上位机实现搜索的原理
步骤 1)Type C uart debug口的tx,rx初始化
-
/
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
**
-
* func name : hw_uart_debug_init
-
* para : baud
_rate(IN) --> Baud rate of uart1
-
* return : hw_uart
_debug_init result
-
* description : Initialization of USART1.PA9->TX PA10->RX
-
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
**/
-
uint8_t hw_uart_debug_init(uint32_t baud_rate)
-
{
-
GPIO_InitTypeDef GPIO_InitStructure;
-
USART_InitTypeDef USART_InitStructure;
-
NVIC_InitTypeDef NVIC_InitStructure;
-
DMA_InitTypeDef DMA_InitStructure;
-
-
/* Enable RCC clock for USART1,GPIOA,DMA1 */
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
-
RCC_AHBPeriphClockCmd(RCC
_AHBPeriph_DMA1, ENABLE);
-
-
/* Initialization GPIOA9 GPIOA10 */
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
-
GPIO_Init(GPIOA, &GPIO
_InitStructure);
-
-
GPIO_InitStructure.GPIO
_Pin = GPIO_Pin
_10;
-
GPIO_InitStructure.GPIO
_Mode = GPIO_Mode
_IN_FLOATING;
-
GPIO_Init(GPIOA, &GPIO_InitStructure);
-
-
/* Data format :1:8:1, no parity check, no hardware flow control */
-
USART_InitStructure.USART_BaudRate = baud_rate;
-
USART_InitStructure.USART
_WordLength = USART_WordLength
_8b;
-
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
_Tx | USART_Mode
_Rx;
-
-
/* Enable USART interrupts, mainly for idle interrupts */
-
NVIC_InitStructure.NVIC
_IRQChannel = USART1_IRQn;
-
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=DEBUG_PREE_PRIO;
-
NVIC_InitStructure.NVIC_IRQChannelSubPriority = DEBUG_SUB_PRIO;
-
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
-
NVIC_Init(&NVIC_InitStructure);
-
-
/* Initializes USART1 to enable USART, USART idle interrupts and USART RX DMA */
-
USART_Init(USART1, &USART_InitStructure);
-
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
-
USART_DMACmd(USART1,USART
_DMAReq_Rx,ENABLE);
-
USART_Cmd(USART1, ENABLE);
-
-
/* Initializes DMA and enables it */
-
DMA_DeInit(DMA1
_Channel5);
-
DMA_InitStructure.DMA
_PeripheralBaseAddr = (uint32_t)&USART1->DR;
-
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart1_rev_buffer;
-
DMA_InitStructure.DMA
_DIR = DMA_DIR
_PeripheralSRC;
-
DMA_InitStructure.DMA
_BufferSize = UART1_MAX
_REV;
-
DMA_InitStructure.DMA
_PeripheralInc = DMA_PeripheralInc
_Disable;
-
DMA_InitStructure.DMA
_MemoryInc = DMA_MemoryInc
_Enable;
-
DMA_InitStructure.DMA
_PeripheralDataSize = DMA_PeripheralDataSize
_Byte;
-
DMA_InitStructure.DMA
_MemoryDataSize = DMA_MemoryDataSize
_Byte;
-
DMA_InitStructure.DMA
_Mode = DMA_Mode
_Normal;
-
DMA_InitStructure.DMA
_Priority = DMA_Priority
_Medium;
-
DMA_InitStructure.DMA
_M2M = DMA_M2M
_Disable;
-
DMA_Init(DMA1
_Channel5, &DMA_InitStructure);
-
-
DMA_Cmd(DMA1_Channel5, ENABLE);
-
-
return HW_ERR_OK;
-
-
}
-
可以看到TX我们就是普通的实现发送,RX我们用串口空闲中断+DMA的方式来实现接受串口工具以及上位机的发送指令,然后串口中断的实现原理是这样:
-
/
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
**
-
* func name : USART1_IRQHandler
-
* para : NULL
-
* return : NULL
-
* description : Interrupt handler for usart1
-
**
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****
****/
-
void USART1
_IRQHandler(void)
-
{
-
-
if(USART_GetITStatus(USART1, USART
_IT_IDLE) != RESET)
-
{
-
/* Without this, the interrupt cannot be cleared and continues into the interrupt */
-
USART_ReceiveData(USART1);
-
uart1_rev_len =UART1_MAX_REV-DMA_GetCurrDataCounter(DMA1_Channel5);
-
-
if(uart1_rev_len != 0)
-
{
-
/* Call the parse function */
-
shell_parse(uart1_rev_buffer);
-
hw_memset(uart1_rev_buffer,0,sizeof(uart1_rev_buffer));
-
}
-
/* Clear the interrupt and reset DMA */
-
USART_ClearITPendingBit(USART1,USART_IT_IDLE);
-
uart1_dma_enable(DMA1_Channel5);
-
}
-
}
-
步骤2)收到串口工具或者上位机的解析函数如下:
-
uint8_t shell_parse(uint8_t *shell_string)
-
{
-
uint8_t result = HW_ERR_OK;
-
-
-
cJSON* parse_json = cJSON_Parse((const char *)shell_string);
-
uint8_t* func_value =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"FUNC"))->valuestring;
-
uint8_t* operate_value =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"OPERATE"))->valuestring;
-
uint8_t* para1 =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM1"))->valuestring;
-
uint8_t* para2 =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM2"))->valuestring;
-
uint8_t* para3 =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM3"))->valuestring;
-
uint8_t* para4 =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM4"))->valuestring;
-
uint8_t* para5 =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM5"))->valuestring;
-
uint8_t* para6 =
(uint8_t*)((cJSON *)cJSON_GetObjectItem(parse_json,"PARAM6"))->valuestring;
-
-
if(hw_strcmp((const char *)func_value,
"BT") ==
0)
-
{
-
result = shell_json_parse(operate_value,para1,para2,para3,para4,para5,para6);
-
}
-
else
-
{
-
result = shell_at_cmd_parse(shell_string);
-
}
-
-
cJSON_Delete(parse_json);
-
return result;
-
-
}
在这里我们分两种方式来解析:①AT command(串口工具采用这种方式) ②Json(上位机采用这种方式),如果解析不是我们定义的json格式,那么就自动转变为AT指令的解析
步骤3)AT command解析执行BT_START,以及BT_INQUIRY
-
uint8_t shell_at_cmd_parse(uint8_t *shell_string)
-
{
-
-
if(hw_strcmp(
"BT_START",(
const
char*)shell_string) ==
0)
-
{
-
HW_DEBUG(
"SHELL:operate bt start\n");
-
bt_start(&bt_app_cb);
-
return HW_ERR_OK;
-
}
-
-
........
-
-
-
if(hw_strcmp(
"BT_INQUIRY",(
const
char*)shell_string) ==
0)
-
{
-
HW_DEBUG(
"SHELL:operate bt inquiry\n");
-
bt_start_inquiry(
0x30,HCI_INQUIRY_MAX_DEV);
-
return HW_ERR_OK;
-
}
-
}
步骤4)接受上位机json指令解析
-
uint8_t shell_json_parse(uint8_t *operate_value,
-
uint8_t *para1,
uint8_t *para2,
uint8_t *para3,
-
uint8_t *para4,
uint8_t *para5,
uint8_t *para6)
-
{
-
if(hw_strcmp((
const
char *)operate_value,
"BT_START") ==
0)
-
{
-
HW_DEBUG(
"UART PARSE DEBUG:operate BT_START\n");
-
bt_start(&bt_app_cb);
-
operate_stauts_oled_show(
"BT",operate_value,
"SUCCESS",
0,
0,
0,
0,
0,
0);
-
return HW_ERR_OK;
-
}
-
-
.....
-
-
-
if(hw_strcmp((
const
char *)operate_value,
"BT_START_INQUIRY") ==
0)
-
{
-
HW_DEBUG(
"UART PARSE DEBUG:operate BT_INQUIRY\n");
-
bt_start_inquiry(
0x30,HCI_INQUIRY_MAX_DEV);
-
return HW_ERR_OK;
-
}
-
}
以上步骤3)4)其中bt_start以及bt_start_inquiry就是协议栈函数了,这样就实现了AT command或者json上位机跟蓝牙协议栈的对接。
转载:https://blog.csdn.net/XiaoXiaoPengBo/article/details/108414679