蓝牙广播只能单向发送数据且数据可以被任何其它设备接收,无法实现设备接收数据且不安全。而我的项目中不仅需要蓝牙设备发送数据给微信小程序而且需要微信小程序给蓝牙设备发送数据,因此只能放弃使用广播方式通信。
在 GAP 中外围设备通过两种方式向外广播数据: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外围设备必需不停的向外广播,让中央设备知道它的存在。外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。大部分情况下,外围设备通过广播自己来让中心设备发现自己,并建立GATT连接,从而进行更多的数据交换。GATT 连接是独占的,也就是一个外围设备同时只能被一个中央设备连接。一旦外围设备被连接,外围设备就会马上停止广播,这样外围设备就对其它中央设备不可见了。当连接断开,外围设备又开始广播。中央设备和外围设备需要双向通信的话,唯一的方式就是建立 GATT 连接。
一个蓝牙设备可以有多个服务,一个服务有可以有多个特征值,特征值都有他的属性,例如长度(size),权限(permission),值(value),描述(descriptor)。如下图:
可以看到程序先判断当前app状态是APPM_CREATE_DB的话就调用Service_Add函数添加服务,添加完毕将app状态置为APPM_READY,所有服务添加完毕后就开始广播,
Service_Add函数中最后是通过app.h中的宏完成添加服务的:
/* List of functions used to create the database */
#define SERVICE_ADD_FUNCTION_LIST \
DEFINE_SERVICE_ADD_FUNCTION(Batt_ServiceAdd_Server), \
DEFINE_SERVICE_ADD_FUNCTION(CustomService_ServiceAdd)
//DEFINE_SERVICE_ADD_FUNCTION(CustomService_Service2Add)
若想加入自己定义的服务,只需要照葫芦画瓢实现自己的CustomService_ServiceAdd函数即可,CustomService_ServiceAdd函数在ble_custom.c中实现:
/* ----------------------------------------------------------------------------
* Function : void CustomService_ServiceAdd(void)
* ----------------------------------------------------------------------------
* Description : Send request to add custom profile into the attribute
* database.Defines the different access functions
* (setter/getter commands to access the different
* characteristic attributes).
* Inputs : None
* Outputs : None
* Assumptions : None
* ------------------------------------------------------------------------- */
void CustomService_ServiceAdd(void)
{
struct gattm_add_svc_req *req =
KE_MSG_ALLOC_DYN(GATTM_ADD_SVC_REQ,
TASK_GATTM, TASK_APP,
gattm_add_svc_req,
CS_IDX_NB * sizeof(struct gattm_att_desc));
const uint8_t svc_uuid[ATT_UUID_128_LEN] = CS_SVC_UUID;
const struct gattm_att_desc att[CS_IDX_NB] =
{
/* Attribute Index = Attribute properties: UUID,
* Permissions,
* Max size,
* Extra permissions */
/* TX Characteristic */
[CS_IDX_TX_VALUE_CHAR] = ATT_DECL_CHAR(),
[CS_IDX_TX_VALUE_VAL] = ATT_DECL_CHAR_UUID_128(CS_CHARACTERISTIC_TX_UUID,
PERM(RD, ENABLE) | PERM(NTF, ENABLE),
CS_TX_VALUE_MAX_LENGTH),
[CS_IDX_TX_VALUE_CCC] = ATT_DECL_CHAR_CCC(),
[CS_IDX_TX_VALUE_USR_DSCP] = ATT_DECL_CHAR_USER_DESC(CS_USER_DESCRIPTION_MAX_LENGTH),
/* RX Characteristic */
[CS_IDX_RX_VALUE_CHAR] = ATT_DECL_CHAR(),
[CS_IDX_RX_VALUE_VAL] = ATT_DECL_CHAR_UUID_128(CS_CHARACTERISTIC_RX_UUID,
PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE)
| PERM(WRITE_COMMAND, ENABLE),
CS_RX_VALUE_MAX_LENGTH),
[CS_IDX_RX_VALUE_CCC] = ATT_DECL_CHAR_CCC(),
[CS_IDX_RX_VALUE_USR_DSCP] = ATT_DECL_CHAR_USER_DESC(CS_USER_DESCRIPTION_MAX_LENGTH),
};
/* Fill the add custom service message */
req->svc_desc.start_hdl = 0;
req->svc_desc.task_id = TASK_APP;
req->svc_desc.perm = PERM(SVC_UUID_LEN, UUID_128);
req->svc_desc.nb_att = CS_IDX_NB;
memcpy(&req->svc_desc.uuid[0], &svc_uuid[0], ATT_UUID_128_LEN);
for (unsigned int i = 0; i < CS_IDX_NB; i++)
{
memcpy(&req->svc_desc.atts[i], &att[i],
sizeof(struct gattm_att_desc));
}
/* Send the message */
ke_msg_send(req);
}
在ble_custom.h中定义了服务和特征值的uuid(uuid遵循一定规范):
/* Custom service UUIDs */
#define CS_SVC_UUID { 0x24, 0xdc, 0x0e, 0x6e, 0x01, 0x40, \
0xca, 0x9e, 0xe5, 0xa9, 0xa3, 0x00, \
0xb5, 0xf3, 0x93, 0xe0 }
#define CS_CHARACTERISTIC_TX_UUID { 0x24, 0xdc, 0x0e, 0x6e, 0x02, 0x40, \
0xca, 0x9e, 0xe5, 0xa9, 0xa3, 0x00, \
0xb5, 0xf3, 0x93, 0xe0 }
#define CS_CHARACTERISTIC_RX_UUID { 0x24, 0xdc, 0x0e, 0x6e, 0x03, 0x40, \
0xca, 0x9e, 0xe5, 0xa9, 0xa3, 0x00, \
0xb5, 0xf3, 0x93, 0xe0 }
为了表述方便,这里我将主动请求数据的手机蓝牙调试app称为主机,被动接受的蓝牙设备RSL10板卡称为从机。若主机请求读特征值,则会调用ble_custom.c中的回调函数GATTC_ReadReqInd。
我们就可以在GATTC_ReadReqInd函数中填充需要发送主机的数据,将数据填充到valptr即可,程序中我将“eeworld”发送给主机。在实际应用中,我们在主循环或者其它函数中将数据填充到cs_env[device_indx].tx_value即可。
int GATTC_ReadReqInd(ke_msg_id_t const msg_id,
struct gattc_read_req_ind const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
uint8_t length = 0;
uint8_t status = GAP_ERR_NO_ERROR;
uint16_t attnum;
uint8_t *valptr = NULL;
PRINTF("===================read\n");
/* Retrieve the index of environment structure representing peer device */
signed int device_indx = Find_Connected_Device_Index(KE_IDX_GET(src_id));
if (device_indx == INVALID_DEV_IDX)
{
return (KE_MSG_CONSUMED);
}
struct gattc_read_cfm *cfm;
/* Set the attribute handle using the attribute index
* in the custom service */
if (param->handle > cs_env[device_indx].start_hdl)
{
attnum = (param->handle - cs_env[device_indx].start_hdl - 1);
}
else
{
status = ATT_ERR_INVALID_HANDLE;
}
PRINTF("==== attnum=0x%02x param->handle=0x%04x ====\n",attnum,param->handle);
/* If there is no error, send back the requested attribute value */
if (status == GAP_ERR_NO_ERROR)
{
switch (attnum)
{
/* RX characteristic*/
case CS_IDX_RX_VALUE_VAL:
{
length = CS_RX_VALUE_MAX_LENGTH;
valptr = (uint8_t *)&cs_env[device_indx].rx_value;
}
break;
case CS_IDX_RX_VALUE_CCC:
{
length = 2;
valptr = (uint8_t *)&cs_env[device_indx].rx_cccd_value;
}
break;
case CS_IDX_RX_VALUE_USR_DSCP:
{
length = strlen(CS_RX_CHARACTERISTIC_NAME);
valptr = (uint8_t *)CS_RX_CHARACTERISTIC_NAME;
}
break;
/* TX characteristic */
case CS_IDX_TX_VALUE_VAL:
{
length = CS_TX_VALUE_MAX_LENGTH;
valptr = (uint8_t *)&cs_env[device_indx].tx_value;
for(int n=0;n<5;n++)
{
PRINTF("====Read=== tx_value[%d]=0x%02x\n",n,cs_env[device_indx].tx_value[n]);
}
}
break;
case CS_IDX_TX_VALUE_CCC:
{
length = 2;
valptr = (uint8_t *)&cs_env[device_indx].tx_cccd_value;
PRINTF("====Read=== tx_cccd_value=0x%04x\n",cs_env[device_indx].tx_cccd_value);
}
break;
case CS_IDX_TX_VALUE_USR_DSCP:
{
length = strlen(CS_TX_CHARACTERISTIC_NAME);
valptr = (uint8_t *)CS_TX_CHARACTERISTIC_NAME;
}
break;
default:
{
status = ATT_ERR_READ_NOT_PERMITTED;
}
break;
}
}
/* Allocate and build message */
cfm = KE_MSG_ALLOC_DYN(GATTC_READ_CFM,
KE_BUILD_ID(TASK_GATTC, ble_env[device_indx].conidx),
TASK_APP,
gattc_read_cfm, length);
length=8;
memcpy(valptr, "eeworld", length);
if (valptr != NULL)
{
memcpy(cfm->value, valptr, length);
}
cfm->handle = param->handle;
cfm->length = length;
cfm->status = status;
/* Send the message */
ke_msg_send(cfm);
return (KE_MSG_CONSUMED);
}
主机开启通知或者关闭通知,发送数据给从机将调用ble_custom.c中的GATTC_WriteReqInd,先看看日志:
int GATTC_WriteReqInd(ke_msg_id_t const msg_id,
struct gattc_write_req_ind const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
/* Retrieve the index of environment structure representing peer device */
signed int device_indx = Find_Connected_Device_Index(KE_IDX_GET(src_id));
if (device_indx == INVALID_DEV_IDX)
{
return (KE_MSG_CONSUMED);
}
struct gattc_write_cfm *cfm = KE_MSG_ALLOC(GATTC_WRITE_CFM,KE_BUILD_ID(TASK_GATTC, ble_env[device_indx].conidx),TASK_APP, gattc_write_cfm);
uint8_t status = GAP_ERR_NO_ERROR;
uint16_t attnum;
uint8_t *valptr = NULL;
/* Check that offset is not zero */
if (param->offset)
{
status = ATT_ERR_INVALID_OFFSET;
}
/* Set the attribute handle using the attribute index
* in the custom service */
if (param->handle > cs_env[device_indx].start_hdl)
{
attnum = (param->handle - cs_env[device_indx].start_hdl - 1);
}
else
{
status = ATT_ERR_INVALID_HANDLE;
}
PRINTF("[%s:%d %s]==== write === attnum=0x%02x param->handle=0x%04x cs_env[device_indx].start_hdl=0x%04x\n",__FILE__,__LINE__,__FUNCTION__,attnum,param->handle,cs_env[device_indx].start_hdl);
/* If there is no error, save the requested attribute value */
if (status == GAP_ERR_NO_ERROR)
{
switch (attnum)
{
case CS_IDX_RX_VALUE_VAL:
{
valptr = (uint8_t *)&cs_env[device_indx].rx_value;
cs_env[device_indx].rx_value_changed = true;
PRINTF("[%s:%d %s]========= rx_value ==========\n",__FILE__,__LINE__,__FUNCTION__);
}
break;
case CS_IDX_RX_VALUE_CCC:
{
valptr = (uint8_t *)&cs_env[device_indx].rx_cccd_value;
PRINTF("[%s:%d %s]=================== rx_cccd_value:0x%04x\n",__FILE__,__LINE__,__FUNCTION__,cs_env[device_indx].rx_cccd_value);
}
break;
case CS_IDX_TX_VALUE_CCC:
{
valptr = (uint8_t *)&cs_env[device_indx].tx_cccd_value;
//这是原来的值
PRINTF("[%s:%d %s]=================== tx_cccd_value:0x%04x\n",__FILE__,__LINE__,__FUNCTION__,cs_env[device_indx].tx_cccd_value);
//要写入的新值
PRINTF("[%s:%d %s]=================== will write 0x",__FILE__,__LINE__,__FUNCTION__);
//大端模式传输
for(int i=param->length-1;i>=0;i--)
{
PRINTF("%02x",param->value[i]);
}
PRINTF(" to it\n");
}
break;
default:
{
status = ATT_ERR_WRITE_NOT_PERMITTED;
}
break;
}
}
if (valptr != NULL)
{
memcpy(valptr, param->value, param->length);
}
cfm->handle = param->handle;
cfm->status = status;
/* Send the message */
ke_msg_send(cfm);
return (KE_MSG_CONSUMED);
}
程序中将valptr = (uint8_t *)&cs_env[device_indx].rx_value,然后主机发来数据(数据存储在 param->value中)attnum==CS_IDX_RX_VALUE_VAL,设置接收标志位cs_env[device_indx].rx_value_changed = true;最后调用memcopy将数据拷贝到valptr指向的内存即cs_env[device_indx].rx_value。我们就可以在app.c中将数据读取到:
#include "app.h"
#include <printf.h>
#include <stdio.h>
int main(void)
{
App_Initialize();
/* Debug/trace initialization. In order to enable UART or RTT trace,
* configure the 'OUTPUT_INTERFACE' macro in printf.h */
printf_init();
PRINTF("__peripheral_server has started!\n");
/* Main application loop:
* - Run the kernel scheduler
* - Send notifications for the battery voltage and RSSI values
* - Refresh the watchdog and wait for an interrupt before continuing */
while (1)
{
Kernel_Schedule();
for (int i = 0; i < NUM_MASTERS; i++)
{
if (ble_env[i].state == APPM_CONNECTED)
{
/* Send battery level if battery service is enabled */
if (app_env.send_batt_ntf[i] && bass_support_env[i].enable)
{
PRINTF("__SEND BATTERY LEVEL %d\n",app_env.batt_lvl);
app_env.send_batt_ntf[i] = false;
Batt_LevelUpdateSend(ble_env[i].conidx,app_env.batt_lvl, 0);
}
/* Update custom service characteristics, send notifications if notification is enabled */
if (cs_env[i].tx_value_changed && cs_env[i].sent_success)
{
cs_env[i].tx_value_changed = false;
(cs_env[i].val_notif)++;
if (cs_env[i].tx_cccd_value & ATT_CCC_START_NTF)
{
PRINTF("[%s:%d %s]==== notifications is enabled ====\n",__FILE__,__LINE__,__FUNCTION__);
//memset(cs_env[i].tx_value, cs_env[i].val_notif,CS_TX_VALUE_MAX_LENGTH);
//CustomService_SendNotification(ble_env[i].conidx,CS_IDX_TX_VALUE_VAL,&cs_env[i].tx_value[0],CS_TX_VALUE_MAX_LENGTH);
memset(cs_env[i].tx_value, cs_env[i].val_notif,1);
CustomService_SendNotification(ble_env[i].conidx,CS_IDX_TX_VALUE_VAL,&cs_env[i].tx_value[0],1);
}
}
if (cs_env[i].rx_value_changed)
{
PRINTF("[%s:%d %s]==== get data from phone start_hdl:0x%04x ====\n",__FILE__,__LINE__,__FUNCTION__,cs_env[i].start_hdl);
for (int j=0;j<5;j++)
{
PRINTF("[%s:%d %s]==== get data from phone %d:0x%02x ====\n",__FILE__,__LINE__,__FUNCTION__,j,cs_env[i].rx_value[j]);
}
cs_env[i].rx_value_changed=false;
}
}
}
/* Refresh the watchdog timer */
Sys_Watchdog_Refresh();
/* Wait for an event before executing the scheduler again */
SYS_WAIT_FOR_EVENT;
}
}
具体如下,首先判断接收标志,然后读取数据:
if (cs_env[i].rx_value_changed)
{
PRINTF("[%s:%d %s]==== get data from phone start_hdl:0x%04x ====\n",__FILE__,__LINE__,__FUNCTION__,cs_env[i].start_hdl);
for (int j=0;j<5;j++)
{
PRINTF("[%s:%d %s]==== get data from phone %d:0x%02x ====\n",__FILE__,__LINE__,__FUNCTION__,j,cs_env[i].rx_value[j]);
}
cs_env[i].rx_value_changed=false;
}
例子中定义的cs_env结构体变量中没有定义一个变量指示接收数据的实际长度,实际使用中可以加上,在接收函数中GATTC_WriteReqInd拷贝的数据的同时赋值数据长度。
开启或者关闭通知的话则会attnum==CS_IDX_TX_VALUE_CCC,开启通知主机向从机发送数据0x0001,关闭通知主机向从机发送数据0x0000,并把这个数据写入cs_env[device_indx].tx_cccd_value。例子中通知数据的改变标志cs_env.tx_value_changed是在app_process.c中APP_Timer函数中定时置位。
总结:有个疑问就是,在不自定义变量的情况,如何在主循环中收发数据的时候区分uuid呢,有没有什么api接口可以根据device_indx获得uuid,因为官方sdk现在还没精力去研究仔细。例程中attnum = (param->handle - cs_env[device_indx].start_hdl - 1);而start_hdl是sdk里面动态申请的(也可以自己设置固定值)。
最后附上一个修改了的例程。
peripheral_server.rar
(619.09 KB, 下载次数: 4)