STM32 OTA APP部分 demo流程学习

 学习依据的源文链接:STM32 OTA应用开发——通过USB实现OTA升级

前言

《STM32 OTA Bootloader部分 demo流程学习》一文中,梳理了OTA过程在Bootloader中的逻辑流程,这篇将梳理APP部分在OTA过程的处理;

需要注意的是,这份demo里面,Bootloader部分主要是进行APP跳转(无需升级)和Download分区程序覆盖写入APP分区(有升级需求)的功能,而将新APP传输到Download分区这部分工作放到了APP部分,所以主要是对这部分进行梳理;

APP的OTA功能工作原理

APP运行起来后,需要连接USB,等待上位机发送升级命令,如果收到对应的升级命令,则进入下载模式;demo当中采用USB连接PC,实际实现可能会使用UART替代USB;

考虑到适用性,USB传输固件的方式采用Ymodem协议,可以适配很多上位机的终端程序(Xshell,secureCRT等等),当然,也可以自定义协议;

如上图是APP部分处理OTA过程的流程图,后面重点分析demo是如何通过Ymodem协议传输固件的,以及Ymodem过程的状态机;

YModem协议简介

Ymodem协议是一种数据传输协议,可以用于在计算机之间传输文件。它是Xmodem协议的改进版,支持传输更大的文件和更高的传输速度。Ymodem协议将文件分成128字节的块,每个块都有一个序号和校验号,以确保数据的完整性。传输过程中,接收方会发送一个ACK信号来确认每个块是否已经正确接收。如果出现错误,发送方会重新发送该块。Ymodem协议还支持批处理传输,可以将多个文件打包成一个压缩文件进行传输。

这样看这个协议有些抽象,举个实际通信的例子,例如MCU通过UART与PC上位机终端对接,MCU接收数据,PC发送数据,两方的动作:

  1. MCU向PC发送一个C字符,请求文件传输。
  2. PC收到启动命令后,通过Xshell等终端工具集成的Ymodem协议向MCU发送数据包。
  3. PC将发送的文件分为若干个带有SOH或STX的数据包,分次向MCU发送。
  4. MCU每收到一个SOH或STX的数据包,需要向PC端返回ACK。
  5. PC端要收到ACK后才会发送下一个数据包,直到发送完最后一个数据块,收到MCU的ACK后,向MCU发送EOT表示发送结束。
  6. MCU收到EOT后需要返回一个ACK。

需要注意的是,C字符,ACK,EOT等信息,在Ymodem协议当中都是规定的一个转义字符,具有特定含义,以下列举协议当中的转义字符:

  1.  SOH(Start of Header):表示一个数据块的开始,其值为0x01。
  2.  STX(Start of Text):表示一个数据块的开始,其值为0x02,与SOH的区别在于STX支持1024字节的数据块,而SOH只支持128字节。
  3.  EOT(End of Transmission):表示文件传输结束,其值为0x04。
  4.  ACK(Acknowledgement):表示接收方已经成功接收到一个数据块,其值为0x06。
  5.  NAK(Negative Acknowledgement):表示接收方接收数据出现错误,需要重新发送数据,其值为0x15。
  6.  CAN(Cancel):表示终止文件传输,其值为0x18。
  7.  CRC(Cyclic Redundancy Check):表示数据块的校验和,其值为两个字节的校验和值。

上述的字符值都是与ASCII码对应的;

源码解析

main流程

demo当中的main.c除开初始化系统时钟,USB设备,打印APP信息这些常规操作以外,主要是执行了ymodem_init()和循环执行ymodem_handle();

其中,ymodem_init()主要是初始化了定时器和一个接收队列(ymodem.c line 216),定时器和接收队列在后面的接收数据流程中会用到;

ymodem_handle()当中,针对三个状态做处理:

START_PROGRAM:没有收到升级命令,继续执行原本程序

UPDATE_PROGRAM:收到升级命令,通过USB向上位机发送一个字符C,表示可以进行Ymodem传输;

