硬件环境:从机:ESP-32S蓝牙模组
主机:基于CC2541蓝牙芯片的USB蓝牙中继模块
需求:主机与从机实现双向通行。从机通过通过通知的方式不断向从机发送数据,主机收到数据是通过USB串口将数据传送到上位机,主机将USB串口的数据通过蓝牙传输给从机,实现对从机的控制。
1、从机端部分代码:
ESP服务UUID
/// Service
static const uint16_t spp_service_uuid = 0xABF0; //服务
/// Characteristic UUID
#define ESP_GATT_UUID_SPP_DATA_RECEIVE 0xABF1 //接收数据
#define ESP_GATT_UUID_SPP_DATA_NOTIFY 0xABF2 //通知
#define ESP_GATT_UUID_SPP_COMMAND_RECEIVE 0xABF3 //接收命令数据
#define ESP_GATT_UUID_SPP_COMMAND_NOTIFY 0xABF4 //通知命令数据
ESP蓝牙事件响应代码
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
esp_ble_gatts_cb_param_t *p_data = (esp_ble_gatts_cb_param_t *) param;
uint8_t res = 0xff;
uint32_t Len;
uint16_t i;
// printf("gatts_profile_event_handler event = %x\n",event);
switch (event) {
case ESP_GATTS_REG_EVT:
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
esp_ble_gap_set_device_name((const char *)g_BLEName); //蓝牙名称
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
memcpy(&spp_adv_data[9],g_BLEName,strlen((char*)g_BLEName));
spp_adv_data[7] = strlen((char*)g_BLEName)+1;
Len = strlen((char*)g_BLEName)+9;
esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data,Len);
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID);
break;
case ESP_GATTS_READ_EVT: //客户端读操作
res = find_char_and_desr_index(p_data->read.handle);
if(res == SPP_IDX_SPP_STATUS_VAL){
//TODO:client read the status characteristic
printf("client read the status characteristic\n");
}
break;
case ESP_GATTS_WRITE_EVT: { //客户端有写数据
res = find_char_and_desr_index(p_data->write.handle);
if(p_data->write.is_prep == false){
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT : handle = %d\n", res);
if(res == SPP_IDX_SPP_COMMAND_VAL){
uint8_t * spp_cmd_buff = NULL;
spp_cmd_buff = (uint8_t *)malloc((spp_mtu_size - 3) * sizeof(uint8_t));
if(spp_cmd_buff == NULL){
ESP_LOGE(GATTS_TABLE_TAG, "%s malloc failed\n", __func__);
break;
}
memset(spp_cmd_buff,0x0,(spp_mtu_size - 3));
memcpy(spp_cmd_buff,p_data->write.value,p_data->write.len);
printf("00recv Data:%s\n",p_data->write.value);
for(i=0;iwrite.len;i++)
{
ReceiveData(BLE, p_data->write.value[i]);
}
}else if(res == SPP_IDX_SPP_DATA_NTF_CFG){
printf("SPP_IDX_SPP_DATA_NTF_CFG:%s\n",p_data->write.value);
if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x01)&&(p_data->write.value[1] == 0x00)){
enable_data_ntf = true;
}else if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x00)&&(p_data->write.value[1] == 0x00)){
enable_data_ntf = false;
}
}
else if(res == SPP_IDX_SPP_DATA_RECV_VAL){
#ifdef SPP_DEBUG_MODE
esp_log_buffer_char(GATTS_TABLE_TAG,(char *)(p_data->write.value),p_data->write.len);
#else
printf("recv Data:%s\n",p_data->write.value);
for(i=0;iwrite.len;i++)
{
ReceiveData(BLE, p_data->write.value[i]);
}
#endif
}else{
//TODO:
}
}else if((p_data->write.is_prep == true)&&(res == SPP_IDX_SPP_DATA_RECV_VAL)){
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_PREP_WRITE_EVT : handle = %d\n", res);
}
break;
}
case ESP_GATTS_EXEC_WRITE_EVT:{
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT\n");
break;
}
case ESP_GATTS_MTU_EVT:
spp_mtu_size = p_data->mtu.mtu;
printf("Set spp_mtu_size:mtu_size=%d\n",spp_mtu_size);
break;
case ESP_GATTS_CONF_EVT:
break;
case ESP_GATTS_UNREG_EVT:
break;
case ESP_GATTS_DELETE_EVT:
break;
case ESP_GATTS_START_EVT:
break;
case ESP_GATTS_STOP_EVT:
break;
case ESP_GATTS_CONNECT_EVT: //客户端连接事件
spp_conn_id = p_data->connect.conn_id;
spp_gatts_if = gatts_if;
is_connected = true;
memcpy(&spp_remote_bda,&p_data->connect.remote_bda,sizeof(esp_bd_addr_t));
break;
case ESP_GATTS_DISCONNECT_EVT:
is_connected = false;
enable_data_ntf = false;
esp_ble_gap_start_advertising(&spp_adv_params);
break;
case ESP_GATTS_OPEN_EVT:
break;
case ESP_GATTS_CANCEL_OPEN_EVT:
break;
case ESP_GATTS_CLOSE_EVT:
break;
case ESP_GATTS_LISTEN_EVT:
break;
case ESP_GATTS_CONGEST_EVT:
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle);
if (param->add_attr_tab.status != ESP_GATT_OK){
ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table failed, error code=0x%x", param->add_attr_tab.status);
}
else if (param->add_attr_tab.num_handle != SPP_IDX_NB){
ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, SPP_IDX_NB);
}
else {
memcpy(spp_handle_table, param->add_attr_tab.handles, sizeof(spp_handle_table));
esp_ble_gatts_start_service(spp_handle_table[SPP_IDX_SVC]);
}
break;
}
default:
break;
}
}
2、主机端部分代码
// Simple Profile Service UUID
#define SIMPLEPROFILE_SERV_UUID 0xABF0// 0xFFF0
// Key Pressed UUID
#define SIMPLEPROFILE_CHAR1_UUID 0xABF1// 0xFFF1
#define SIMPLEPROFILE_CHAR2_UUID 0xABF2 //0xFFF2
#define SIMPLEPROFILE_CHAR3_UUID 0xABF3 //0xFFF3
#define SIMPLEPROFILE_CHAR4_UUID 0xABF4 //0xFFF4
//开始扫描可以连接的从机
static void simpleBLE_ScanStart( void)
{
simpleBLEScanning = TRUE;
simpleBLEScanRes = 0;
LCD_WRITE_STRING( "Discovering...", HAL_LCD_LINE_1 );
SerialPrintString("Discovering...\r\n");
LCD_WRITE_STRING( "", HAL_LCD_LINE_2 );
GAPCentralRole_StartDiscovery( DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST );
}
添加扫描到的设备
static void simpleBLEAddDeviceInfo( uint8 *pAddr, uint8 addrType, uint8 *Name, uint8 NameLen)
{
uint8 i;
// If result count not at max
if ( simpleBLEScanRes < DEFAULT_MAX_SCAN_RES )
{
// Check if device is already in scan results
for ( i = 0; i < simpleBLEScanRes; i++ )
{
if ( osal_memcmp( pAddr, simpleBLEDevList[i].addr , B_ADDR_LEN ) )
{
return;
}
}
// Add addr to scan result list
osal_memcpy( simpleBLEDevList[simpleBLEScanRes].addr, pAddr, B_ADDR_LEN );
osal_memcpy( simpleBLEDevList[simpleBLEScanRes].Name, Name, NameLen);
simpleBLEDevList[simpleBLEScanRes].addrType = addrType;
simpleBLEScanRes++;
Uart_Send_ScanDevInfo(pAddr, Name, NameLen); //扫描到的设备按照通信协议通过USB串口发出
}
}
连接设备
static void simpleBLE_Connect( uint8 index)
{
if ( simpleBLEState == BLE_STATE_IDLE )
{
// if there is a scan result
if ( simpleBLEScanRes > 0 )
{
uint8 addrType;
uint8 *peerAddr;
// connect to current device in scan result
peerAddr = simpleBLEDevList[index].addr;
addrType = simpleBLEDevList[index].addrType;
simpleBLEState = BLE_STATE_CONNECTING;
GAPCentralRole_EstablishLink( DEFAULT_LINK_HIGH_DUTY_CYCLE,
DEFAULT_LINK_WHITE_LIST,
addrType, peerAddr );
}
}
}
开始发现服务
static void simpleBLECentralStartDiscovery( void )
{
uint8 uuid[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_SERV_UUID), //SIMPLEPROFILE_SERV_UUID
HI_UINT16(SIMPLEPROFILE_SERV_UUID)
};
// Initialize cached handles
simpleBLESvcStartHdl = simpleBLESvcEndHdl = simpleBLECharHdl = GUA_charHdl[0]= 0;
simpleBLEDiscState = BLE_DISC_STATE_SVC;
// Discovery simple BLE service
GATT_DiscPrimaryServiceByUUID( simpleBLEConnHandle,uuid,ATT_BT_UUID_SIZE,simpleBLETaskId );
// GATT_DiscAllPrimaryServices( simpleBLEConnHandle,simpleBLETaskId );
}
发现特指值
static void simpleBLEGATTDiscoveryEvent( gattMsgEvent_t *pMsg )
{
attReadByTypeReq_t req;
char str[128];
sprintf(str, "hande =0x%x,pMsg->method = %x,numInfo = %d,pMsg->hdr.status=%x\r\n",BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0], pMsg->msg.readByTypeRsp.dataList[1] ),pMsg->method, pMsg->msg.findByTypeValueRsp.numInfo,pMsg->hdr.status);
SerialPrintString((uint8 *)str);
if ( simpleBLEDiscState == BLE_DISC_STATE_SVC ) //发现服务
{
// Service found, store handles
if ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP && pMsg->msg.findByTypeValueRsp.numInfo > 0 )
{
simpleBLESvcStartHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].handle;
simpleBLESvcEndHdl = pMsg->msg.findByTypeValueRsp.handlesInfo[0].grpEndHandle;
SerialPrintString("Service discovery\r\n");
}
// If procedure complete
if ( ( pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP &&
pMsg->hdr.status == bleProcedureComplete ) ||
( pMsg->method == ATT_ERROR_RSP ) )
{
if ( simpleBLESvcStartHdl != 0 )
{
// Discover characteristic
simpleBLEDiscState = BLE_DISC_STATE_CHAR1;
req.startHandle = simpleBLESvcStartHdl;
req.endHandle = simpleBLESvcEndHdl;
req.type.len = ATT_BT_UUID_SIZE;
req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR1_UUID);
req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR1_UUID);
// GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
GATT_DiscCharsByUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
SerialPrintString("Characteristic1 discovery...\r\n");
}
}
}
else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR1 )//发现发现特征值1
{
if ( pMsg->method == ATT_READ_BY_TYPE_RSP && pMsg->msg.readByTypeRsp.numPairs > 0 )
{
simpleBLECharHdl = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],
pMsg->msg.readByTypeRsp.dataList[1] );
sprintf(str, "simpleBLECharHdl= %x\r\n",simpleBLECharHdl);
SerialPrintString((unsigned char*)str);
simpleBLEProcedureInProgress = TRUE;//此时仍在进程中
SerialPrintString("Characteristic1 discovery\r\n");
//simpleBLEProcedureInProgress = FALSE;
// simpleBLEDiscState = BLE_DISC_STATE_IDLE;
osal_start_timerEx( simpleBLETaskId, GUA_READ_CHAR1_EVT, 1000 );//一定要延时一定时间,否则会读取特征值失败。通过这一步才会有后面的Read rsp:0
}
else
{
//注意这里一定要else,当numPairs=0时才能再读下一个,下同
simpleBLEDiscState = BLE_DISC_STATE_CHAR2;
req.startHandle = simpleBLESvcStartHdl;
req.endHandle = simpleBLESvcEndHdl;
req.type.len = ATT_BT_UUID_SIZE;
req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR2_UUID);
req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR2_UUID);
SerialPrintString("Characteristic2 discovery...\r\n");
//GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
GATT_DiscCharsByUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
}
}
else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR2 ) //发现char2
{
//读出char2的handle并保存到GUA_charHdl
if (pMsg->method == ATT_READ_BY_TYPE_RSP && pMsg->msg.readByTypeRsp.numPairs > 0 )
{
GUA_charHdl[1] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0], pMsg->msg.readByTypeRsp.dataList[1] );
simpleBLEProcedureInProgress = TRUE; //此时仍在进程中
sprintf(str, "GUA_charHdl[1]= %x\r\n",GUA_charHdl[1]);
SerialPrintString((unsigned char*)str);
SerialPrintString("Characteristic2 discovery\r\n");
}
//发送命令读取下一个特征值的句柄
else
{
simpleBLEDiscState = BLE_DISC_STATE_CHAR3;
req.startHandle = simpleBLESvcStartHdl;
req.endHandle = simpleBLESvcEndHdl;
req.type.len = ATT_BT_UUID_SIZE;
req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR3_UUID);
req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR3_UUID);
SerialPrintString("Characteristic3 discovery...\r\n");
// GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
GATT_DiscCharsByUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
}
}
else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR3 ) //发现char3
{
sprintf(str, "pMsg->method = %x,numInfo = %d\r\n",pMsg->method,pMsg->msg.readByTypeRsp.numPairs);
SerialPrintString((uint8 *)str);
//读出char3的handle并保存到GUA_charHdl
if ( pMsg->method == ATT_READ_BY_TYPE_RSP && pMsg->msg.readByTypeRsp.numPairs > 0 )
{
GUA_charHdl[2] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],pMsg->msg.readByTypeRsp.dataList[1] );
sprintf(str, "GUA_charHdl[2]= %x\r\n",GUA_charHdl[2]);
SerialPrintString((unsigned char*)str);
simpleBLEProcedureInProgress = TRUE;//此时仍在进程中
SerialPrintString("Characteristic3 discovery\r\n");
}
//发送命令读取下一个特征值的句柄
else
{
simpleBLEDiscState = BLE_DISC_STATE_CHAR4;
req.startHandle = simpleBLESvcStartHdl;
req.endHandle = simpleBLESvcEndHdl;
req.type.len = ATT_BT_UUID_SIZE;
req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR4_UUID);
req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR4_UUID);
SerialPrintString("Characteristic4 discovery...\r\n");
// GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
GATT_DiscCharsByUUID( simpleBLEConnHandle, &req, simpleBLETaskId );
}
}
else if ( simpleBLEDiscState == BLE_DISC_STATE_CHAR4 ) //发现char6
{
if ( pMsg->method == ATT_READ_BY_TYPE_RSP && pMsg->msg.readByTypeRsp.numPairs > 0 )
{
GUA_charHdl[3] = BUILD_UINT16( pMsg->msg.readByTypeRsp.dataList[0],pMsg->msg.readByTypeRsp.dataList[1] );
sprintf(str, "GUA_charHdl[3]= %x\r\n",GUA_charHdl[3]);
SerialPrintString((unsigned char*)str);
SerialPrintString("Characteristic4 discovery\r\n");
//注意最后一个特征值时需要赋值
simpleBLEProcedureInProgress = FALSE;
}
simpleBLEDiscState = BLE_DISC_STATE_IDLE;
//读完最后的char6,就可以返回闲置模式了
}
}
主机接收从机的通知数据
static void simpleBLECentral_ProcessOSALMsg( osal_event_hdr_t *pMsg )
{
switch ( pMsg->event )
{
case KEY_CHANGE:
simpleBLECentral_HandleKeys( ((keyChange_t *)pMsg)->state, ((keyChange_t *)pMsg)->keys );
break;
case GATT_MSG_EVENT:
simpleBLECentralProcessGATTMsg( (gattMsgEvent_t *) pMsg );
break;
}
}
主机向从机写数据(注:写数据当然要判断连接情况等)
uint8 BLE_SendData(uint8 *pBuff, uint8 iLen)
{
attWriteReq_t AttReq;
osal_memset((char*)&AttReq,0,sizeof(attWriteReq_t));
AttReq.handle = simpleBLECharHdl+1;
AttReq.len = iLen;
AttReq.sig = 0;
AttReq.cmd = 0;
osal_memcpy(AttReq.value, pBuff, iLen);
return GATT_WriteCharValue(0, &AttReq,simpleBLETaskId);
}
测试中遇到的问题总结:
在调试过程中,从机给主机发送的通知数据能正常的收到,但是主机无法向从机写数据,总是返回blePending或者bleTimeout错误,
经过长时间调试,发现在static void simpleBLEGATTDiscoveryEvent( gattMsgEvent_t *pMsg )函数中,按照CC2541开发例程中 使用GATT_ReadUsingCharUUID( simpleBLEConnHandle, &req, simpleBLETaskId )特征值,得到的特征值无法写入成功,但是使用 GATT_DiscCharsByUUID( simpleBLEConnHandle, &req, simpleBLETaskId )函数发现特征值,能全部发现从机预设的特征值,并能正确的读写。问题得以解决。由于协议栈底层代码并不开源,未找到两个函数之间的区别。
注:GATT_DiscCharsByUUID读到的句柄,比GATT_ReadUsingCharUUID()读到的句柄小1