上一期我们详细的介绍了BLE蓝牙中的广播数据和服务特性,BLE蓝牙正是通过广播让其他设备发现,通过服务特性来传输数据。
本期我们来介绍STM32WBA中使用CubeMX来实现服务特性通知上传数据。
1、CubeMX配置
首先我们在STM32CubeMX中配置STM32WBA的WPAN选项,BLE选项配置客户端和GATT,系统会创建模板,不用我们来搭建骨架。
广播设置中,添加设备名称:STM32还有包含服务信息,服务信息的UUID选择位18 09,具体可以根据后面的选项更改。
在BLE Application and Services选项卡中,将服务的数量修改,这里我们添加一个服务。
在服务1选项卡中,设置我们的UUID类型为16位UUID,类型选择SIG蓝牙联盟提供的标准UUID,根据应用场景选择具体的UUID并设置服务的名称。
UUID allocation Type提供了多种预设的可识别UUID,我们可以根据我们的需要设置。
添加特性数量,这里我们启用一个特性来传输数据。
接下来要对特性选项卡进行具体的配置操作,首先我们需要设置特性的UUID和名称。
设置特性名称和数据段配置,这里需要注意的是,Value Length代表着一次上传的长度,例如设置为2 就代表着数据上传为两个字节。Lenght characteristic可以设置为固定和可变,设置为固定则代表着每次数据上传必须为Value Length的长度。可变则代表着数据上传的长度可以小于等于Value Length。
举个例子,例如我们上传时间数据,需要用到两个字节。Lenght characteristic设置为可变的话,正常情况下可以上传两个字节的时间数据。但是也可以选择上传一个字节的状态数据(例如读取失败)。
接下来是服务特性的核心配置选项:CHAR_PROP_BROADCAST= No,是否允许特性值通过广播发送。
CHAR_PROP_READ= Yes,是否允许客户端读取特性的当前值。
CHAR_PROP_WRITE_WITHOUT_RESP= No,是否允许客户端写入值,且不需要服务器回复确认。
CHAR_PROP_WRITE= No,是否允许客户端写入值,并且需要服务器回复确认。
CHAR_PROP_NOTIFY= Yes,是否允许服务器在特性值改变时,主动通知客户端,而无需客户端不停地询问。
CHAR_PROP_INDICATE,与NOTIFY类似,也是服务器主动发送。但不同之处在于,INDICATE需要客户端回复确认,因此更可靠,但速度稍慢,功耗更高。
Update char value offset= 0,当更新特性值时,可以从值的哪个字节偏移量开始写入。设为0表示总是从第一个字节开始覆盖。
ATTR_PERMISSION_AUTHEN_READ/WRITE: 访问是否需要身份认证(如配对时输入密码)。
ATTR_PERMISSION_AUTHOR_READ/WRITE: 访问是否需要授权(更高级别的许可)。
ATTR_PERMISSION_ENCRY_READ/WRITE: 通信是否需要加密。
GATT_NOTIFY_ATTRIBUTE_WRITE= Yes,当客户端写入与该特性相关的描述符时,通知应用层。
GATT_NOTIFY_READ_REQ_AND_WAIT_FOR_APPL_RESP= Yes,当客户端发送读请求时,先暂停BLE协议栈的响应并通知应用层。
GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP= Yes,与上一条类似,但对于写请求。当客户端写入特性值时,先通知应用程序处理。
根据我们的需要配置完成CubeMX中BLE的初始化工作。
2、代码使用
在main.c中,程序通过调用MX_APPE_Init(NULL)进行BLE部分的初始化工作。
程序进入MX_APPE_Init(NULL)后调用APP_BLE_Init()进行蓝牙的初始化。
/* Initialize Services and Characteristics. */LOG_INFO_APP("n");LOG_INFO_APP("Services and Characteristics creationn");TEST_APP_Init();LOG_INFO_APP("End of Services and Characteristics creationn");LOG_INFO_APP("n");/* USER CODE BEGIN APP_BLE_Init_3 *///开启广播APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);/* USER CODE END APP_BLE_Init_3 */
在该函数中,我们可以在BLE完成初始化后,让BLE蓝牙开启广播,其他设备可以发现此设备。
随后系统进行GATT_CLIENT的初始化。
首先我们找到App应用层的文件,找到我们的服务对应程序所在。
#include"stm32_timer.h"
添加STM32使用的RTOS头文件,我们需要使用软件定时器(硬件定时器的中断回调好像和他有冲突)
typedefstruct{TEST_APP_SendInformation_t Snac_Notification_Status;/* USER CODE BEGIN Service1_APP_Context_t */UTIL_TIMER_Object_t UpDate_Id;/* USER CODE END Service1_APP_Context_t */uint16_t ConnectionHandle;} TEST_APP_Context_t;
为服务的结构体添加一个定时器句柄。
voidTEST_APP_Init(void){UNUSED(TEST_APP_Context);TEST_Init();/* USER CODE BEGIN Service1_APP_Init */UTIL_TIMER_Create(&(TEST_APP_Context.UpDate_Id), 250, UTIL_TIMER_PERIODIC, Update_Timer_Callback, 0);/* USER CODE END Service1_APP_Init */return;}
服务的初始化函数中创建一个软件定时器,定时周期为250ms,定时器的回调函数为Update_Timer_Callback(自己定义);
voidUpdate_Timer_Callback(void * arg){}
定义定时器回调函数,内容我们稍后再补充
voidTEST_Notification(TEST_NotificationEvt_t *p_Notification)
特性的回调函数中,我们添加开启停止定时器逻辑:
case TEST_SNAC_NOTIFY_ENABLED_EVT:/* USER CODE BEGIN Service1Char1_NOTIFY_ENABLED_EVT */TEST_APP_Context.Snac_Notification_Status = Snac_NOTIFICATION_ON;UTIL_TIMER_Stop(&TEST_APP_Context.UpDate_Id);UTIL_TIMER_StartWithPeriod(&TEST_APP_Context.UpDate_Id,250);/* USER CODE END Service1Char1_NOTIFY_ENABLED_EVT */break;case TEST_SNAC_NOTIFY_DISABLED_EVT:/* USER CODE BEGIN Service1Char1_NOTIFY_DISABLED_EVT */TEST_APP_Context.Snac_Notification_Status = Snac_NOTIFICATION_OFF;UTIL_TIMER_Stop(&TEST_APP_Context.UpDate_Id);/* USER CODE END Service1Char1_NOTIFY_DISABLED_EVT */break;
分别在特性的通知被订阅的时候开启定时器更新内容,特性通知被取消订阅的时候关闭定时器,同时设置标志位状态。
需要注意的是,这里创建的定时器中不能直接进行特征值更新(应该是硬件中断不允许更新),官方推荐我们通过创建任务分配任务的方式来更新特征值,我们找到app_conf.h文件,添加一个新的任务:
typedefenum{CFG_TASK_HW_RNG,CFG_TASK_LINK_LAYER,CFG_TASK_DISCOVER_SERVICES_ID,CFG_TASK_HCI_ASYNCH_EVT_ID,CFG_TASK_TEMP_MEAS,CFG_TASK_BLE_HOST,CFG_TASK_AMM,CFG_TASK_BPKA,CFG_TASK_BLE_TIMER_BCKGND,/* USER CODE BEGIN CFG_Task_Id_t */FG_TASK_UPDATE_MEAS_REQ_ID,//添加任务ID/* USER CODE END CFG_Task_Id_t */CFG_TASK_NBR /* Shall be LAST in the list */} CFG_Task_Id_t;
添加一个CFG_TASK_UPDATE_MEAS_REQ_ID,取名任意,作为我们的任务ID句柄。
voidTEST_APP_Init(void){UNUSED(TEST_APP_Context);TEST_Init();/* USER CODE BEGIN Service1_APP_Init */UTIL_SEQ_RegTask( 1<< CFG_TASK_UPDATE_MEAS_REQ_ID, UTIL_SEQ_RFU, UPDATE_REQ);UTIL_TIMER_Create(&(TEST_APP_Context.UpDate_Id), 250, UTIL_TIMER_PERIODIC, Update_Timer_Callback, 0);/* USER CODE END Service1_APP_Init */return;}
回到初始化函数中,我们注册一个任务和回调函数:UPDATE_REQ函数:
voidUPDATE_REQ(void){local_data[0] = num++;msg_conf.p_Payload = local_data;msg_conf.Length = 1;TEST_UpdateValue(TEST_SNAC, &msg_conf);HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);}
定义任务回调函数,之后我们返回定时器中断函数,定时器触发中断时候去调用这个任务来实现特性值更新:
voidUpdate_Timer_Callback(void * arg){UTIL_SEQ_SetTask(1 << CFG_TASK_UPDATE_MEAS_REQ_ID, CFG_SEQ_PRIO_1);}
最后代码编译烧录,打开BLE蓝牙助手,查看特性值是否正常工作:
可以看到在BLE蓝牙助手中,该服务特性正常的接收到了递增的特性值,并且速度是4B/s和定时器触发时间(250ms)对应。
767