UPDATE_SUCCESS:升级完成后,重置Setting区域的标志,然后重启MCU,后面又会从Bootloader启动,在Bootloader中检测到Setting区域的标志变化,再执行新旧固件的擦除搬运(Bootloader这部分的处理流程见《STM32 OTA Bootloader部分 demo流程学习》源码解析中的第5点);

接收数据流程

  1. 首先是usb_rxdata_handle()接口和TIM3_IRQHandler()接口,分别是USB中断接收信息和TIM3定时器中断执行函数;前者是用来接收来自PC机的信息和固件数据,接收到之后,会使能TIM3的定时,到达定时后产生UPDATE定时中断,跳转到后者函数再执行之后的数据处理操作;
  2. 在TIM3_IRQHandler()接口当中,检查接收队列当中是否有信息(ymodem.c line 268),如果有,就代表上位机发送了消息过来,之后再将接收队列(rx_queue)中的内容出队到接收缓存(recvBuf.data)当中
  3. 再根据接收到的数据长度,进入到ymodem_recv()接口当中进行处理;

注:之所以通过队列---定时器这种方式来接收上位机的数据,是为了给接收数据提供一层缓存,可以保证接收数据的顺序性和可靠性;

YModem数据处理流程

因为YModem协议规定,发送来的数据包第一个byte是特殊转义字符SOH或者STX,所以在ymodem_recv()接口中对接收到的第一个byte进行判断就能知道数据发送的步骤;

  1. 在demo中,原作者设定为,字符‘1’作为升级命令,可以看到在ymodem_recv()接口中对字符‘1’的处理(ymodem.c line 128)
  2. 接收到升级命令字符‘1’后,将状态置为UPDATE_PROGRAM,这样就可以跳转到main流程中的UPDATE_PROGRAM状态处理过程,向上位机每隔一秒发送一个字符C,表示可以进行Ymodem传输;
  3. PC上位机收到字符C后就可以向MCU发送固件数据包了,这个时候ymodem.status仍是初始状态0,还是进入case 0的流程,代表第一个数据包的处理,收到SOH后确定是Ymodem的数据包,擦除0x08012000地址为起始的Download分区数据,然后向上位机发送ACK和字符C,ymodem.status自增
  4. 进入case 1的流程,接收固件数据包的数据块,并且将SOH或STX的数据块写入Download分区,一旦收到EOT,表示固件已经传输完成后,MCU向PC返回一个NACK要求重发,ymodem.status自增;
  5. 进入case 2的流程,确认EOT后,返回ACK和C;
  6. 根据返回的C收到PC的最后一个SOH,返回ACK,标志着新固件包的接收完毕,ymodem_recv状态机复位;将状态置为UPDATE_SUCCESS,跳转到main流程中的UPDATE_SUCCESS状态处理过程;

注:demo这里在收到EOT之后返回ACK的同时,又发送了一个C,启动了下一次传输,收到的SOH属于下一次传输的头数据包,这里的处理流程存疑;

附:

ymodem.c

#include "ymodem.h"
#include "flash.h"
#include "SysTick.h"
#include "hw_config.h"

ymodem_t ymodem = {START_PROGRAM, 0, 0, APP_SECTOR_ADDR, 0, {0}};
download_buf_t recvBuf;
seq_queue_t rx_queue;

// 初始化queue_initiate(Q)
void queue_initiate(seq_queue_t *Q)
{
    Q->rear = 0;        
    Q->front = 0;
    Q->count = 0;
}

// 非空否queue_not_empty(Q)
//判断循环队列Q非空否,非空则返回1,否则返回0
int queue_not_empty(seq_queue_t *Q)
{
    if(Q->count != 0)
        return 1;
    else 
        return 0;
}

// 入队列queue_append(Q, x)
//把数据元素值x插入顺序循环队列Q的队尾,成功返回1,失败返回0
int queue_append(seq_queue_t *Q, uint8_t x)
{
    if(Q->count > 0 && Q->rear == Q->front)
    {    
        printf("queue is full ! \n");
        return 0;
    }
    else
    {    
        Q->queue[Q->rear] = x;
        Q->rear = (Q->rear + 1) % MAX_QUEUE_SIZE;
        Q->count ++;
        return 1;
    }
}

