1. 项目概述
在嵌入式开发中,OTA(Over-The-Air)技术是实现设备远程固件升级的关键手段。本文将详细介绍如何在RT-Thread操作系统环境下,基于STM32F103VET6硬件平台和W5500以太网模块,实现从HTTP服务器下载固件文件并存储到外部Flash的完整流程。
这个方案特别适合资源受限的嵌入式设备,通过模块化设计实现了网络通信、文件下载和存储管理的解耦。虽然本文不涉及固件校验和跳转执行部分,但提供了可靠的文件下载和存储基础,为完整OTA功能开发打下了坚实基础。
2. 硬件与软件环境准备
2.1 硬件配置
我们使用的硬件平台是野火霸道开发板,核心配置如下:
- 主控芯片:STM32F103VET6(Cortex-M3内核,512KB Flash,64KB RAM)
- 网络模块:W5500硬件TCP/IP协议栈芯片
- 外部存储:SPI Flash(用于存储下载的固件文件)
W5500是一款集成了硬件TCP/IP协议栈的以太网控制器,相比软件协议栈方案,它能显著减轻MCU负担,特别适合资源有限的STM32F103平台。
2.2 软件环境
- RT-Thread版本:5.0.2标准版
- 开发环境:RT-Thread Studio
- 关键软件包:
- webclient:用于HTTP协议通信
- fal:Flash抽象层,提供统一的Flash操作接口
- EasyFlash:轻量级嵌入式Flash存储库
提示:在开始前,请确保已正确配置RT-Thread的以太网驱动,并能通过ping命令测试网络连通性。这是后续所有工作的基础。
3. 网络连通性测试
在实现OTA下载前,必须确认设备能够正常访问互联网。通过RT-Thread的MSH(Micro Shell)执行以下测试:
bash复制msh /> ping www.baidu.com
成功响应示例如下:
code复制32 bytes from 220.181.38.148 icmp_seq=0 ttl=52 time=30 ms
32 bytes from 220.181.38.148 icmp_seq=1 ttl=52 time=31 ms
32 bytes from 220.181.38.148 icmp_seq=2 ttl=52 time=30 ms
如果ping测试失败,需要按以下步骤排查:
- 检查W5500硬件连接是否正确(SPI接口和复位电路)
- 确认RT-Thread的W5500驱动配置正确
- 验证网络参数(IP地址、网关、DNS等)设置是否合适
4. 添加webclient软件包
webclient是RT-Thread提供的轻量级HTTP客户端实现,我们将使用它来完成固件文件的下载。
在RT-Thread Studio中添加webclient软件包的步骤:
- 右键点击项目 → 选择"RT-Thread Settings"
- 在软件包中心找到"webclient"并启用
- 保存配置后自动更新并编译项目
关键配置选项说明:
- WEBCLIENT_DEBUG:调试日志开关,开发阶段建议开启
- WEBCLIENT_USING_SAMPLES:示例代码,首次使用建议开启
- WEBCLIENT_BUFSZ:接收缓冲区大小,根据固件大小适当调整
5. Flash存储配置
5.1 fal分区设置
fal(Flash Abstraction Layer)是RT-Thread的Flash抽象层,它统一了不同Flash设备的操作接口。我们需要在board.h中配置下载分区:
c复制#define FAL_PART_TABLE
{
{FAL_PART_MAGIC_WORD, "download", "onchip_flash", 0, 1024*1024, 0},
}
这个配置定义了一个名为"download"的1MB分区,用于存储下载的固件文件。
5.2 EasyFlash初始化
EasyFlash提供了更友好的KV存储接口,虽然OTA下载中不是必须的,但可以用来存储下载状态等信息:
c复制int easyflash_init(void)
{
/* 初始化EasyFlash */
if (easyflash_init() == EF_NO_ERR) {
rt_kprintf("EasyFlash init success!\n");
} else {
rt_kprintf("EasyFlash init failed!\n");
}
return 0;
}
INIT_APP_EXPORT(easyflash_init);
6. OTA下载实现
6.1 下载流程设计
完整的OTA下载流程包括以下几个步骤:
- 建立HTTP连接
- 获取文件大小信息
- 擦除目标Flash分区
- 分块下载并写入Flash
- 校验下载完整性
6.2 核心代码实现
以下是实现HTTP下载的核心函数:
c复制static int http_ota_fw_download(const char* uri)
{
int ret = 0, resp_status;
int file_size = 0, length, total_length = 0;
rt_uint8_t *buffer_read = RT_NULL;
struct webclient_session* session = RT_NULL;
const struct fal_partition * dl_part = RT_NULL;
/* 创建webclient会话 */
session = webclient_session_create(GET_HEADER_BUFSZ);
if (!session){
rt_thread_mdelay(500);
LOG_E("创建会话失败");
ret = -RT_ERROR;
goto __exit;
}
/* 发送GET请求 */
if ((resp_status = webclient_get(session, uri)) != 200){
rt_thread_mdelay(500);
LOG_E("GET请求失败, 响应码: %d", resp_status);
ret = -RT_ERROR;
goto __exit;
}
/* 获取文件大小 */
file_size = webclient_content_length_get(session);
if (file_size <= 0){
LOG_E("无效的文件大小: %d", file_size);
ret = -RT_ERROR;
goto __exit;
}
/* 查找并擦除下载分区 */
if ((dl_part = fal_partition_find(DEFAULT_DOWNLOAD_PART_AKA)) == RT_NULL){
LOG_E("找不到分区: %s", DEFAULT_DOWNLOAD_PART_AKA);
ret = -RT_ERROR;
goto __exit;
}
LOG_I("开始擦除Flash分区: %s", dl_part->name);
if (fal_partition_erase(dl_part, 0, file_size) < 0){
LOG_E("擦除分区失败: %s", dl_part->name);
ret = -RT_ERROR;
goto __exit;
}
/* 分配下载缓冲区 */
buffer_read = web_malloc(HTTP_OTA_BUFF_LEN);
if (buffer_read == RT_NULL){
LOG_E("内存分配失败");
ret = -RT_ERROR;
goto __exit;
}
/* 分块下载并写入Flash */
do {
length = webclient_read(session, buffer_read,
file_size - total_length > HTTP_OTA_BUFF_LEN ?
HTTP_OTA_BUFF_LEN : file_size - total_length);
if (length > 0){
if (fal_partition_write(dl_part, total_length, buffer_read, length) < 0){
LOG_E("写入Flash失败");
ret = -RT_ERROR;
goto __exit;
}
total_length += length;
print_progress(total_length, file_size);
}
rt_thread_mdelay(100); // 适当延时防止阻塞其他任务
} while(total_length != file_size);
if (total_length == file_size){
LOG_I("下载完成! 文件大小: %d", file_size);
ret = RT_EOK;
}
__exit:
/* 资源释放 */
if (session) webclient_close(session);
if (buffer_read) web_free(buffer_read);
return ret;
}
6.3 进度显示实现
为了提升用户体验,我们实现了下载进度显示功能:
c复制static void print_progress(size_t cur_size, size_t total_size)
{
static unsigned char progress_sign[100 + 1];
uint8_t i, per = cur_size * 100 / total_size;
if (per > 100) per = 100;
for (i = 0; i < 100; i++){
if (i < per) progress_sign[i] = '=';
else if (per == i) progress_sign[i] = '>';
else progress_sign[i] = ' ';
}
progress_sign[sizeof(progress_sign) - 1] = '\0';
LOG_I("\033[2A"); // 光标上移两行
LOG_I("下载进度: [%s] %d%%", progress_sign, per);
}
7. 用户接口设计
7.1 MSH命令接口
为了方便测试,我们提供了MSH命令接口:
c复制void http_ota_app(uint8_t argc, char **argv)
{
if (argc < 2){
rt_kprintf("使用默认URL: " HTTP_OTA_URL1 "\n");
http_ota_fw_download(HTTP_OTA_URL1);
}
else{
rt_kprintf("使用指定URL: %s \n", argv[1]);
http_ota_fw_download(argv[1]);
}
}
MSH_CMD_EXPORT(http_ota_app, 通过HTTP下载固件);
7.2 应用程序接口
对于实际应用,我们提供了更灵活的API接口:
c复制#define HTTP_OTA_URL2 "http://www.example.com/firmware/%s"
char UserHttp_OTA[100]; // 存储动态生成的URL
void User_http_OTA_App(char mode)
{
rt_memset(UserHttp_OTA, 0, sizeof(UserHttp_OTA));
uint16_t Send_Buff_Len = 0;
if(mode == 1){
Send_Buff_Len = rt_sprintf(UserHttp_OTA, HTTP_OTA_URL2, "firmware_v1.rbl");
http_ota_fw_download(UserHttp_OTA);
}
else {
Send_Buff_Len = rt_sprintf(UserHttp_OTA, HTTP_OTA_URL2, "firmware_v2.rbl");
http_ota_fw_download(UserHttp_OTA);
}
}
8. 常见问题与解决方案
8.1 网络连接问题
问题现象:ping测试失败或HTTP连接超时
- 检查W5500硬件连接
- 确认网络参数(IP、网关、DNS)配置正确
- 验证路由器/防火墙设置是否阻止了设备访问互联网
8.2 下载中断问题
问题现象:下载过程中断或卡死
- 增加接收超时设置:
webclient_set_timeout(session, 5000) - 适当增大接收缓冲区:
#define HTTP_OTA_BUFF_LEN 1024 * 8 - 添加看门狗喂狗操作,防止系统死机
8.3 Flash写入失败
问题现象:fal_partition_write返回错误
- 确认Flash分区大小足够
- 检查Flash擦除操作是否成功
- 验证写入地址是否对齐(通常需要4字节或256字节对齐)
8.4 内存不足问题
问题现象:内存分配失败或系统崩溃
- 优化内存使用,减少不必要的缓冲区
- 使用静态内存分配替代动态分配
- 调整RT-Thread内存池大小
9. 性能优化建议
- 双缓冲技术:使用两个缓冲区交替进行网络接收和Flash写入,提高吞吐量
- 断点续传:记录已下载的字节数,支持从断点继续下载
- 压缩传输:在服务器端对固件进行压缩,设备端解压,减少传输数据量
- 差分升级:仅下载差异部分,大幅减少下载量
10. 安全注意事项
- HTTPS支持:考虑使用webclient的HTTPS功能,确保传输安全
- 固件签名:虽然本文不涉及升级部分,但实际应用中必须对下载的固件进行签名验证
- 回滚机制:设计固件回滚方案,防止升级失败导致设备变砖
- 访问控制:对OTA服务器进行访问控制,防止未授权下载
11. 测试与验证
完整的测试流程应包括:
- 小文件下载测试(<100KB)
- 大文件下载测试(接近Flash分区大小)
- 网络中断恢复测试
- 多次重复下载测试
- 不同服务器响应时间测试
测试命令示例:
bash复制# 使用默认URL测试
msh /> http_ota_app
# 使用指定URL测试
msh /> http_ota_app http://www.example.com/firmware/test.rbl
12. 扩展应用
基于本方案可以进一步实现:
- 完整的OTA升级流程(校验、跳转)
- 多固件版本管理
- 远程配置更新
- 数据采集与上报
在实际项目中,我曾遇到过因Flash写入速度慢导致下载超时的问题。解决方案是调整了接收缓冲区大小和写入块大小,找到了最佳的性能平衡点。另一个经验是,一定要在Flash写入前进行完整擦除,否则可能导致固件校验失败。
