Bluetooth MCU development journey: Dialog OTA scheme combing and package transfer transformation

Keywords: Mobile less iOS Android

Dialog 14585 OTA uses a dual backup scheme, and the external interface is embedded in the user's application as a separate Service (0xF5FE) without using their mobile app for OTA, or as their own defined UUID.

In the following figure, the entire OTA scheme contains one ProductHeader and two images, and one ImageHeader per image is used to store image information, where the ProductHeader is stored at 0x38000 by default and can be modified as required.

The code the firmware uses to process OTA mainly consists of four files, suotar.c, suotar_task.c, app_suotar.c and app_suotar_task.c.Suotar.c defines the Service s and attributes of ota, where you can modify the UUID of OTA. Suotar_task.c handles various read/write/notify Handler s. When a mobile phone performs OTA operations, the protocol stack forwards the message here, which is initially processed and forwarded to the user app layer: app_suotar.c and app_suotar_task.c.

If you go directly to the official scheme, you can add OTA services without modifying any code, and open source sample code is provided on your mobile phone, so you can just write one.Here's a comb of the process against the official codes on the firmware and mobile ends, which is about this:

The firmware side provides the following OTA Service and attributes: 
SPOTA_SERVICE:      //service,OTA Master Service
SPOTA_SERV_STATUS:  //status notification, used to update the status of OTA
SPOTA_GPIO_MAP:     //GPIO settings (gpio should be defined by the firmware side and need not be modified, I remove this)
SPOTA_MEM_DEV :     //memory information, which defines where data is stored (RAM/EEPROM/FLASH), is typically flash
SPOTA_PATCH_LEN:    //Length of each packet, no need to rewrite when the length has not changed
SPOTA_PATCH_DATA:   //data packet

1.modify MTU  23-->247                                  //ios mobile phone some models can only change mtu to 186 Max
  BluetoothGattCharacteristic.requestMtu(patchDataSize + 3);//patchDataSize is the length of a package	

2.open notification : SPOTA_SERV_STATUS

3.write MEM_DEV :(value:0x13000000) start ota  //The vast majority of applications currently use the flash scheme, which can be defaulted to 0x13000000, so that when OTA is done on the mobile side, it will not be written.

4.Division patch Package, calculation crc,crc Value attached to img Last byte  	//patch package: (sizeof(img)+1), this value can be modified, adapting to different mobile phones may need to be set to a different value, corresponding, the next patchLen also needs to be modified, note that the last package length may be shorter;
crc:
	byte crc_code = 0;
        for (int i = 0; i < this.bytesAvailable; i++) {
            Byte byteValue = this.bytes[i];
            int intVal = byteValue.intValue();
            crc_code ^= intVal;
        }

5.Set up patch Len  =244,Representing transmission patchData Length,

6.upload patch Data
...

7.If the length of the last package changes before uploading, it needs to be rewritten patchLen
 //For example, set patch Len=132
//Upload the last patch Data.

8.All data transfer is complete. MEM_DEV: write end Signal  0xfe000000

9.Restart the device, MEM_DEV: write reboot Signal   0xfd000000

In order to reduce the interactive process, here are some common things that can be initialized directly to a default value:

#if (SUOTA_DEF_CONFIG)
	memset(&suota_state,0x00,sizeof(suota_state));
	suota_state.mem_dev=0x13;       //Device type, default here is flash
	suota_state.mem_base_add=0x00;	//Initial address 
	suota_state.gpio_map=0x05060300;//Set GPIO map	
#endif

Then, we encountered a strange, helpless and sad problem epsilon () 3: Each Image package starts with an ImageHeader of 64 bytes, and when Dialog's official code moves the end OTA directly by default, the first package will transfer the Header completely, if the length of the first package is less than 64And errors will occur!But the app they use for the OTA example can also set a very low patchDataSize, which is more or less OK...

There's nothing wrong with this; the more bytes you transfer at a time, the less time you spend in total.But the problem is: MDK of Mijia APP plug-in does not provide api to modify Bluetooth MTU for compatibility with different mobile phone models, and does not roll around to do this... ios mobile phone can also directly transfer so much data without modifying MTU, android machine can only transfer 20 bytes if it does not modify MTU...

The firmware then begins to transform the OTA service to accommodate cases where the packet length is less than 64 bytes...

Several key functions:

 static int suotar_patch_data_ind_handler(ke_msg_id_t const msgid,
                                          struct suotar_patch_data_ind const *param,
                                          ke_task_id_t const dest_id,
                                          ke_task_id_t const src_id);
void app_suotar_img_hdlr(void);
int app_read_image_headers(uint8_t image_bank, uint8_t* data, uint32_t data_len);

Here, if the packet length is less than 64 bytes, ImageHeader can only be sent in several packages, so a new attribute flag_img_header_is_read has been added to the original tag status structure to mark whether the image header has been read completely:

