1740 views|0 replies

1236

Posts

66

Resources
The OP
 

RSL1 Bluetooth characteristic value reading and writing [Copy link]

 This post was last edited by dql2016 on 2021-6-28 20:07

Bluetooth broadcast can only send data in one direction and the data can be received by any other device. It is not possible to realize the device receiving data and it is not safe. In my project, not only the Bluetooth device needs to send data to the WeChat applet, but also the WeChat applet needs to send data to the Bluetooth device, so I have to give up using broadcast communication.

By consulting the information, I learned that to achieve two-way data transmission and reception between Bluetooth devices and WeChat applet, it is necessary to use a custom service and then read and write characteristic values. First, learn the concepts that are often used in the following programs.

  • Each Bluetooth device can play one or more GAP roles at the same time. There are two types of roles: central device and peripheral device . The GAP role is related to establishing a connection. A peripheral device can send a broadcast to let other devices know of its existence, but only a central device can send a connection request to establish a connection.
  • A Bluetooth device can play the role of a central device and a peripheral device at the same time . For example, a smartphone plays the role of a central device when it is connected to a sports bracelet, but at the same time it also plays the role of a peripheral device connected to a computer. ON Semiconductor also provides examples of multiple roles and mixed roles:

In GAP, peripheral devices broadcast data in two ways: Advertising Data Payload and Scan Response Data Payload. Each data can contain up to 31 bytes. Advertising data is necessary because the peripheral device must broadcast continuously to let the central device know its existence. The peripheral device will set a broadcast interval, and in each broadcast interval, it will resend its own advertising data. In most cases, the peripheral device broadcasts itself to let the central device find itself and establish a GATT connection to exchange more data. GATT connection is exclusive, that is, a peripheral device can only be connected to one central device at a time . Once the peripheral device is connected, it will stop broadcasting immediately, so that the peripheral device is invisible to other central devices . When the connection is disconnected, the peripheral device starts broadcasting again. If the central device and the peripheral device need two-way communication, the only way is to establish a GATT connection.

The two parties in GATT communication are in a client/server relationship. The peripheral device acts as a GATT server , which maintains the ATT lookup table and the definition of service and characteristic. The central device is a GATT client , which initiates requests to the server. It should be noted that all communication events are initiated by the client (also called the master) and receive responses from the server (also called the slave).

A Bluetooth device can have multiple services, and a service can have multiple characteristic values. Each characteristic value has its own attributes, such as size, permission, value, and descriptor. See the figure below:

The following is excerpted from some authoritative materials, and the translation is not easy to understand.

Connections “Connections” provides more information about connections at the lower level, and “Roles” discusses the corresponding GAP roles.download attach  save to album

2021-6-28 15:48 上传

Since the SDK uses a lot of callback mechanisms, it is not easy to understand the program flow directly from the initialization code, so start with the log:

You can see that the first log is __GAPM_PROFILE_ADDED_IND, which is found in the ble_std.c file

It can be seen that the program first determines that the current app status is APPM_CREATE_DB, then calls the Service_Add function to add the service. After adding, the app status is set to APPM_READY. After all services are added, it starts broadcasting.

The final step in the Service_Add function is to add the service through the macro in 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)

If you want to add your own defined service, you only need to implement your own CustomService_ServiceAdd function. The CustomService_ServiceAdd function is implemented in 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);
}

The uuids of services and characteristic values are defined in ble_custom.h (uuids follow certain specifications):

/* 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 }

For the sake of convenience, here I call the mobile Bluetooth debugging app that actively requests data the host, and the passive Bluetooth device RSL10 board the slave. If the host requests to read the characteristic value, the callback function GATTC_ReadReqInd in ble_custom.c will be called.

We can fill the data that needs to be sent to the host in the GATTC_ReadReqInd function and fill the data into valptr. In the program, I send "eeworld" to the host. In actual applications, we can fill the data into cs_env[device_indx].tx_value in the main loop or other functions.

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);
}

The host turns on or off notifications and sends data to the slave, which will call GATTC_WriteReqInd in ble_custom.c. Let's take a look at the log first:

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);
}

In the program, valptr = (uint8_t *)&cs_env[device_indx].rx_value, then the host sends data (data is stored in param->value) attnum==CS_IDX_RX_VALUE_VAL, sets the receive flag cs_env[device_indx].rx_value_changed = true; finally, memcopy is called to copy the data to the memory pointed to by valptr, i.e. cs_env[device_indx].rx_value. We can read the data in 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;
    }
}

The details are as follows: first determine the receiving flag, and then read the data:

               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;
                }

The cs_env structure variable defined in the example does not define a variable to indicate the actual length of the received data. In actual use, you can add it and assign the data length when GATTC_WriteReqInd copies the data in the receiving function.

If the notification is turned on or off, attnum==CS_IDX_TX_VALUE_CCC. If the notification is turned on, the host sends data 0x0001 to the slave, and if the notification is turned off, the host sends data 0x0000 to the slave, and writes this data into cs_env[device_indx].tx_cccd_value. In the example, the notification data change flag cs_env.tx_value_changed is set regularly in the APP_Timer function in app_process.c.

Summary: I have a question, that is, without custom variables, how to distinguish UUID when sending and receiving data in the main loop? Is there any API interface to obtain UUID according to device_indx? Because the official SDK has not yet had the energy to study it carefully. In the routine, attnum = (param->handle - cs_env[device_indx].start_hdl - 1); and start_hdl is dynamically applied in the SDK (you can also set a fixed value yourself).

Finally, a modified routine is attached.

peripheral_server.rar (619.09 KB, downloads: 4)

 
 

Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews

Room 1530, Zhongguancun MOOC Times Building, Block B, 18 Zhongguancun Street, Haidian District, Beijing 100190, China Tel:(010)82350740 Postcode:100190

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list