在嵌入式开发中,使产品的软件能够更新升级是个很常见的需求,作为开发人员,能够快速熟练地实现该功能是基本能力。
一般当设备不具备直接获取完整的更新文件时,需要逐包获取更新文件,因此要设计专门的更新代码以实现此功能。
在此记录下OTA的常用升级代码,以备不时之需。
1.数据结构
首先先来定义OTA需要的数据结构:
- 具有更新功能的软件,必须定义软件版本号,以下为一个参考:
1 2 3 4 5 6 7 8 9 10 11 12
| #ifndef __VERSION_H__ #define __VERSION_H__
#define PROJECT_ID 0x2A4B #define BOOT_VERSION 0x0 #define STACK_VERSION 0x16 #define USER_APP_VERSION 0x0002
#define APP_VERSION ((BOOT_VERSION << 24) | (STACK_VERSION << 16) | USER_APP_VERSION)
#endif
|
1 2 3 4 5 6 7 8 9 10
| uint8_t updata_ok = 0; uint32_t ota_offset = 16; uint16_t pk = 1024; uint16_t x_bk = 0; uint16_t remain = 0;
UPDATE_HEADER sys_vs_info = {0}; UPDATE_HEADER ota_vs_info = {0};
uint8_t ota_buff[1024 + 32] = {0};
|
- 以下为更新包16字节头的参考结构,其中必须要包含的是软件版本号、更新文件长度、校验码:
1 2 3 4 5 6 7 8 9 10
| typedef struct { uint16 project_id; uint8 root_version; uint8 stack_version; uint32 app_version; uint32 code_size; uint32 crc32; } UPDATE_HEADER;
|
2.OTA过程
OTA开始条件:一般设定一个时间间隔或定时来 check 一次是否有新版本更新,或是接收升级指令更新。check 有符合更新的版本时,即可开始请求。
获取更新数据:如果从其它模块获取更新数据,需要等待模块下载完成存储到数组中后,触发更新请求。OTA请求的第一个包是16字节的头文件,之后是设备更新数据。每次请求数据,offset 是数组的下标,长度为 pk 或 remain,更新数据写入 flash 的预留空间中。
判断接收完成:当接收的总字节数等于头文件的字节数时,即可置位 updata_ok,在合适的时间重启。
完成更新:重启后,进入 bootloader——如果设备有更新功能,则必须有 bootloader,每次设备复位后先进入 bootloader,检查 flash 预留空间是否有更新包可用,如果有则在校验通过后将更新包拷贝到应用程序空间,然后跳转到应用程序。
3.获取更新数据程序
OTA的其它过程没什么说的,主要是获取更新数据的过程,分为发送和接收。发送和接收又分为头16字节数据和剩余数据。在剩余数据中,又分为1024字节整的数据包和最后一个不足1024字节的数据包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
| bool check_ota_head(uint8_t *pData,uint16_t lens) { bool s = false; uint16_t sysAppNum = 0; uint16_t otaAppNum = 0; uint8_t otaApproot = 0; uint8_t otaAppstack = 0; ota_init(); memcpy(&ota_vs_info,pData,lens); sysAppNum = sys_vs_info.app_version; otaAppNum = ota_vs_info.app_version; otaApproot = ota_vs_info.app_version >> 24; otaAppstack = ota_vs_info.app_version >> 16; if(ota_vs_info.project_id == sys_vs_info.project_id && ota_vs_info.root_version == 0 && ota_vs_info.stack_version == 0 && sys_vs_info.root_version == otaApproot && sys_vs_info.stack_version == otaAppstack && sysAppNum != otaAppNum && ota_vs_info.code_size <= (40 * 1024)) { s = true; write_ota_ctrl(WRITE_OTA_INFO_CTRL); write_ota_ctrl(WRITE_OTA_FILE_SIZE_CTRL); x_bk = (ota_vs_info.code_size) / pk; remain = (ota_vs_info.code_size) % pk; } return s; }
bool check_bin_file(uint8_t *pData,uint16_t lens) { uint8_t i,j,k; k = lens / 128; j = lens % 128; if(ota_offset < ota_vs_info.code_size) { for(i = 0;i < k;i++) { ota_write_ota_data(&pData[i * 128],128); } if(j) { ota_write_ota_data(&pData[i*128],j); } } ota_offset += lens; if(ota_offset >= (ota_vs_info.code_size)) { dly_us(100000); write_ota_ctrl(WRITE_OTA_FILE_CRC32_CTRL); updata_ok = 1; ota_offset = 16; } return updata_ok; }
uint8_t copy_bin_file(uint8_t *p) { uint8_t len = 0; pk_t pt = {0}; if(ota_offset / pk < x_bk) { pt.addr = ota_offset; pt.pk = pk; } else if(remain) { pt.addr = ota_offset; pt.pk = remain; } len = sizeof(pk_t); memcpy(p,(uint8_t*)&pt,len); return len; }
void send_uartttl_data(void) { uint16_t ask = 0; uartMsg uMsg = {0};
memset(pub_buf.buf, 0, sizeof(pub_buf.buf)); pst_info_t s_cat1_data = (pst_info_t)&pub_buf.buf; getUartQueMsg(&uMsg);
switch(uMsg.msgType) { case OTA_HEAD: { ask = 1; } break;
case OTA_DATA: { ask = copy_bin_file((uint8_t*)&pub_buf.buf[5]); } break;
default: break; }
if(ask) { s_cat1_data -> head = CAT1_OTA_HEAD; s_cat1_data -> cmd = uMsg.msgType ; s_cat1_data -> len = ask; s_cat1_data -> crc = m_crc8((uint8_t*)&pub_buf.buf[5], ask); uart0_send((uint8_t*)pub_buf.buf, 5 + ask); uartQueMsg.w_count += 1; } }
void read_uartttl_data(void) { uint8_t t_crc = 0;
if(mxd_ttl.priv_len) { t_crc = m_crc8(&mxd_ttl.buf[5], mxd_ttl.priv_len - 5);
if(*(mxd_ttl.buf + 4) != t_crc || mxd_ttl.buf[0] != CAT1_OTA_HEAD) { clear_ttl(); return ; }
switch(*(mxd_ttl.buf + 1)) { case OTA_HEAD: { if(check_ota_head(&mxd_ttl.buf[5], mxd_ttl.priv_len - 5)) { set_uart_queue(OTA_DATA); } getUartQueMsgNumAdd(); uartQueMsg.w_count = 0; } break;
case OTA_DATA: { if(check_bin_file(&mxd_ttl.buf[5], mxd_ttl.priv_len - 5)) { } else { set_uart_queue(OTA_DATA); }
getUartQueMsgNumAdd(); uartQueMsg.w_count = 0; } break; } clear_ttl(); } }
|
4.OTA常见的问题
(stm32f103c8t6)传输完第一个1024字节后,mcu写入flash保存,写入之后程序挂死。
经过检查,有关升级的代码部分没有问题,查看程序大小,发现超出了自己所分配的程序运行空间(分配了25K的程序运行空间,紧接着是25K的升级包存储空间,而实际上程序大小超出了25K达到了30K),导致升级包被写入到了程序运行空间中。
解决方法是优化程序大小,或者重新分配程序运行空间和升级包存储空间的大小。