DA14531mod central request data

I currently have one DA14531mod running the peripheral example. Using the LightBlue app I can read different values from the module.

How can I instead of using a phone use another module to read data from the peripheral? I have another DA14531mod running the central example but I don't know how to use that to read data from the peripheral.

I have read this document: http://lpccs-docs.renesas.com/tutorial-custom-profile-DA145xx/create_custom_profile_characteristic.html but I don't get how I should read a peripherals characteristic with the central.

Parents
  • Hi Markus,

    Thank you for posting your question online and for your interest in our BLE products.
    Please check our Central example on our GitRepo:BLE_SDK6_examples/connectivity/central at main · dialog-semiconductor/BLE_SDK6_examples · GitHub
    In this example:

    Inside the user_ble_gatt.c file you can find the functions we are using for GATT Reads and Writes.

    Kind Regards,
    OV_Renesas

  • Ok so let us say I run the "ble_app_peripheral" example on one module and the central example on another. If I for example want to toggle the LED in the peripheral I need to write to the characteristic "SVC1_IDX_LED_STATE_VAL" right?

    If that is the case, can I use the "user_ble_gatt_write" function, and what should the parameters be?

    I'm just trying to understand how I actually should use these functions to write to and read from characteristics.

  • Hi Markus,

    Let me try to explain based on the central example and the prox_reporter example which are both being used:
    As you can see on user_ble_gatt.c file we use the following function for GATT Writes:

    /**
     ****************************************************************************************
     * @brief Perform a gatt write
     * @param[in] con_idx - connection identifier
     * @param[in] handle - attribute handle to write 
     * @param[in] data - data to write
     * @param[in] data_len - data len
     * @return void
     ****************************************************************************************
     */
    void user_ble_gatt_write(uint8_t op, uint8_t con_idx, uint16_t handle, uint8_t *data, uint16_t data_len)
    {
    	struct gattc_write_cmd *cmd  = KE_MSG_ALLOC_DYN(GATTC_WRITE_CMD,
    																									 KE_BUILD_ID(TASK_GATTC, con_idx),
    																									 TASK_APP,
    																									 gattc_write_cmd,
    																									 sizeof(struct gattc_write_cmd) + data_len);
    
    	cmd->operation = op;
    	cmd->auto_execute = 1;
    	cmd->seq_num = 0;
    	cmd->offset = 0;
    	cmd->length = data_len;
    	cmd->cursor = 0;
    	cmd->handle = handle;
    	memcpy(&cmd->value[0], data, data_len);
    
    	ke_msg_send(cmd);
    	
    }

    Inside the user_central_app.c file when we press a button the app_button_press_cb is triggered which then will call the GATT write as follows:
    	user_ble_gatt_write(central_app_env.periph_devices[i].serv_disc.ias_write_op,
    														central_app_env.periph_devices[i].con_idx, 
    														central_app_env.periph_devices[i].serv_disc.ias_char.c.value_handle, 
    														&central_app_env.periph_devices[i].serv_disc.ias_alert_counter, 
    														sizeof(uint8_t));

    As you can see for the user_ble_gatt_write function at first we will have to give:
    uint8_t op

    which the value is being initialized in the same file by handle_svc_ind function:
    .
    .
    .
                    if(gatt_char->c.properties & GATT_PROP_WRITE)
    				{
    					central_app_env.periph_devices[con_idx].serv_disc.ias_write_op = GATTC_WRITE;
    				}
    				else if(gatt_char->c.properties & GATT_PROP_WRITE_NO_RESP)
    				{
    					central_app_env.periph_devices[con_idx].serv_disc.ias_write_op = GATTC_WRITE_NO_RESPONSE;	
    				}
    				else if(gatt_char->c.properties & GATT_PROP_WRITE_SIGNED)
    				{
    					central_app_env.periph_devices[con_idx].serv_disc.ias_write_op = GATTC_WRITE_SIGNED;	
    				}else
    				{		
    					central_app_env.periph_devices[con_idx].serv_disc.ias_handle_valid = false;
    				}
    .
    .
    .

    After that we will need to give the con_idx which is the connection identifier. The connection identifier is being saved on the user_ble_common.h file:
    typedef struct{
    	 
    	uint8_t 					con_idx;
    	bool 	 						con_valid;
    	struct 						bd_addr addr;
    	service_disc_t		serv_disc;
    	 
    }periph_device_t;

    You can get the con_idx on the user_on_connection function inside the user_central_app.c file:
    /**
     ****************************************************************************************
     * @brief Callback from SDK when  connection occurs.
     * @param[in] connection_idx  connection identifier
     * @param[in] param  peer connection info
     * @return void
     ****************************************************************************************
     */
    void user_on_connection(uint8_t connection_idx, struct gapc_connection_req_ind const *param)
    {
    	
    		if(central_app_env.connection_timer != EASY_TIMER_INVALID_TIMER){
    			app_easy_timer_cancel(central_app_env.connection_timer);
    		}
    	
        default_app_on_connection(connection_idx, param);
    		dbg_printf("%s\r\n", __func__);
    		
    		central_app_env.periph_devices[connection_idx].addr = param->peer_addr;
    		central_app_env.periph_devices[connection_idx].con_idx = connection_idx;
    		central_app_env.periph_devices[connection_idx].con_valid = true;
    		central_app_env.num_connections++;
    	
    		if(central_app_env.num_connections < CFG_MAX_CONNECTIONS)
    		{
    			ble_scan_for_devices();
    		}
    
    #ifdef ENABLE_IAS		
    		central_app_env.periph_devices[connection_idx].serv_disc.ias_handle_valid = false;
    #endif
    #ifdef ENABLE_BAS
    		central_app_env.periph_devices[connection_idx].serv_disc.bas_handle_valid = false;
    #endif
    		
    		user_gatt_discover_all_services(connection_idx, 1);
    }

    For the 3rd parameter of user_ble_gatt.c you will to give the handler of the characteristic:
    central_app_env.periph_devices[i].serv_disc.ias_char.c.value_handle, 

    As you can see serv_disc is saved on the periph_device_t struct which con_idx was as well. In the same user_ble_common.h you can find:
    typedef struct{
    	uint16_t 			last_handle;
    #ifdef ENABLE_BAS
    	gattc_chars_t	bas_char;
    	bool 		 			bas_handle_valid;
    #endif
    #ifdef ENABLE_IAS
    	gattc_chars_t ias_char;
    	bool 		 			ias_handle_valid;
    	uint8_t 			ias_write_op;
    	uint8_t	 			ias_alert_counter;
    #endif
    	 
    }service_disc_t;

    Inside the serv_disc_t struct there is the gatt_chars_t which corresponds to ias_char:
    typedef struct {
            att_uuid_t 	uuid;                ///< item UUID
            uint16_t 		handle;                ///< item handle 	
    				/** characteristic data (if type == ::GATTC_ITEM_TYPE_CHARACTERISTIC) */
    				struct {
    								uint16_t value_handle;  ///< characteristic value handle
    								uint8_t properties;     ///< characteristic properties
    				} c;
    
                    
    } gattc_chars_t;

    Inside the gatt_chars_t struct you can find the handle we are looking for.
    For the last 2 parameters for the user_ble_gatt_write function you will have to give the value you want to write and the size of that value.
    In the central example the value we are writing changes depending on how many peripherals are connected on our central and the ias_alert_counter value is saved on the serv_disc_t  struct as well.
    If you open the prox_reporter example you will see that we have the Immediate Alert Service which only contains the Alert Level characteristic, and each time we have another device connected to our central and the button is pressed, the Alert Level characteristic can change from No Alert to Mild Alert or High Alert.
    On the prox_reporter example, inside the proxr.c file you can find the proxr_init function which we use to create the database with the Services and Characteristics we want for this specific example.
    I would suggest running the central example on one DA14531 and on another one run the prox_reporter example and take it step by step inside your code with different prints or breakpoints in order to understand how this work.
    You will have to connect, discover the services, discover the characteristics and save all this information on correct structs in order to use the GATT Read and Write for your application.
    I hope this will be helpful.

    Kind Regards,
    OV_Renesas

  • Hi Markus,

    In order to try to explain your question as well:
    If you want to toggle the LED in the peripheral you will need to write on Service 1, on characteristic:

      // LED State Characteristic Declaration
        [SVC1_IDX_LED_STATE_CHAR]          = {(uint8_t*)&att_decl_char, ATT_UUID_16_LEN, PERM(RD, ENABLE),
                                                0, 0, NULL},
    
        // LED State Characteristic Value
        [SVC1_IDX_LED_STATE_VAL]           = {SVC1_LED_STATE_UUID_128, ATT_UUID_128_LEN, PERM(WR, ENABLE) | PERM(WRITE_COMMAND, ENABLE),
                                                PERM(RI, ENABLE) | DEF_SVC1_LED_STATE_CHAR_LEN, 0, NULL},
    
        // LED State Characteristic User Description
        [SVC1_IDX_LED_STATE_USER_DESC]     = {(uint8_t*)&att_desc_user_desc, ATT_UUID_16_LEN, PERM(RD, ENABLE),
                                                sizeof(DEF_SVC1_LED_STATE_USER_DESC) - 1, sizeof(DEF_SVC1_LED_STATE_USER_DESC) - 1,
                                                (uint8_t *) DEF_SVC1_LED_STATE_USER_DESC},

    This Characteristic has a property of: Write No Response. So for the first parameter of user_ble_gatt_write function you will need to use:
    GATTC_WRITE_NO_RESPONSE

    Note: If you discover the Services and each characteristic it will be selected automatically and you do not have to insert it manually yourself.
    For the 2nd parameter you will have to give the connection identifier which can be found on user_on_connection which is on user_central_app.c file. Please check the answer above for more information.
    For the 3rd parameter you will have to give the handler which is saved on gattc_chars_t struct. In order for handler to be saved there you will have to execute the  user_gatt_parse_service function which is based on user_ble_gatt.c file. 
    ...
    for(i = 0; i < svc->end_hdl - svc->start_hdl; i++)
    	{
    
    		const union gattc_sdp_att_info *info = &svc->info[i];
    		
    		if (info->att_type == GATTC_SDP_NONE) {
    					continue;
    		}
    		
    		current_handle = svc->start_hdl + i + 1;
    		switch(info->att_type)
    		{
    			case GATTC_SDP_ATT_VAL:
    			{
    				
    				//struct gattc_sdp_att att;
    				memcpy(&svc_ret->items[char_index].uuid.uuid, info->att.uuid, info->att.uuid_len);
    				svc_ret->items[char_index].c.value_handle = current_handle;
    				svc_ret->items[char_index].uuid.type = info->att.uuid_len == sizeof(uint16_t) ? ATT_UUID_16 : ATT_UUID_128;
    				
    				
    			}break;
    			.
    			.
    			.

    For the 4th parameter you can create your own variable:
    uint8_t val = 01;

    For the 5th parameter you must give the size of your variable:
    sizeof(uint8_t) or sizeof(val)


    This way you will be able to toggle the LED on the peripheral example.
    Keep in mind this thread as well: How to connect two DA14531mod - Bluetooth Low Energy - Wireless Connectivity - Renesas Community
    You will have to configure the central in order to detect and connect to your Peripheral example.

    Kind Regards,
    OV_Renesas

     

  • Thanks a lot for this! It made things a lot clearer.

    I now have the write functionality working with two modules, but struggling with reading.

    To begin with, I'm running the peripheral example on one of the modules. I connect to it with my phone and read service 3 characteristic 4. This returns a value that increments with every read. 

    Now when running the central example on another module and connecting to the peripheral I can't read the same characteristic that I did with the phone. This is what I've tried:

    When the central is discovering all the services, it lists all the available characteristics. This is a picture of the discovery data being displayed for service 3. For characteristic 4 (the one I want to read) we can see it has a handle ID "0045". 

    I've then made some changes to the button press callback on the central. So when I'm pressing the button the "user_gatt_read_simple" gets called and passed the connection ID and the handle ID we got earlier (0045). So to my understanding, this should read service 3 characteristic 4.

    The problem is that this does not do anything. It doesn't even trigger the "user_catch_rest_hndl" function on the peripheral side. 

    What am I doing wrong?

  • Hi Markus, 

    Glad you were able to get the write functionality working.
    Regarding the read functionality on the central example:
    The example is searching for specific Services and Characteristics. Have you made any changes in your implementation?
    As you can see on handle_svc_ind function on the user_central_app.c file:

    	
    #ifdef ENABLE_IAS
    	bool ias_uuid_match;
    	uint16_t ias_uuid = ATT_SVC_IMMEDIATE_ALERT;
    	uint16_t ias_alert_uuid = ATT_CHAR_ALERT_LEVEL;
    	if(svc->svc_uuid.type == ATT_UUID_16){
    		ias_uuid_match = match_uuid((uint8_t *)&ias_uuid, (uint8_t *)&svc->svc_uuid.uuid.uuid16, sizeof(uint16_t));
    	}
    	
    	if(ias_uuid_match){
    		dbg_block_printf("Immediate Alert Service: ", NULL);
    	}
    #endif 

    We are checking if our Service UUID is type of ATT_UUID_16 and if it matches the ATT_SVC_IMMEDIATE_ALERT service UUID which is:
        /// Immediate alert Service
        ATT_SVC_IMMEDIATE_ALERT                     = ATT_UUID_16(0x1802),

    In the peripheral example we are using custom Services with UUID type ATT_UUID_128. I believe you have made some modifications because you would not get any printed messages regarding the Alert Service being discovered and found these characteristics.
    In the same function we save the operation value of the Alert Service like this:
    #ifdef ENABLE_IAS
    		if(ias_uuid_match)
    		{
    			if( match_uuid( (uint8_t *)&ias_alert_uuid , (uint8_t *)&gatt_char->uuid.uuid, 2) )
    			{
    				memcpy(&central_app_env.periph_devices[con_idx].serv_disc.ias_char, 
    																									gatt_char, sizeof(gattc_chars_t) );
    				
    				central_app_env.periph_devices[con_idx].serv_disc.ias_handle_valid = true;
    				dbg_block_printf("\tAlert Level Char\r\n", NULL);
    				
    				if(gatt_char->c.properties & GATT_PROP_WRITE)
    				{
    					central_app_env.periph_devices[con_idx].serv_disc.ias_write_op = GATTC_WRITE;
    				}
    				else if(gatt_char->c.properties & GATT_PROP_WRITE_NO_RESP)
    				{
    					central_app_env.periph_devices[con_idx].serv_disc.ias_write_op = GATTC_WRITE_NO_RESPONSE;	
    				}
    				else if(gatt_char->c.properties & GATT_PROP_WRITE_SIGNED)
    				{
    					central_app_env.periph_devices[con_idx].serv_disc.ias_write_op = GATTC_WRITE_SIGNED;	
    				}else
    				{		
    					central_app_env.periph_devices[con_idx].serv_disc.ias_handle_valid = false;
    				}
    			}
    		}
    #endif 

    If you did not do any modifications the ias_uuid_match should be false and this part of the code would not be executed.
    At the end you would be able to print the Characteristics UUIDs and properties since it is not in any if else statement:
    		dbg_block_printf("\t%04x char %s prop=%02x (%s)\r\n", gatt_char->handle,
                                                    format_uuid(&gatt_char->uuid), gatt_char->c.properties,
                                                    format_properties(gatt_char->c.properties));	

    Your Gatt Read is not working because you have given wrong Handle ID.
    You will need to work with the structs based on user_ble_common.h file:
    typedef struct {
            att_uuid_t 	uuid;                ///< item UUID
            uint16_t 		handle;                ///< item handle 	
    				/** characteristic data (if type == ::GATTC_ITEM_TYPE_CHARACTERISTIC) */
    				struct {
    								uint16_t value_handle;  ///< characteristic value handle
    								uint8_t properties;     ///< characteristic properties
    				} c;
    
                    
    } gattc_chars_t;
    
    typedef struct{
    	uint16_t 			last_handle;
    #ifdef ENABLE_BAS
    	gattc_chars_t	bas_char;
    	bool 		 			bas_handle_valid;
    #endif
    #ifdef ENABLE_IAS
    	gattc_chars_t ias_char;
    	bool 		 			ias_handle_valid;
    	uint8_t 			ias_write_op;
    	uint8_t	 			ias_alert_counter;
    #endif
    	 
    }service_disc_t;
     
    typedef struct{
    	 
    	uint8_t 					con_idx;
    	bool 	 						con_valid;
    	struct 						bd_addr addr;
    	service_disc_t		serv_disc;
    	 
    }periph_device_t;
    
    typedef struct{
    	
    	 att_uuid_t			svc_uuid;
    	 uint16_t				num_chars;
    	 gattc_chars_t 	items[__ARRAY_EMPTY];
    	
    }service_info_t;

    As you can see the service_disc_t struct is custom for the Battery and Immediate Alert Services only.
    So, if you go on handle_service_disc_finished function on user_app_central.c file you will find the implementation of gatt read you want to implement:
    		user_gatt_read_simple(con_idx, 
    						central_app_env.periph_devices[con_idx].serv_disc.bas_char.c.value_handle);

    As you can see as a handler, we are giving the value of the value_handle (characteristic value handle) which is inside the c struct, which is inside the gattc_chars_t struct. Which for the Battery Service is the same as the bass_char.

    In your implementation you are reading the handler from the print I shared above. Please see that you are printing the item handle of the gattc_chars_t struct and not the value_handle of the c struct which is inside the gattc_chars_t struct.
    I know it can be quite confusing to follow. You could try with your debugger attached and try to read the values (by adding them on a Watch window) saved inside those structs in order to get a better understanding. 
    I hope this could be helpful. If you face any issues, feel free to ask. 
    If you found any of the answers helpful to your issues, please verify them so you can help others in the community as well.

    Kind Regards,
    OV_Renesas

  • Thank you for the thorough answers. Here are some clarifications:

    "The example is searching for specific Services and Characteristics. Have you made any changes in your implementation?"

    Yes, I have made some modifications so the code executes even though there is no UUID-match

    "Your Gatt Read is not working because you have given wrong Handle ID."

    I checked this and I am giving the right Handle ID. If I print the value handle for every discovered       characteristic like this:

    I get this: 

    When I read the last characteristic (0048) on my phone everything works. When I do it with my central nothing happens for 20-30 seconds and then I get a message on the central:  GATTC_READ: status: 0045

    As I mentioned earlier, I'm able to write to characteristics just fine, but reading does not work.

  • Hi Markus,

    Thank you for your reply.
    After checking it again I think the problem is that you do not catch the response message.
    The GATTC_READ: status 0045 you got corresponds to a Timeout Error.
    As you can see on the uset_gatt_read_simple function:

    /**
     ****************************************************************************************
     * @brief Perform a simple gatt read - read is returned by GATTC_READ_IND
     * @param[in] con_idx - connection identifier
     * @param[in] handle - attribute handle to read
     * @return void
     ****************************************************************************************
     */
    void user_gatt_read_simple(uint8_t con_idx, uint16_t handle)
    {
        struct gattc_read_cmd * req  = KE_MSG_ALLOC(GATTC_READ_CMD, KE_BUILD_ID(TASK_GATTC, con_idx),
                TASK_APP, gattc_read_cmd);
        //request type
        req->operation                       = GATTC_READ;
        req->nb                             = 1;
        req->req.simple.offset              = 0;
        req->req.simple.length              = 0;
        req->req.simple.handle              = handle;
    
        //send request to GATT
        ke_msg_send(req);
    }

    As you can see we sent the GATTC_READ_CMD message and we will receive the result with GATTC_READ_IND:
        /*Read Value*/
        /*Read Using UUID*/
        /*Read Long Value*/
        /*Read Multiple Values*/
        /// Read command
        GATTC_READ_CMD,
        /// Read response
        GATTC_READ_IND,

    You can find these messages on gattc_task.h file. 
    As you can see on the central example in order to read the Battery Level we catch the GATTC_READ_IND on the user_catch_rest_hndl function:
    ...
    		case GATTC_READ_IND:
    		{
    			struct gattc_read_ind const *ind = (struct gattc_read_ind const*)param;
    #ifdef ENABLE_BAS
    			if(ind->handle == central_app_env.periph_devices[conn_idx].serv_disc.bas_char.c.value_handle)
    			{
    				dbg_printf("Battery Level Read: %d \r\n", ind->value[0]);
    			}else
    #endif 
    			{
    				dbg_printf("GATTC_READ_IND: handle: %04x\r\n", ind->handle);
    				
    			}
    		}break;
    		
    		
    		default:
    			break;
    ...

    Inside the GATTC_READ_IND case you should create another case for the specific characteristic you want. Then you should be able to get the response (read) from this characteristic and print it.
    I hope this would work on your side.

    Kind Regards,
    OV_Renesas

  • This can't be the problem. I placed a breakpoint inside the GATTC_READ_IND case, and apparently, the code never goes there. 

    So to clarify, I have the read-call placed inside the pushbutton callback. So every time I push the button I should read the characteristic. When I first press the button, nothing happens for 20-30s but then I get the status message "GATTC_READ: status 0045". If I press the button after this I get the status message GATTC_READ: status 0043.

  • Hi Markus,

    The status message you are getting corresponds to:

        ///Request not allowed in current state.
        GAP_ERR_COMMAND_DISALLOWED = 0x43,

    Could you please add another case on the GATTC_READ_IND for the specific characteristic you want to read?
    Could you also change the Optimization Level from Options for Target:

    I would suggest adding a BKPT on the read function and go step by step to see which part of the code is being triggered.
    I would also suggest trying to debug from the ble_app_peripheral example side. To see if the user_catch_rest_hndl is being triggered when you request to read this characteristic. 
    If you want, you can share your project so I can try to debug it myself.

    Kind Regards,
    OV_Renesas 

  • I debugged on the peripheral side now. It behaves the same way as the central. When I press the button on the central side nothing happens. After 20-30s the user_catch_rest_hndl gets called. I printed the message IDs for this event. So 20-30s after I've pressed the button this shows up in the peripheral:

    Here are the projects if you could take a look:

    projects_src.zip

    Because I'm using the Event recorder you might have to make the following change:

    Open EvenRecorder.c -> There will be a file called RTE_Components.h -> open this file -> make sure line 17 is the following: 

    Thanks a lot!

Reply
  • I debugged on the peripheral side now. It behaves the same way as the central. When I press the button on the central side nothing happens. After 20-30s the user_catch_rest_hndl gets called. I printed the message IDs for this event. So 20-30s after I've pressed the button this shows up in the peripheral:

    Here are the projects if you could take a look:

    projects_src.zip

    Because I'm using the Event recorder you might have to make the following change:

    Open EvenRecorder.c -> There will be a file called RTE_Components.h -> open this file -> make sure line 17 is the following: 

    Thanks a lot!

Children
No Data