DA14531 connection with generic sensor

Hi everyone,
I am working on a project where a DA14531 device on a custom board needs to connect to a generic external BLE sensor, not part of this device family. I am using the template provided by the example BLE_SDK6_examples/connectivity/central/. Running the example, the sensor is obviously scanned (I modified some filtering settings compared to the default SCAN_FILTER_NAME with "DLG-PROXR"), but I can't figure out how to modify the code so that, when the sensor is scanned, a connection procedure starts (similar to what happens by default in target_apps/ble_examples/prox_reporter). The name ad BD address of the sensor are known.
Thanks for your time!

  • Hi Seba,

    Thank you for posting your question online.
    Let me describe the procedure below:
    1) The DA14531 starts scanning. After booting up, we configure the BLE stack. When the BLE stack has been properly configured, the user_app_on_set_dev_config_complete callback function will be executed. In that callback we call the ble_scan_for_devices API.

    /**
     ****************************************************************************************
     * @brief Callback from SDK once stack is configured
     * @return void
     ****************************************************************************************
     */
    void user_app_on_set_dev_config_complete(void)
    {
    		/*Advertising starts within this default handler, but we have made the callback NULL to avoid advertising. 
    		* alternate method, would be to copy the default handler code into here, removing the advertising call
    		*/
    		default_app_on_set_dev_config_complete();
    	
    		ble_scan_for_devices();
    	
    		spi_flash_power_down(); //power down flash before entering sleep
    }
    

    2) While we scan, we receive the advertising reports. The reports are being handled in the user_on_adv_report_ind callback function. By default, we have set some SCAN macros so you can filter the devices you have found.

    /**
     ****************************************************************************************
     * @brief Callback from SDK when we get the advertising report.
     * @param[in] report  advertising report information
     * @return void
     ****************************************************************************************
     */
    void user_on_adv_report_ind(struct gapm_adv_report_ind const * adv_ind)
    {
    	bool dev_found = false;
    	
    #if CONNECT_TO_PERIPHERAL == 1
    	bool conn_to_device = false;
    	struct bd_addr dev_addr = adv_ind->report.adv_addr;
    #endif 
    
    	uint8_t num_ad_elements = user_ble_gap_get_adv_num_elements(adv_ind->report.data, 
    																																		adv_ind->report.data_len);
    	ble_gap_adv_struct_t adv_data_structs[num_ad_elements]; 
    	
    	user_ble_gap_parse_adv_data(adv_ind->report.data_len, adv_ind->report.data, adv_data_structs);
    	
    	uint8_t i;
    	for(i = 0 ; i < num_ad_elements; i++)
    	{
    		
    		switch(adv_data_structs[i].type)
    		{
    #if SCAN_FILTER == SCAN_FILTER_NONE
    			case GAP_AD_TYPE_FLAGS:
    			{
    				dev_found = true;
    				dbg_block_printf("GAP FLAGS: %s\r\n", 
    													format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) );
    				break;
    			}
    #endif 
    #if (SCAN_FILTER == SCAN_FILTER_NONE) 
    			case GAP_AD_TYPE_MORE_16_BIT_UUID:
    			{
    				dev_found = true;
    				dbg_block_printf("INCOMP LIST 16-BIT SVC: %s\r\n", 
    													format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) );
    				break;
    			}
    #endif
    #if (SCAN_FILTER == (SCAN_FILTER_NAME) || SCAN_FILTER == SCAN_FILTER_NONE) 
    
    			case GAP_AD_TYPE_COMPLETE_NAME:
    			{
    				dev_found = true;
    				
    				char local_name[adv_data_structs[i].len + 1];
    				format_adv_string(adv_data_structs[i].data,adv_data_structs[i].len, local_name);
    				dbg_block_printf("Device Local Name: %s\r\n", local_name);
    	#if CONNECT_TO_PERIPHERAL == 1
    				conn_to_device = memcmp(PERIPH_MATCH_DATA, adv_data_structs[i].data, PERIPH_MATCH_DATA_LEN) ? false : true;
    	#endif 
    				break;
    			}
    #endif 
    #if SCAN_FILTER == SCAN_FILTER_NONE
    			
    			case GAP_AD_TYPE_TRANSMIT_POWER:
    			{
    				dev_found = true;
    				
    				int8_t power;
    				memcpy(&power, adv_data_structs[i].data, 1); 
    				dbg_block_printf("TX_POWER: %i dBm\r\n", power);
    													
    				break;
    			}
    #endif
    #if (SCAN_FILTER == SCAN_FILTER_16_BIT_SVC_DATA || SCAN_FILTER == SCAN_FILTER_NONE) 
    			case GAP_AD_TYPE_SERVICE_16_BIT_DATA:
    			{
    				dev_found = true;
    				
    				uint16_t UUID;
    				memcpy(&UUID, adv_data_structs[i].data, sizeof(uint16_t));
    				dbg_block_printf("GAP_TYPE: SVC_DATA_16_BIT: UUID:%04X DATA:%s\r\n", 
    														UUID, format_hex_string(adv_data_structs[i].data+2, adv_data_structs[i].len-2) );
    				break;
    			}
    #endif 
    #if (SCAN_FILTER == SCAN_FILTER_MFG_DATA || SCAN_FILTER == SCAN_FILTER_NONE) 
    			case GAP_AD_TYPE_MANU_SPECIFIC_DATA:
    			{
    				dev_found = true;
    				dbg_block_printf("MFG_SPECIFIC_DATA: %s\r\n", 
    													format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) );
    				break;
    			}
    #endif 
    			default:
    			{
    #if SCAN_FILTER == SCAN_FILTER_NONE
    				dev_found = true;
    				
    				dbg_block_printf("GAP Type: %02x, Data: %s\r\n", adv_data_structs[i].type, 
    												format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) );
    #endif
    				break;
    			}
    			
    		}
    		
    	}
    	
    	if(dev_found){
    		uint8_t rssi_abs = 256 - adv_ind->report.rssi;
    		dbg_block_printf("RSSI: -%d\r\n", rssi_abs);
    		dbg_block_printf("BD_ADDR:%s \r\n---------------END_ADV-----------\r\n", 
    																						format_bd_address(&adv_ind->report.adv_addr) );
    	}
    
    	#if CONNECT_TO_PERIPHERAL
    	if(conn_to_device)
    	{
    		dbg_block_printf("Connecting to Device...\r\n", NULL);
    		central_app_env.connect_to_periph = true;
    		memcpy(&central_app_env.connect_to_addr, &dev_addr, sizeof(struct bd_addr));
    		central_app_env.connect_to_addr_type = adv_ind->report.adv_addr_type;
    		
    		user_ble_gap_stop_scan();
    		
    	}
    	#endif
    
    	
    	
    
    }
    

    In the Central example, we filter the advertising report by Device Name and we are checking for the DLG-PROXR name. The name is being handled by the PERIPH_MATCH_DATA macro on the user_central_config.h file. 

    When we find the correct name that matches with the one declared on PERIPH_MATCH_DATA we set the conn_to_device variable to TRUE. This will result in storing this specific advertising report information and call the user_ble_gap_stop_scan API to stop the scan procedure.

    3) When the Scan procedure stops, the user_on_scanning_completed callback function is executed. In that callback function we use the user_ble_gap_connect API to initiate the connection.

    /**
     ****************************************************************************************
     * @brief Callback from SDK when scan completed.
     * @param[in] reason reason for stopping the scan procedure
     * @return void
     ****************************************************************************************
     */
    void user_on_scanning_completed(uint8_t reason)
    {
    	
    	if(reason == GAP_ERR_CANCELED)
    	{
    		user_ble_gap_connect(central_app_env.connect_to_addr.addr, central_app_env.connect_to_addr_type);
    		central_app_env.connection_timer = app_easy_timer(CONNECTION_TIMEOUT_10MS, connection_timeout_cb);
    	}
    	else{
    			
    		dbg_printf("%s: ERR: reason: %d\r\n", __func__, reason);
    	}
    	
    	
    	
    }


    In your scenario, you could either change the PERIPH_MATCH_DATA macro to match your Device name or you could filter based on other parameters and use the conn_to_device variable in order to stop scanning and inititate the connection.

    Best regards,
    OV_Renesas


  • Thank you very much; I can now connect the DA14531 and the external sensor.

    The external sensor operates via AT commands, so I want to periodically send certain AT commands, receive the responses, and pass them via UART to a host microcontroller. My approach is as follows:

    • At the end of the connection, start a timer after discovering all the services:
    void user_on_connection(uint8_t connection_idx, struct gapc_connection_req_ind const *param)
    {
    [...]
    user_gatt_discover_all_services(connection_idx, 1);
    periodic_timer = app_easy_timer(5000, timer_callback_function);
    }
    • Periodically, the timer should call the timer_callback_function, which handles writing AT commands.

    From the output terminal, I see that the sensor connection index is 0x0000, the service of interest is 0xfff0, and the characteristic of interest is 0xfff2, which is the only one I can write to. For istance, the output terminal shows:

    handle_svc_ind: conn_idx=0000 start_h=0013 end_h=0018
    0xfff0:
       0015 char 0xfff1 prop=12 (-R--N---)
       0018 char 0xfff2 prop=0c (--XW----)
    Completion Event: GATTC_DISC_ALL_SVC: status: 0000
    Completion Event: GATTC_WRITE

    So, for timer_callback_function(), my plan is to:

    - Write the AT commands, example:

    user_ble_gatt_write(GATTC_WRITE_NO_RESPONSE, 0x0000, 0xfff2, at_command, data_len);
    - Restart the timer:
    periodic_timer = app_easy_timer(5000, timer_callback_function);


    In conclusion, with the timer_callback_function, I plan to send the AT command, receive the response, and later print the response using dbg_printf.
    Am I missing anything or doing something incorrectly in this procedure?

    Thank youfor your time,

    Regards.

  • Hi Seba,

    Thank you for the reply.

    At the end of the connection, start a timer after discovering all the services:

    I would personally start or call directly a function to control the AT commands on the static void handle_service_disc_finished(uint8_t con_idx) callback function that will be executed when all available services have been discovered.

    You are also utilizing the user_ble_gatt_write API the wrong way. Please refer on the user_ble_gatt.c/h files:

    /**
     ****************************************************************************************
     * @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);
    

    On the parameters, we give the characteristic handler and not the UUID of the characteristic.
    That would mean, that your call should look like this:


    user_ble_gatt_write(GATTC_WRITE_NO_RESPONSE, connection_idx0x0018, at_command, data_len);

    Where connection_idx variable has been taken from the user_on_connection callback function and 0x0018 corresponds to the char 0xfff2 with the Write Permission.

    Best Regards,
    OV_Renesas

  • All clear, thanks!

    To recap, I need to work with the following characteristics (verified through a smartphone app):

    • Service UUID: 0000fff0
    • Read characteristic: 0000fff1 (handle 0015), properties: Read, Notify
    • Write characteristic: 0000fff2 (handle 0018), properties: Write

    For my goal of sending AT commands and directly printing the responses on the terminal, I imagine I need to enable the notification service.

    Currently, based on your suggestion, I have only added the following line:

    user_ble_gatt_write(GATTC_WRITE_NO_RESPONSE, con_idx, 0x0018, at_command, data_len);

    in the static void handle_service_disc_finished(uint8_t con_idx) function.

    This alone of course doesn’t produce any output on the terminal.

    By adding the following command:

    user_gatt_read_simple(con_idx, 0x0015);

    along with the modification in user_catch_rest_hndl:

    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);
    				
    				for (int i = 0; i < ind->length; i++) {
    					dbg_printf("%02X ", ind->value[i]);
    				}
    			}
    		}break;

    I get the following on the terminal:

    Completion Event: GATTC_WRITE
    GATTC_READ_IND: handle: 0015
    GATTC_READ: status: 0000

    So, the reading is successful.

    Is there a way to achieve this using the notification service, without having to perform a direct read?

    Thank you, and I apologize for the continued disturbance.

  • Hi Seba,

    Thank you for the reply.

    For my goal of sending AT commands and directly printing the responses on the terminal, I imagine I need to enable the notification service.

    Just for clarification: Notifications are not a service. Notify is a Permission of the Service or the Characteristics. From the central side you can subscribe or unsubscribe in order to receive Notifications.
    This can be achieved with the following API:

    /**
     ****************************************************************************************
     * @brief Register for notifications/indications for specific service
     * @param[in] con_idx - connection id
     * @param[in] svc - service to parse
     * @return void
     ****************************************************************************************
     */
    
    void user_register_notification(uint8_t conidx, struct gattc_sdp_svc_ind const *disc_svc)	
    {
        if( disc_svc->start_hdl != ATT_INVALID_HANDLE)
        {
            //register profile task in gatt for indication/notifications
            struct gattc_reg_to_peer_evt_cmd * reg = KE_MSG_ALLOC(GATTC_REG_TO_PEER_EVT_CMD,
                    KE_BUILD_ID(TASK_GATTC, conidx), TASK_APP,
                    gattc_reg_to_peer_evt_cmd);
    
            reg->operation =  GATTC_REGISTER;
            reg->start_hdl = disc_svc->start_hdl;
            reg->end_hdl =   disc_svc->end_hdl;
    
            ke_msg_send(reg);
        }
    }
    
    
    //On the header file:
    void user_register_notification(uint8_t conidx, struct gattc_sdp_svc_ind const *disc_svc);

    You can call this API after you have discovered a Service, and the DA14531 (Central) will subscribe to the Notifications for this specific Service and its characteristics.

    Whenever you receive a Notification, the case GATTC_EVENT_IND will be triggered on the user_catch_rest_hndl function.

    Best Regards,
    OV_Renesas

  • Good Morning,

    To recap, I inserted the function you suggested here in  user_catch_rest_hndl:

    case GATTC_SDP_SVC_IND:
    {
    struct gattc_sdp_svc_ind const *disc_svc = (struct gattc_sdp_svc_ind const *)(param);
    
    uint8_t con_idx = KE_IDX_GET(src_id);
    
    if (disc_svc->start_hdl == 0x0013 && disc_svc->end_hdl == 0x0018)
    {
    dbg_printf("Service 0xfff0 found, registering for notifications...\r\n", NULL);
    
    user_register_notification(con_idx, disc_svc);
    
    }
    
    handle_svc_ind(con_idx, disc_svc);
    }break;

    with the goal of subscribing to the service discussed in previous questions for receiving notification.

    In the static void handle_service_disc_finished function, I inserted the usual command:

    user_ble_gatt_write(GATTC_WRITE, 0x0000, 0x0018, &temp, sizeof(temp));

    which I am sure works correctly, as the sensor receives it and lights up some LEDs.

    At this point, with notifications enabled and the following modified section:

    		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);
    				
    				for (int i = 0; i < ind->length; i++) {
    					dbg_printf("%04x\r\n", ind->value[i]);  // Stampa i dati in formato esadecimale
    				}
    				
    			}
    		}break;

    Should I expect that the data from the characteristic with handle 0x0015 will be notified and printed on the screen?

    This is very important because I need to interface a host microcontroller with the external sensor via BLE. Therefore, I have to monitor the entire process to see and understand what data are being sent.

    Thank for your time.

  • Hi Seba,

    Thank you for the reply.
    I would personally subscribe on the notifications of this specific service on the handle_svc_ind function when we discover the services. But I believe your implementation should work as well.
    To verify if it worked, please add the following case on the user_catch_rest_hndl:

    			case GATTC_REGISTER:
    			{
    				dbg_printf("GATTC_REGISTER: status: %04x \r\n", evt->status);
    			}
    			break;

    Should I expect that the data from the characteristic with handle 0x0015 will be notified and printed on the screen?

    You are checking on the case related to the Read request.
    You should check on the GATTC_EVENT_IND

    		case GATTC_EVENT_IND:
    		{
    			struct gattc_event_ind const *ind = (struct gattc_event_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 Changed: %d \r\n", ind->value[0]);			
    			}
    #endif		
    		}break;

    The Proximity reporter example has the Battery Service implemented, which will sent notifications when the Battery level has changed. The Central receives the Notification on the GATTC_EVENT_IND case. 
    Please add another if statement that compares your characteristic UUID with the ind->handle and then you can print out the data you received from the ind->value variable.

    Best Regards,
    OV_Renesas

  • Thank you, this was very useful.
    Now I can send AT commands and view the corresponding responses.
    Thank you very much for your valuable guidance!!

    Regards.