// 出队列  queue_delete(Q, d)
//删除顺序循环队列Q的队头元素并赋值给d,成功则返回1,失败返回0
int queue_delete(seq_queue_t *Q, uint8_t *d)
{
    if(Q->count == 0)
    {    
        // printf("queue is empty! \n");
        return 0;
    }
    else
    {    *d = Q->queue[Q->front];
        Q->front = (Q->front + 1) % MAX_QUEUE_SIZE;
        Q->count--;
        return 1;
    }
}

// 取队头数据元素 queue_get(Q, d)
int queue_get(seq_queue_t Q, uint8_t *d)
{
    if(Q.count == 0)
    {
        // printf("queue is empty! \n");
        return 0;
    }
    else
    {
        *d = Q.queue[Q.front];
        return 1;
    }
}

void ymodem_ack(void) 
{
    usb_printf("%c\r", YMODEM_ACK);
}

void ymodem_nack(void) 
{
    usb_printf("%c\r", YMODEM_NAK);
 
}

void ymodem_c(void) 
{
    usb_printf("%c\r", YMODEM_C);
}

void set_ymodem_status(process_status process) 
{
    ymodem.process = process;
}

process_status get_ymodem_status(void) 
{
    process_status process = ymodem.process;
    return process;
}

void ymodem_start(ymodem_callback cb) 
{
    if (ymodem.status == 0) 
    {
        ymodem.cb = cb;
    }
}

void ymodem_recv(download_buf_t *p) 
{
    uint8_t type = p->data[0];
    switch (ymodem.status) 
    {
        case 0:
            if (type == YMODEM_SOH) 
            {
                ymodem.process = BUSY;
                ymodem.addr = APP_SECTOR_ADDR;
                mcu_flash_erase(ymodem.addr, ERASE_SECTORS);
                ymodem_ack();
                ymodem_c();
                ymodem.status++;
            }
            else if (type == '1') 
            {
                printf("enter update mode\r\n");
                ymodem.process = UPDATE_PROGRAM;
            }
            break;
        case 1:
            if (type == YMODEM_SOH || type == YMODEM_STX) 
            {
                if (type == YMODEM_SOH) 
                {
                    mcu_flash_write(ymodem.addr, &p->data[3], 128);
                    ymodem.addr += 128;
                }
                else 
                {
                    mcu_flash_write(ymodem.addr, &p->data[3], 1024);
                    ymodem.addr += 1024;
                }
                ymodem_ack();
            }
            else if (type == YMODEM_EOT) 
            {
                ymodem_nack();
                ymodem.status++;
            }
            else 
            {
                ymodem.status = 0;
            }
            break;
        case 2:
            if (type == YMODEM_EOT) 
            {
                ymodem_ack();
                ymodem_c();
                ymodem.status++;
            }
            break;
        case 3:
            if (type == YMODEM_SOH) 
            {
                ymodem_ack();
                ymodem.status = 0;
                ymodem.process = UPDATE_SUCCESS;
            }
    }
    p->len = 0;
}

void system_reboot(void)
{
    __set_FAULTMASK(1);//关闭总中断
    NVIC_SystemReset();//请求单片机重启
}

void ymodem_handle(void)
{
    uint8_t boot_state;
    process_status process;

    process = get_ymodem_status();
    switch (process) 
    {
        case START_PROGRAM:
            break;
        case UPDATE_PROGRAM:
            usb_printf("C\r\n");
            delay_ms(1000);
            break;
        case UPDATE_SUCCESS:
            boot_state = UPDATE_PROGRAM_STATE;
            mcu_flash_erase(SETTING_BOOT_STATE, 1);
            mcu_flash_write(SETTING_BOOT_STATE, &boot_state, 1);
            printf("firmware download success\r\n");

            // mcu_flash_read(SETTING_BOOT_STATE, &boot_state, 1);
            // printf("boot_state:%d\n", boot_state);
            // PrintTip();
            printf("system reboot...\r\n");
            delay_ms(2000);
            system_reboot();
            break;
        default:
            break;
    }
}

