基于RT-Thread实现4G STM32 OTA升级

基于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 
#include 
#include 
#include 
#include 
#include 


#define DBG_TAG "ec200s"
#define DBG_LVL DBG_LOG
#include 

/* 每次烧录的大小为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;

基于RT-Thread实现4G STM32 OTA升级_第1张图片

有问题,可以找我。

你可能感兴趣的:(物联网,RTthread,stm32,单片机)