基于RT-Thread实现4G STM32 OTA升级
硬件:
STM32F429BIT6开发板
EC200S开发板
USB-TTL调试器
这里采用的HTTP从服务器获取BIN文件,再保存在4G模块内部FLASH的方法,后面会做程序切片从服务器接收的。
使用msh命令行来测试,总共四个函数
- 一个初始化EC200S
- 一个从服务器下载bin
- 一个烧录到flash
- 一个跳转函数
代码还有很多改进的地方,目前经测试是可以使用,可能还有潜在bug。
芯片直接使用内部FLASH来划分区域升级,使用FAL来抽象FLASH,后期可以很方便的使用spi flash来存储升级程序的文件。
平台使用自己搭建的,目前用的NodeRED来测试,后面弄成前后端分离的。
程序接受bin数据使用URC方式来接受bin文件数据,更加稳定。
下面代码使用的msh来调试测试的
**注意:**给shell,at_client和串口要大缓存
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-01-08 zhengshijian the first version
*/
#ifndef APPLICATIONS_EC200S_C_
#define APPLICATIONS_EC200S_C_
#include "ec200s.h"
#include <stdlib.h>
#include <string.h>
#include <rtthread.h>
#include <at_device.h>
#include <netdev.h>
#include <fal.h>
#define DBG_TAG "ec200s"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
/* 每次烧录的大小为4k */
#define FLASH_SECTOR_SIZE 4096
/* HTTP服务器链接 */
#define HTTP_FILE_URL "http://zhengshijian.cn:1880/test"
static int ec200s_check_ready(void)
{
at_response_t resp = RT_NULL;
int result = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -2;
}
result = at_exec_cmd(resp, "AT");
if (result != RT_EOK) {
LOG_E("Failed Check Ready");
}
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
static int ec200s_echo_close(void)
{
at_response_t resp = RT_NULL;
int result = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -2;
}
result = at_exec_cmd(resp, "ATE0");
if (result != RT_EOK) {
LOG_E("Failed Close Echo");
}
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
static int ec200s_sim_card_check(void)
{
at_response_t resp = RT_NULL;
int result = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
result = at_exec_cmd(resp, "AT+CPIN?");
if (result != RT_EOK){
LOG_E("Failed Check CPIN");
goto __exit;
}else {
if(strstr(at_resp_get_line(resp, 2), "READY")){
LOG_I("CPIN READY");
}else {
LOG_E("CSQ Parse error");
result = -1;
}
}
__exit:
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
static int ec200s_signal_quality_check(void)
{
at_response_t resp = RT_NULL;
int rssi, ber;
int result = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
result = at_exec_cmd(resp, "AT+CSQ");
if (result != RT_EOK){
LOG_E("Failed Check CSQ");
goto __exit;
}
{
/* 自定义数据解析表达式 ,用于解析两双引号之间字符串信息 */
const char * resp_expr = "+CSQ: %d,%d";
if (at_resp_parse_line_args(resp, 2, resp_expr, &rssi, &ber))
{
LOG_I("CSQ : %d %d", rssi, ber);
if (rssi == 99) {
result = -1;
}
}
else
{
LOG_E("CSQ Parse error");
result = -1;
goto __exit;
}
}
__exit:
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
static int ec200s_gsm_network_check(void)
{
at_response_t resp = RT_NULL;
int result = 0;
int try = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -1;
}
while (try++ < 10) {
/* 查询信号强度 */
result = at_exec_cmd(resp, "AT+CREG?");
if (result != RT_EOK){
LOG_E("Failed Check GSM Network");
goto __exit;
}
{
int n, stat;
/* 自定义数据解析表达式 ,用于解析两双引号之间字符串信息 */
const char * resp_expr = "+CREG: %d,%d";
LOG_D(" Parse arguments");
/* 解析响应数据中第一行数据 */
if (at_resp_parse_line_args(resp, 2, resp_expr, &n, &stat))
{
LOG_I("CREG : %d %d", n, stat);
if(stat != 1){
LOG_E("CREG stat error");
}else goto __exit;
}
else
{
LOG_E("CREG Parse error");
result = -1;
goto __exit;
}
}
rt_thread_delay(2000);
}
__exit:
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
static int ec200s_gprs_network_check(void)
{
at_response_t resp = RT_NULL;
int result = 0;
int try = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -1;
}
while (try++ < 10) {
/* 查询信号强度 */
result = at_exec_cmd(resp, "AT+CGREG?");
if (result != RT_EOK){
LOG_E("Failed Check CGREG Network");
goto __exit;
}
{
int n, stat;
/* 自定义数据解析表达式 ,用于解析两双引号之间字符串信息 */
const char * resp_expr = "+CGREG: %d,%d";
LOG_D(" Parse arguments");
/* 解析响应数据中第一行数据 */
if (at_resp_parse_line_args(resp, 2, resp_expr, &n, &stat))
{
LOG_I("CGREG : %d %d", n, stat);
if(stat != 1){
LOG_E("CGREG stat error");
}else goto __exit;
}
else
{
LOG_E("CGREG Parse error");
result = -1;
goto __exit;
}
}
rt_thread_delay(2000);
}
__exit:
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
static int ec200s_close_apn(void)
{
at_response_t resp = RT_NULL;
int result = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -1;
}
result = at_exec_cmd(resp, "AT+QIDEACT=1");
if (result != RT_EOK){
LOG_E("Failed Close APN");
}
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
static int ec200s_set_apn(void)
{
at_response_t resp = RT_NULL;
int result = 0;
resp = at_create_resp(32, 0, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -1;
}
result = at_exec_cmd(resp, "AT+QICSGP=1,1,\"CMNET\"\r\n");
if (result != RT_EOK){
LOG_E("Failed Set PDP");
goto __exit;
}
result = at_exec_cmd(resp, "AT+QIACT=1\r\n");
if (result != RT_EOK){
LOG_E("Failed Set APN");
}
__exit:
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
/*
*********************************************************************************************************
* 函 数 名: ec200s_init
* 功能说明: EC200S 4G模块初始化
* 形 参: 无
* 返 回 值: 0 成功 -1失败
*********************************************************************************************************
*/
static int ec200s_init(void)
{
LOG_I("Init ec200s ...");
if (ec200s_check_ready() != 0) {
LOG_D("wait module ready timeout, please check your module");
return -1;
}
if (ec200s_echo_close() != 0) {
LOG_D("echo close failed,please check your module");
return -1;
}
if(ec200s_sim_card_check() != 0) {
LOG_D("sim card check failed,please insert your card");
return -1;
}
if (ec200s_signal_quality_check() != 0) {
LOG_D("signal quality check status failed");
return -1;
}
if(ec200s_gsm_network_check() != 0) {
LOG_D("GSM network register status check fail");
return -1;
}
if(ec200s_gprs_network_check() != 0) {
LOG_D("GPRS network register status check fail");
return -1;
}
if(ec200s_close_apn() != 0) {
LOG_D("close apn failed");
return -1;
}
if (ec200s_set_apn() != 0) {
LOG_D("apn set FAILED");
return -1;
}
LOG_I("Init ec200s ok");
return 0;
}
MSH_CMD_EXPORT(ec200s_init, EC200 Init Test);
/*
*********************************************************************************************************
* 函 数 名: http_download_ota_file
* 功能说明: 从服务器下载升级文件,并保存在4G模块中
* 形 参: 无
* 返 回 值: 0 成功 1其他失败
*********************************************************************************************************
*/
int http_download_ota_file(void)
{
at_response_t resp = RT_NULL;
int result = 0;
int http_err = 0, http_rspcode = 0, http_resplength = 0;
/* 判断EC200x是否有网 */
if(!netdev_is_internet_up(netdev_get_by_name("ec200x"))){
LOG_I("network error");
return -1;
}
/* != 0: the response data will return when received setting lines number data */
resp = at_create_resp(512, 2, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -2;
}
/* AT+QHTTPURL=返回的是CONNECT, 可以使用URC来实现,也可以改变line_num来判断CONNECT */
result = at_exec_cmd(resp, "AT+QHTTPURL=%d,80", strlen(HTTP_FILE_URL));
if (result != RT_EOK || !at_resp_get_line_by_kw(resp, "CONNECT")){
LOG_E("Failed Set Url");
goto __exit;
}
/* = 0: the response data will auto return when received 'OK' or 'ERROR' */
resp->line_num = 0;
/* 设置URL */
result = at_exec_cmd(resp, HTTP_FILE_URL);
if (result != RT_EOK){
LOG_E("Failed Set Get Url = %d", result);
goto __exit;
}
/* 等待GET请求5s,并修改返回的行数,可改成URC数据方式 */
resp->line_num = 4;
resp->timeout = 5000;
result = at_exec_cmd(resp, "AT+QHTTPGET=80");
if (result != RT_EOK){
LOG_E("Failed Set Get");
goto __exit;
}
/* 判断HTTP返回参数 */
if(at_resp_parse_line_args(resp, 4, "+QHTTPGET: %d,%d,%d", &http_err, &http_rspcode, &http_resplength) != 3){
LOG_E("Failed Prase Get Result ");
result = -3;
goto __exit;
}
/* 需要等待get请求返回并判断 */
if(http_err != 0 || http_rspcode != 200 || http_resplength < 2048){
LOG_E("Get Response Error");
LOG_E("http_err = %d http_rspcode = %d http_resplength = %d", http_err, http_rspcode, http_resplength);
result = -4;
goto __exit;
}
/* 保存BIN文件到4G模块内部 */
result = at_exec_cmd(resp, "AT+QHTTPREADFILE=\"UFS:ota.bin\"");
if (result != RT_EOK){
LOG_E("Failed Save File");
goto __exit;
}else {
LOG_I("DOWNLOAD SUCCESSFUL");
}
__exit:
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
}
return result;
}
MSH_CMD_EXPORT(http_download_ota_file, download bin file);
/* OTA全局变量 */
static rt_mailbox_t ota_mb;
static rt_sem_t ota_sem;
static uint8_t recv_buf[FLASH_SECTOR_SIZE];
/*
*********************************************************************************************************
* 函 数 名: urc_recv_func
* 功能说明: 回调函数定义,接收对应下发的URC数据
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void urc_recv_func(struct at_client *client, const char *data, rt_size_t size)
{
int length = 0;
if (rt_mb_recv(ota_mb, (rt_ubase_t *)&length, RT_WAITING_FOREVER) == RT_EOK){
LOG_I("length = %d", length);
/* 接收指定长度数据 */
if(at_client_recv((char*)recv_buf, length, 5000) != 0){
LOG_I("RECV OK");
}else {
LOG_I("RECV ERROR");
}
/*完成后释放信号量*/
rt_sem_release(ota_sem);
}
}
/*
*********************************************************************************************************
* 函 数 名: write_bin_to_flash
* 功能说明: 下载APP到Flash
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
int write_bin_to_flash(void)
{
at_response_t resp = RT_NULL;
const struct fal_partition *ota_fal = NULL;
int result = 0;
int file_length = 0;
int division = 0, remainder = 0;
int i = 0;
ota_sem = rt_sem_create("ota_sem", 0, RT_IPC_FLAG_FIFO);
ota_mb = rt_mb_create("ota_mb", 100, RT_IPC_FLAG_FIFO);
resp = at_create_resp(1200, 0, rt_tick_from_millisecond(300));
if (resp == RT_NULL)
{
LOG_E("No memory for response structure!");
return -2;
}
/* 列出4G模块内部文件列表,并获取文件大小 */
result = at_exec_cmd(resp, "AT+QFLST=\"*\"");
if (result != RT_EOK){
LOG_E("Failed Check File");
result = -3;
goto __exit;
}
if(at_resp_parse_line_args(resp, 2, "+QFLST: \"UFS:ota.bin\",%d", &file_length) != 1){
LOG_E("Failed Prase QFLIST Result ");
result = -4;
goto __exit;
}
/* 擦除所需用到的扇区 */
ota_fal = fal_partition_find("app2");
if (ota_fal != NULL) {
if(fal_partition_erase(ota_fal, 0, file_length) == -1){
LOG_E("Failed Erase app2_part Flash ");
result = -5;
goto __exit;
}
}
/* 计算循环次数 */
division = file_length / FLASH_SECTOR_SIZE;
remainder = file_length % FLASH_SECTOR_SIZE;
LOG_I("file_length = %d division = %d remainder = %d ",file_length, division, remainder);
/* 打开文件 */
result = at_exec_cmd(resp, "AT+QFOPEN=\"ota.bin\"");
if (result != RT_EOK){
LOG_E("Failed Open File");
goto __exit;
}
/* 未采用URC方法,增加超时时间,不稳定 */
// resp->timeout = 2000;
// result = at_exec_cmd(resp, "AT+QFREAD=1,1024");
// if (result != RT_EOK){
// LOG_E("Failed Read File");
// goto __exit;
// }
/* 采用URC方法,接受固定的长度最稳定 */
static struct at_urc urc_table[] = {
{"CONNECT", "\r\n", urc_recv_func},
};
at_set_urc_table(urc_table, sizeof(urc_table) / sizeof(urc_table[0]));
for (i = 0; i < division; ++i) {
/* 移动文件读指针 */
result = at_exec_cmd(resp, "AT+QFSEEK=1,%d,0", i*FLASH_SECTOR_SIZE);
if (result != RT_EOK){
LOG_E("Failed Move Pointer");
goto __close;
}
/* 发送邮件 下载的字节数 */
rt_mb_send(ota_mb, (rt_uint32_t)FLASH_SECTOR_SIZE);
at_exec_cmd(resp, "AT+QFREAD=1,%d", FLASH_SECTOR_SIZE);
/* 等待URC数据接收完 */
rt_sem_take(ota_sem, RT_WAITING_FOREVER);
if(fal_partition_write(ota_fal, i*FLASH_SECTOR_SIZE, recv_buf, FLASH_SECTOR_SIZE))LOG_I("write done to addr (%d), size %d", i*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
else LOG_E("write done to addr (%d), size %d", i*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
}
/* 读最后一部分的数据 */
if(remainder != 0)
{
/* 移动文件读指针 */
result = at_exec_cmd(resp, "AT+QFSEEK=1,%d,0", i*FLASH_SECTOR_SIZE);
if (result != RT_EOK){
LOG_E("Failed Move Pointer");
goto __close;
}
/* 发送邮件 下载的字节数 */
rt_mb_send(ota_mb, (rt_uint32_t)remainder);
at_exec_cmd(resp, "AT+QFREAD=1,%d", remainder);
/* 等待URC数据接收完 */
rt_sem_take(ota_sem, RT_WAITING_FOREVER);
if(fal_partition_write(ota_fal, i*FLASH_SECTOR_SIZE, recv_buf, remainder))LOG_I("write done to addr (%d), size %d", i*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
else LOG_E("write done to addr (%d), size %d", i*FLASH_SECTOR_SIZE, remainder);
}
__close:
result = at_exec_cmd(resp, "AT+QFCLOSE=1");
if (result != RT_EOK){
LOG_E("Failed Close File");
}
__exit:
if(resp)
{
/* 删除 resp 结构体 */
at_delete_resp(resp);
rt_sem_delete(ota_sem);
rt_mb_delete(ota_mb);
}
return result;
}
MSH_CMD_EXPORT(write_bin_to_flash, write flash);
/*
*********************************************************************************************************
* 函 数 名: JumpToBootloader
* 功能说明: 跳转到系统BootLoader
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void JumpToApplication(void)
{
uint32_t i=0;
register rt_ubase_t temp;
__IO uint32_t ApplicationAddr = 0x08120000;
void (*SysMemBootJump)(void); /* 声明一个函数指针 */
/* 关闭全局中断 */
temp = rt_hw_interrupt_disable();
/* 关闭滴答定时器,复位到默认值 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/* 设置所有时钟到默认状态,使用HSI时钟 */
HAL_RCC_DeInit();
/* 关闭所有中断,清除所有中断挂起标志 */
for (i = 0; i < 8; i++)
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
/* 使能全局中断 */
rt_hw_interrupt_enable(temp);
/* 跳转到系统BootLoader,首地址是MSP,地址+4是复位中断服务程序地址 */
SysMemBootJump = (void (*)(void)) (*((uint32_t *) (ApplicationAddr + 4)));
/* 设置朱堆栈指针 */
__set_MSP(*(uint32_t *)ApplicationAddr);
/* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
__set_CONTROL(0);
/* 跳转到系统BootLoader */
SysMemBootJump();
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
while (1);
}
MSH_CMD_EXPORT(JumpToApplication, JumpToApplication);
#endif /* APPLICATIONS_EC200S_C_ */
使用前at_client需要初始化,我用的串口3;
有问题,可以找我。