// Holds the retainable variables of SUOTAR app
typedef struct
{
    uint8_t     mem_dev;
    uint32_t    mem_base_add;
    uint32_t    gpio_map;
    uint32_t    new_patch_len;
    uint8_t     suota_pd_idx;
    uint8_t     suota_image_bank;
    uint32_t    suota_block_idx;
    uint32_t    suota_img_idx;
    bool        flag_img_header_is_read;//Add a new property to mark if the image header has been read completely
    uint8_t     crc_calc;
    uint32_t    suota_image_len;
    void (*status_ind_func) (const uint8_t);
    uint8_t     reboot_requested;
}app_suota_state;

This flag needs to be cleared before starting OTA, which can be done in the function app_suotar_start.

Processing ImageHeader to make it compatible:

int app_read_image_headers(uint8_t image_bank, uint8_t* data, uint32_t data_len)
{
    image_header_t *pImageHeader;
    product_header_t *pProductHeader;
    image_header_t *pfwHeader;
    uint32_t codesize;
    int32_t ret;
    uint8_t mem_data_buff[64];
    uint32_t imageposition1;
    uint32_t imageposition2;
    uint8_t new_bank = ANY_IMAGE_BANK;
    uint8_t is_invalid_image1 = IMAGE_HEADER_OK;
    uint8_t is_invalid_image2 = IMAGE_HEADER_OK;
    uint8_t imageid  = IMAGE_ID_0;
    uint8_t imageid1 = IMAGE_ID_0;
    uint8_t imageid2 = IMAGE_ID_0;

//You need to wait until you have read enough 64 bytes before processing the imageheader.

//suota_state.img_idx_at_img_header_is_read+=data_len;
	
//    if( data_len < sizeof(image_header_t) )
//    {
//        // block size should be at least image header size
//        return SUOTAR_INVAL_IMG_HDR;
//    }
//    else
//    {
//        // read header form first data block
//        pfwHeader = (image_header_t*)data;
//    }

	if(suota_state.suota_block_idx<sizeof(image_header_t)){
		return IMAGE_HEADER_OK;
	}else{
		// read header form suota_all_pd.
        pfwHeader = (image_header_t*)suota_all_pd;	
	}

    //Other logic
    ......

    // write image and header
    if( suota_state.mem_dev == SUOTAR_IMG_SPI_FLASH )
    {
#if (!SUOTAR_SPI_DISABLE)
        // write header
        ret = app_flash_write_data((uint8_t*)pImageHeader, suota_state.mem_base_add, sizeof(image_header_t));
        if( ret != sizeof(image_header_t) ) return SUOTAR_EXT_MEM_WRITE_ERR;
        //The data written to flash here is no longer taken from the current package and needs to be taken from suota_all_pd instead
		//ret = app_flash_write_data((uint8_t*) &data[CODE_OFFSET], suota_state.mem_base_add + CODE_OFFSET, data_len - CODE_OFFSET);
        //if( ret != (data_len - CODE_OFFSET) ) return SUOTAR_EXT_MEM_WRITE_ERR;
		
        ret = app_flash_write_data((uint8_t*) &suota_all_pd[CODE_OFFSET], suota_state.mem_base_add + CODE_OFFSET, suota_state.suota_block_idx - CODE_OFFSET);
        		
		if( ret != (suota_state.suota_block_idx - CODE_OFFSET) ) return SUOTAR_EXT_MEM_WRITE_ERR;
#else
        return SUOTAR_EXT_MEM_WRITE_ERR;
#endif
    }
    
    //Other logic
    ......
	
    //When you get here, the image header is fully read
	suota_state.flag_img_header_is_read=true;
	
    return IMAGE_HEADER_OK;
}

The app_suotar_img_hdlr function, which checks the image block and writes it to flash, removes the CRC check here:

void app_suotar_img_hdlr(void)
{
	//Other logic
    ......

    //Verify CRC, where suota_block_idx was moved to another entry function because it was not cleared
    // Update CRC
    //for(i=0;i<suota_state.suota_block_idx;i++){
    //    suota_state.crc_calc ^= suota_all_pd[i];
    //}
    // Check mem dev.
    switch (suota_state.mem_dev)
    {
    //Other logic
	......	
    //Branch of flash
        case SUOTAR_IMG_SPI_FLASH:
#if (!SUOTAR_SPI_DISABLE)
            app_suotar_spi_config(&spi_conf);
            app_spi_flash_init(&spi_conf.cs);

		    //If you haven't read the full image header yet, continue reading
            // When the first block is received, read image header first
            //if( suota_state.suota_block_idx != 0 && suota_state.suota_img_idx == 0 )
			if( suota_state.suota_block_idx != 0 && !suota_state.flag_img_header_is_read )
            {
                // Read image headers and determine active image.
                ret = app_read_image_headers( suota_state.suota_image_bank, suota_all_pd, suota_state.suota_block_idx );
                if( ret != IMAGE_HEADER_OK )
                {
                    status = ret;
                }
                else
                {
                    // Update block index
                    //suota_state.suota_img_idx += suota_state.suota_block_idx;	
                    if(suota_state.flag_img_header_is_read)suota_state.suota_img_idx += suota_state.suota_block_idx;					
                }
            }
            else
            {
                //check file size
                if (( suota_state.suota_image_len+ADDITINAL_CRC_SIZE ) >= ( suota_state.suota_img_idx + suota_state.suota_block_idx ))
                {
                    if (suota_state.suota_image_len < (suota_state.suota_img_idx + suota_state.suota_block_idx))
                        suota_state.suota_block_idx = suota_state.suota_image_len - suota_state.suota_img_idx;
					
                    ret = app_flash_write_data (suota_all_pd, (suota_state.mem_base_add + suota_state.suota_img_idx), suota_state.suota_block_idx);
                    					
					if( ret !=  suota_state.suota_block_idx){
                        status = SUOTAR_EXT_MEM_WRITE_ERR;
                    }
                    else
                    {
                        // Update block index
                        suota_state.suota_img_idx += suota_state.suota_block_idx;						
                    }
                }
                else
                {
                    status = SUOTAR_EXT_MEM_WRITE_ERR;
                }
            } 
            //Zeroing the index only after reading the image header           
			//suota_state.suota_block_idx = 0;
            if(suota_state.flag_img_header_is_read){suota_state.suota_block_idx = 0;}
            mem_info = suota_state.suota_img_idx;
            suotar_send_mem_info_update_req(mem_info);
#else
            status = SUOTAR_INVAL_MEM_TYPE;
#endif //(!SUOTAR_SPI_DISABLE)
            break;

        default:
            status = SUOTAR_INVAL_MEM_TYPE;
            break;
    }

    // SUOTA finished successfully. Send Indication to initiator
    suotar_send_status_update_req((uint8_t) status);
}

The suotar_patch_data_ind_handler function, which is a function for processing packets, is mainly supplemented by the CRC checks removed from the app_suotar_img_hdlr function:

static int suotar_patch_data_ind_handler(ke_msg_id_t const msgid,
                                         struct suotar_patch_data_ind const *param,
                                         ke_task_id_t const dest_id,
                                         ke_task_id_t const src_id)
{
    if( suota_state.mem_dev < SUOTAR_MEM_INVAL_DEV )
    {
        if (param->char_code)
        {
            if( suota_state.new_patch_len )
            {
                if( SUOTAR_IS_FOR_IMAGE( suota_state.mem_dev ) )
                {
                    //---------------------------- Handle SUOTAR image payload -----------------------
                    if( (suota_state.suota_block_idx + param->len) <= SUOTA_OVERALL_PD_SIZE)
                    {
                        memcpy(&suota_all_pd[suota_state.suota_block_idx], param->pd, param->len );
						suota_state.suota_block_idx += param->len;
						//suota_block_idx will reset to 0,then this check converm that new_patch_len equals param->len.
						//if( suota_state.new_patch_len == suota_state.suota_block_idx )
                        if( suota_state.new_patch_len == param->len )
                        {	
                            //Check CRC here instead	
							// Update CRC
							uint32_t     i;
							for(i=0;i<param->len;i++){
								suota_state.crc_calc ^= param->pd[i];
							}
					
                            app_suotar_img_hdlr();							
                        }
                        //Do this check only after reading the image header 
						//if( suota_state.suota_block_idx > suota_state.new_patch_len  )
                        if( suota_state.flag_img_header_is_read&&suota_state.suota_block_idx > suota_state.new_patch_len  )
                        {
                            // Received patch len not equal to PATCH_LEN char value
                            suotar_send_status_update_req((uint8_t) SUOTAR_PATCH_LEN_ERR);
                        }
                    }
                    else
                    {
                        suotar_send_status_update_req((uint8_t) SUOTAR_INT_MEM_ERR);
                    }
                }
                else
                {
                    suotar_send_status_update_req((uint8_t) SUOTAR_INVAL_MEM_TYPE);
                }
            }
            else
            {
                // Inavild PATCH_LEN char value
                suotar_send_status_update_req((uint8_t) SUOTAR_PATCH_LEN_ERR);
            }
        }
    }
    else
    {
        suotar_send_status_update_req((uint8_t) SUOTAR_INVAL_MEM_TYPE);
    }

    return (KE_MSG_CONSUMED);
}

Thus, a 20-byte package OTA scheme is modified.

Posted by Hieroglyphics on Sun, 15 Sep 2019 00:13:29 -0700