void ymodem_init(void)
{
    timer_init();
    queue_initiate(&rx_queue);
}

void usb_rxdata_handle(uint8_t *buf, uint16_t len)
{
    uint16_t i;
    for(i = 0; i < len; i++)
    {
        queue_append(&rx_queue, buf[i]);
        // printf("%02X ", buf[t]);
    }
    TIM3->CNT = 0;
    TIM_Cmd(TIM3, ENABLE);    
}

void timer_init(void) 
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
    
    //定时器TIM3初始化
    TIM_TimeBaseStructure.TIM_Period = 999; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 (9999+1)*1us=10ms
    TIM_TimeBaseStructure.TIM_Prescaler = 71; //设置用来作为TIMx时钟频率除数的预分频值 72M/(71+1)=1MHz 1us
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
    TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE); //使能指定的TIM3中断,允许更新中断

    //中断优先级NVIC设置
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器

    TIM_Cmd(TIM3, ENABLE);  //使能TIMx    
}

void TIM3_IRQHandler(void) 
{
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) 
    {
        int result = 1;
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
        TIM_Cmd(TIM3, DISABLE);
        
        result = queue_not_empty(&rx_queue);
        if(result == 1)
        {
            uint8_t i = 0;
            recvBuf.len = 0;
            do
            {
                result = queue_delete(&rx_queue, &recvBuf.data[i]);
                if(result == 1)
                {
                    recvBuf.len ++;
                    i ++;
                }
            }
            while(result);

            for (i = 0; i < recvBuf.len; i++)
            {
                ymodem_recv(&recvBuf);
            }
        }
    }
}

ymodem.h

#ifndef __YMODEM_H
#define __YMODEM_H

#include "stm32f10x_conf.h"
#include "flash.h"
#include "usart.h"	 

#define YMODEM_SOH		0x01
#define YMODEM_STX		0x02
#define YMODEM_EOT		0x04
#define YMODEM_ACK		0x06
#define YMODEM_NAK		0x15
#define YMODEM_CA		0x18
#define YMODEM_C		0x43

#define FLASH_SECTOR_SIZE       1024
#define FLASH_SECTOR_NUM        128    // 128K
#define FLASH_START_ADDR        ((uint32_t)0x08000000)
#define FLASH_END_ADDR          ((uint32_t)(0x08000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))

#define APP_SECTOR_ADDR         0x08012000
#define ERASE_SECTORS	        ((FLASH_END_ADDR - APP_SECTOR_ADDR) / FLASH_SECTOR_SIZE)   // 128k - 16 - 56 = 56k

#define MAX_QUEUE_SIZE            1200

#define SETTING_BOOT_STATE      0x08003000
#define UPDATE_PROGRAM_STATE    2
#define UPDATE_SUCCESS_STATE    3

typedef enum {
	NONE,
	BUSY,
	START_PROGRAM,
	UPDATE_PROGRAM,
	UPDATE_SUCCESS
} process_status;

typedef void (*ymodem_callback)(process_status);

typedef struct {
	process_status process;
	uint8_t status;
	uint8_t id;
	uint32_t addr;
	uint32_t filesize;
	char filename[32];
	ymodem_callback cb;
} ymodem_t;

//顺序循环队列的结构体定义如下:
typedef struct
{
	uint8_t queue[MAX_QUEUE_SIZE];
	int rear;  //队尾指针
	int front;  //队头指针
	int count;  //计数器
} seq_queue_t; 

typedef  void (*jump_callback)(void);

typedef struct 
{
	uint8_t data[1200];
	uint16_t len;
} download_buf_t;

extern seq_queue_t rx_queue;
extern download_buf_t down_buf;

void queue_initiate(seq_queue_t *Q);
int queue_not_empty(seq_queue_t *Q);
int queue_delete(seq_queue_t *Q, uint8_t *d);

void set_ymodem_status(process_status process);
process_status get_ymodem_status(void);
void ymodem_start(ymodem_callback cb);
void ymodem_recv(download_buf_t *p);
void ymodem_init(void);
void ymodem_handle(void);
void timer_init(void);

#endif

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值