系统分区表
W801是平头哥内核的WIFI芯片。给到的SDK里面没有找到中断向量表重定向的函数,类似于STM32的NVIC_SetVectorTable,所以中断向量表只能给主程序用,那么BootLoader就没法弄了。干脆不用BootLoader了,直接划一个分区用来存放Updater代码,用于解压或差分计算新固件并复制到主分区(存在变砖的可能性)。
国产芯片就是资料不全,规格书里面写了Flash的寻址空间为0x0800 0000 ~ 0x0FFF FFFF,但是在SDK里面,链接脚本是从0x080d0400开始的,0x080d0400是在toolsw800wconfig文件里面W800_RUN_ADDRESS选项定义的默认值:
但是把W800_RUN_ADDRESS改成0x08000000之后无法运行,而且下载的时候发现芯片的MAC地址值还被冲刷了,把W800_RUN_ADDRESS改成大于0x080d0400的地址值是可以运行的,可能是前面的一些空间是用作其它用途的(后面发现擦写Flash的时候,从0x08000000开始计算2MB之后的地址是没法写入的,代码里面做了地址范围判断)。所以这里只好按照0x080d0400之后的地址值作为Flash的起始地址开始分区,由于芯片内部Flash的最小擦除单位是sector,一个sector为4KB,为了方便擦写,取一个最近的4KB对齐地址0x080D1000作为起始地址,同时每个分区的大小也设置为4KB的整数倍。2MB Flash的有效范围是0x08000000到0x08200000,那么定义用户代码的有效地址范围为0x080D1000~0x08200000,一共1212KB。
分区表设计如下图:
最开始的800KB必须作为主程序Main APP,因为中断向量表固定在这个位置。紧跟64KB为Updater程序。Updater后面的32KB的Sub APP用于其它用途,然后是4KB存放OTA参数。最后312KB存放下载的OTA固件包(全量包、全量压缩包或差分包)。
OTA工作流程
Updater工程
Updater软件为单独的一个工程,直接拷贝原来的SDK软件,修改连接脚本:
......
MEMORY
{
I-SRAM : ORIGIN = 0x08199000 , LENGTH = 0x10000 /* I-SRAM 100KB */
D-SRAM : ORIGIN = 0x20000100 , LENGTH = 0x47f00 /* D-SRAM 288KB */
V-SRAM : ORIGIN = 0x20000000 , LENGTH = 0x100 /* off-chip SRAM 8MB */
}
......
I-SRAM是指令存储器,把它的起始地址修改成Updater分区的起始地址0x08199000,LENGTH为64KB。把wm_main.c里面的main函数直接改成:
int main(void)
{
u32 value = 0;
/*32K switch to use RC circuit & calibration*/
tls_pmu_clk_select(0);
/*Switch to DBG*/
value = tls_reg_read32(HR_PMU_BK_REG);
value &= ~(BIT(19));
tls_reg_write32(HR_PMU_BK_REG, value);
value = tls_reg_read32(HR_PMU_PS_CR);
value &= ~(BIT(5));
tls_reg_write32(HR_PMU_PS_CR, value);
/*Close those not initialized clk except uart0,sdadc,gpio,rfcfg*/
value = tls_reg_read32(HR_CLK_BASE_ADDR);
value &= ~0x3fffff;
value |= 0x1a02;
tls_reg_write32(HR_CLK_BASE_ADDR, value);
void disp_version_info(void);
disp_version_info();
tls_sys_clk_set(CPU_CLK_80M);
UserMain(); // for updater proj, OS is not required, directly jump to UserMain
while(1);
return 0;
}
因为Updater不需要运行操作系统,只需要做一些解压、差分运算以及读取、擦除和写入数据到flash。另外没有在main函数里面添加Updater的核心代码,而是调用的UserMain函数,是因为编译系统将wm_main.c和其它系统层代码编译成SDK库,最后和app文件夹里面的应用层代码链接成可执行文件的,并且编译库make lib速度较慢,而Usermain函数是属于应用层的代码,直接编译更方便。
Main APP工程
由于官方没有给很好的Flash烧写工具,每次make flash或者make down好像都只能从固定地址处开始下载,没法下载文件到指定的地址,所以把Updater软件的可执行bin文件作为常量数组先放到Main APP的工程中,并指定该数组的存放段为 .updater_bin,下载Main APP的时候连带Updater一起下载进去(也可以在Main APP里面添加一个烧写功能,使用X-modem之类的通信协议下载数据到指定位置)。
__attribute__((section(".updater_bin"))) const uint8_t updater_bin[1024*64] =
{
0x00, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08,
0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08,
0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08,
0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08, 0xD4, 0x85, 0x19, 0x08,
......
修改Main APP的链接脚本:
......
MEMORY
{
I-SRAM : ORIGIN = 0x080D1000 , LENGTH = 0x120000 /* I-SRAM 1M+128KB */
D-SRAM : ORIGIN = 0x20000100 , LENGTH = 0x47f00 /* D-SRAM 288KB */
V-SRAM : ORIGIN = 0x20000000 , LENGTH = 0x100 /* off-chip SRAM 8MB */
UPDATER : ORIGIN = 0x08199000 , LENGTH = 0x10000 /* updater */
}
......
.updater_bin :
{
KEEP(*main.o(.updater_bin))
} > UPDATER
......
链接脚本里面添加一个存储器UPDATER,起始地址为Updater分区的起始地址,大小64KB。再添加一个updater_bin段,Updater的bin文件数组就放在这个段里面。这样Main APP生成的bin文件大小就变成864KB了(800KB + 64KB),下载较慢。
这样在Main APP中就可以正常跳转到Updater中去了(注意,可执行文件的前256个字节为中断向量表,第一个中断向量为复位中断,所以向量表的第一个字为reset handler的地址):
#define OTA_PARAM_START_ADDRESS 0x081B1000
#define OTA_PARAM_LENGTH (4 * 1024)
#define OTA_PACKAGE_START_ADDRESS 0x081B2000
#define OTA_PACKAGE_LENGTH (312 * 1024)
#define OTA_UPDATER_START_ADDRESS 0x08199000
#define OTA_UPDATER_LENGTH (64 * 1024)
#define OTA_SUBAPP_START_ADDRESS 0x081B1400
#define OTA_SUBAPP_LENGTH (32 * 1024)
#define OTA_MAINAPP_START_ADDRESS 0x080D1000
#define OTA_MAINAPP_LENGTH (800 * 1024)
__attribute__((section(".updater_bin"))) const uint8_t updater_bin[1024*64] =
{
......
};
void UserMain(void)
{
uint32 *updater_start_addr = (uint32 *)OTA_UPDATER_START_ADDRESS;
uint32 updater_reset_handler_addr = *updater_start_addr;
LOGI("updater_bin addr:%.8Xn", (uint32)updater_bin);
LOGI("updater_bin:%.2X %.2X %.2X %.2Xn", updater_bin[0], updater_bin[1], updater_bin[2], updater_bin[3]);
LOGI("updater_start_addr:%.8Xn", (uint32)updater_start_addr);
LOGI("updater_reset_handler_addr:%.8Xn", (uint32)updater_reset_handler_addr);
void (*reset_handler)(void) = (void (*)(void))updater_reset_handler_addr;
reset_handler();
while (1) {
tls_os_time_delay(5 * HZ);
}
}
附:后面发现,Updater工程中设置了W800_RUN_ADDRESS为0x08199000之后,下载是从0x08199000处开始下载,这样Main APP下载就不会影响Updater代码了,两个工程可以独立下载不干扰,所以不需要再把Updater文件作为常量数组放在Main APP里面了。但是要注意,代码的执行地址也会变成W800_RUN_ADDRESS(可能0x08000000前面的一段空间是一个BootLoader,下载的时候把W800_RUN_ADDRESS传给它了,所以BootLoader启动应用程序的时候会跳转到W800_RUN_ADDRESS处运行并且设置了中断向量表地址也为W800_RUN_ADDRESS),所以单片机复位之后是会运行最后下载的代码的,那么需要先下载Updater再下载Main APP才行。
简单的HTTP服务器
创建本地HTTP服务器用于下载OTA固件包。参考链接:
快速搭建一个简易的HTTP服务器用于文件分享与下载 - 灰信网(软件开发博客聚合)
https://www.freesion.com/article/2710660021/
1、使用Python脚本创建http server:
import http.server
import socketserver
PORT = 80
Handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("serving at port", PORT)
httpd.serve_forever()
2、使用小工具软件MyWebServer,也很方便:
3、使用Node.js搭建一个简单的RESTful API服务器:
var express = require('express');
var app = express();
var fs = require("fs");
app.get('/ota/query', function (req, res) {
fs.readFile( __dirname + "/resource/" + "info.json", 'utf8', function (err, data) {
console.log( data );
res.end( data );
});
})
app.get('/ota/down/*', function (req, res) {
console.log("req.params[0]:" + req.params[0]);
fs.readFile( __dirname + "/resource/" + req.params[0], function (err, data) {
res.end( data );
});
})
var server = app.listen(80, function () {
// var host = server.address().address
var host = "127.0.0.1"
var port = server.address().port
console.log("Server address: http://%s:%s", host, port)
})
OTA固件压缩和解压
使用的miniz库:Miniz is a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards. It supports the most commonly used functions exported by the zlib library......
PC端可以对OTA固件进行压缩,单片机端进行解压。由于单片机端没有足够的空间进行一次性数据解压,所以PC端进行一次性数据压缩,单片机端进行分批次数据解压(解压比较耗内存,单片机端代码尽量将大数组用全局变量或静态变量定义,不然heap空间不够解压算法使用会导致解压失败),代码示例:
#include #include #include #include #include #include "string.h" #include "debug.h" #include "event.h" #include #include #include #include #include "miniz.h" typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned int uint; #define my_max(a,b) (((a) > (b)) ? (a) : (b)) #define my_min(a,b) (((a) < (b)) ? (a) : (b)) #define BUF_SIZE (64 * 1024) static uint8 s_inbuf[BUF_SIZE]; static uint8 s_outbuf[BUF_SIZE]; int my_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ if ((*pSource_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = s_inbuf; stream.avail_in = 0; stream.next_out = s_outbuf; stream.avail_out = BUF_SIZE; stream.total_out = 0; status = mz_inflateInit(&stream); if (status != MZ_OK) return status; uint remaining = *pSource_len; uint cursor = 0; uint total = 0; // LOGI("remaining: %d, cursor: %dn", remaining, cursor); while (1) { // If input buffer is empty, read more bytes from input file. if (!stream.avail_in) { uint n = my_min(BUF_SIZE, remaining); LOGI("Reading %d bytes data...n", n); memcpy(s_inbuf, pSource + cursor, n); // if (fread(s_inbuf, 1, n, pInfile) != n)
上一篇:W801上电自动重连wifi并通过蓝牙更新账号密码
下一篇:联盛德W801系列3-如何提高采集多路ADC效率
推荐阅读最新更新时间:2024-11-03 17:24