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.