1531|1

208

帖子

0

TA的资源

纯净的硅(初级)

楼主
 

[极海M3内核 APM32E103VET6S MINI开发板]06.基于Xmodem实现IAP在线升级功能 [复制链接]

 

APM32E103VET6S内置了512KB的FLASH空间,每一页的大小为2KB,我们通过USART接口实现Xmodem传输协议与PC端的软件进行数据传输,再结合对MCU FLASH的读、写、擦除等操作实现IAP在线程序升级的功能,具体实现步骤如下:

Bootloader端:

  • 配置Flash Download设置如下图所示:

  • 实现消息队列,作为存储Xmodem的数据缓存:
/* Private variables ---------------------------------------------------------*/
static QUEUE_InitTypeDef QUEUE[QUEUE_NUMBER];


/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/


/*******************************************************************************
 * [url=home.php?mod=space&uid=159083]@brief[/url] * @param       
 * @retval      
 * [url=home.php?mod=space&uid=1020061]@attention[/url] *******************************************************************************/
void QUEUE_INIT(uint8_t index)
{
    QUEUE[index].head = 0;
    QUEUE[index].tail = 0;

    memset(QUEUE[index].data, 0, sizeof(QUEUE[index].data));
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
uint8_t QUEUE_EMPTY(uint8_t index)
{
    if(QUEUE[index].head == QUEUE[index].tail)  return 1;
    else                                        return 0;
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
uint8_t QUEUE_READ(uint8_t index)
{
    uint8_t data = QUEUE[index].data[QUEUE[index].head++];
    QUEUE[index].head %= QUEUE_SIZE;          return data;
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void QUEUE_WRITE(uint8_t index, uint8_t data)
{
    QUEUE[index].data[QUEUE[index].tail++] = data;
    QUEUE[index].tail %= QUEUE_SIZE;
}
  • Xmodem接口相关的初始化和操作(USART、FLASH):
/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void Xmodem_USART_SendData(uint8_t Data)
{
    if(Xmodem_Running == 1)
    {
        USART_TxData(USART1, Data);
        while(USART_ReadStatusFlag(USART1, USART_FLAG_TXBE) == RESET);
    }
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void Xmodem_FLASH_ErasePage(uint32_t Page)
{
    FMC_Unlock();

    FMC_ClearStatusFlag((FMC_FLAG_T)(FMC_FLAG_OC | FMC_FLAG_PE | FMC_FLAG_WPE));

    FMC_ErasePage(FLASH_PAGE_SIZE * Page);

    FMC_ClearStatusFlag(FMC_FLAG_OC);

    FMC_Lock();
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void Xmodem_FLASH_PageProgram(uint32_t Address, uint8_t *Buffer, uint32_t Length)
{
    uint16_t *Data = (uint16_t *)Buffer;

    FMC_Unlock();

    FMC_ClearStatusFlag((FMC_FLAG_T)(FMC_FLAG_OC | FMC_FLAG_PE | FMC_FLAG_WPE));

    for(uint32_t i = 0; i < Length; i+=2)
    {
        FMC_ProgramHalfWord(Address + i, *Data);

        FMC_ClearStatusFlag(FMC_FLAG_OC);

        if(*(volatile uint16_t *)(Address + i) != *Data)
        {
            while(1)
            {
                GPIO_WriteBitValue(GPIOB, GPIO_PIN_8, BIT_RESET);
                GPIO_WriteBitValue(GPIOB, GPIO_PIN_9, BIT_RESET);
            }
        }

        Data++;
    }

    FMC_Lock();
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void Xmodem_Init(void)
{
    GPIO_Config_T  GPIO_ConfigStruct;
    USART_Config_T USART_ConfigStruct;

    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART1);

    USART_ConfigStructInit(&USART_ConfigStruct);
    USART_ConfigStruct.baudRate     = 115200;
    USART_ConfigStruct.wordLength   = USART_WORD_LEN_8B;
    USART_ConfigStruct.stopBits     = USART_STOP_BIT_1;
    USART_ConfigStruct.parity       = USART_PARITY_NONE;
    USART_ConfigStruct.mode         = USART_MODE_TX_RX;
    USART_ConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
    USART_Config(USART1, &USART_ConfigStruct);

    USART_EnableInterrupt(USART1, USART_INT_RXBNE);
    NVIC_EnableIRQRequest(USART1_IRQn, 0, 1);

    USART_Enable(USART1);

    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);

    GPIO_ConfigStructInit(&GPIO_ConfigStruct);
    GPIO_ConfigStruct.pin   = GPIO_PIN_9;
    GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
    GPIO_ConfigStruct.mode  = GPIO_MODE_AF_PP;
    GPIO_Config(GPIOA, &GPIO_ConfigStruct);

    GPIO_ConfigStructInit(&GPIO_ConfigStruct);
    GPIO_ConfigStruct.pin   = GPIO_PIN_10;
    GPIO_ConfigStruct.mode  = GPIO_MODE_IN_FLOATING;
    GPIO_Config(GPIOA, &GPIO_ConfigStruct);
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void USART1_IRQHandler(void)
{
    if(USART_ReadIntFlag(USART1, USART_INT_RXBNE) == SET)
    {
        if(Xmodem_Running == 0)
        {
            printf("%c", USART_RxData(USART1));
        }
        else
        {
            QUEUE_WRITE(QUEUE_XMODEM_IDX, USART_RxData(USART1));
        }
    }
}


/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
int fputc(int ch, FILE *f)
{
    if(Xmodem_Running == 0)
    {
        USART_TxData(USART1, (uint8_t)ch);
        while(USART_ReadStatusFlag(USART1, USART_FLAG_TXBE) == RESET);
    }

    return ch;
}
  • Xmodem传输协议实现部分:
/*******************************************************************************
 * @brief       对Xmodem接收到的数据包进行CRC校验数据值的计算
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
uint16_t Xmodem_CalcCheckCRC(uint8_t *Buffer, uint16_t Length)
{
    uint32_t Result = 0;

    Length += 2;

    while(Length--)
    {
        uint8_t Value = (Length < 2) ? 0 : *Buffer++;

        for(uint8_t j = 0; j < 8; j++)
        {
            Result <<= 1;

            if(Value  & 0x00080) Result += 0x0001;

            Value  <<= 1;

            if(Result & 0x10000) Result ^= 0x1021;
        }
    }

    Result &= 0xFFFF;

    printf("\r\n\r\nCheck CRC:0x%04x\r\n\r\n", Result);

    return Result;
}


/*******************************************************************************
 * @brief       对Xmodem接收到的数据包进行累加和校验数据值的计算
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
uint8_t Xmodem_CalcCheckSum(uint8_t *Buffer, uint16_t Length)
{
    uint16_t Result = 0;

    while(Length--)
    {
        Result += *Buffer++;
    }

    Result &= 0xFF;

    printf("\r\n\r\nCheck Sum:0x%02x\r\n\r\n", Result);

    return Result;
}


/*******************************************************************************
 * @brief       Xmodem接收数据处理函数
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void Xmodem_RxHandler(void)
{
    uint32_t Address = 0x08010000;

    uint32_t StartTryTimtout = 0;

    /* 对UART1串口接收消息队列进行初始化 */
    QUEUE_INIT(QUEUE_XMODEM_IDX);

    /* 初始化Xmodem接收状态为接收接收数据包起始关键字 */
    Xmodem_State = XMODEM_RX_STATE_SOH;

    while(1)
    {
        /* 判断当前消息接收列表不为空 */
        if(QUEUE_EMPTY(QUEUE_XMODEM_IDX) == 0)
        {
            /* 从消息队列中读取一个字节的数据 */
            uint8_t data = QUEUE_READ(QUEUE_XMODEM_IDX);

            printf("0x%02x ", data);

            switch(Xmodem_State)
            {
                case XMODEM_RX_STATE_SOH:
                    /* 如果接收到的是数据包的起始关键字 */
                    if((data == XMODEM_SOH) || (data == XMODEM_STX))
                    {
                        /* 如果SOH开头, 表示数据包有效数据的长度为128字节, 如果STX开头, 则为1024字节 */
                        Xmodem_Length  = (data == XMODEM_STX) ? 1024 : 128;

                        Xmodem_Index = 0;
                        Xmodem_State = XMODEM_RX_STATE_NUM;
                    }
                    /* 如果接收到的是数据包结束关键字 */
                    else if(data == XMODEM_EOT)
                    {
                        /* 发送ACK回复, 确认结束并返回 */
                        Xmodem_USART_SendData(XMODEM_ACK);

                        printf("\r\nXmodem Finish"); return;
                    }
                    /* 如果接收到的是数据包取消关键字 */
                    else if(data == XMODEM_CAN)
                    {
                        /* 直接返回, 不需要做任何回复和处理 */
                        printf("\r\nXmodem Cancle"); return;
                    }
                    else
                    {
                    }
                    break;

                case XMODEM_RX_STATE_NUM:
                    /* 存储数据包的序列号和序列号校验数据 */
                    Xmodem_Number[Xmodem_Index++] = data;

                    /* 接收到2个字节的序列号包数据, 接收状态切换到接收数据包有效数据的状态 */
                    if(Xmodem_Index == 2)
                    {
                        Xmodem_Index = 0;
                        Xmodem_State = XMODEM_RX_STATE_DAT;
                    }
                    break;

                case XMODEM_RX_STATE_DAT:
                    /* 存储数据包的有效数据 */
                    Xmodem_Buffer[Xmodem_Index++] = data;

                    /* 如果数据包的有效数据接收长度达到指定接收长度时, 接收状态切换到接收校验数据状态 */
                    if(Xmodem_Index == Xmodem_Length)
                    {
                        Xmodem_Index = 0;
                        Xmodem_State = XMODEM_RX_STATE_CHK;
                    }
                    break;

                case XMODEM_RX_STATE_CHK:
                    /* 存储校验数据 */
                    Xmodem_CheckData[Xmodem_Index++] = data;

                    /* 累加校验和方式, 只有一个字节的校验数据 */
                    if((Xmodem_CheckType == 1) && (Xmodem_Index == 1))
                    {
                        Xmodem_Index = 0;

                        /* 对数据包序列号的合法性进行判断 */
                        if(Xmodem_Number[0] == ((Xmodem_Number[1] ^ 0xFF) & 0xFF))
                        {
                            /* 对数据包接收的有效数据进行累加和校验计算和判断 */
                            if(Xmodem_CheckData[0] == Xmodem_CalcCheckSum(Xmodem_Buffer, Xmodem_Length))
                            {
                                if((Address % FLASH_PAGE_SIZE) == 0)
                                {
                                    Xmodem_FLASH_ErasePage(Address / FLASH_PAGE_SIZE);
                                }

                                Xmodem_FLASH_PageProgram(Address, Xmodem_Buffer, Xmodem_Length);
                                Address += Xmodem_Length;


                                /* 累加校验和计算正确, 发送ACK回复, 准备接收下一个数据包 */
                                Xmodem_USART_SendData(XMODEM_ACK);
                            }
                            else
                            {
                                /* 累加校验和计算错误, 发送NAK回复, 要求重传本次的数据包 */
                                Xmodem_USART_SendData(XMODEM_NAK);
                            }
                        }
                        else
                        {
                            /* 数据包的序列号校验错误, 发送NAK回复, 要求重传本次的数据包 */
                            Xmodem_USART_SendData(XMODEM_NAK);
                        }

                        /* 接收状态切换到接收数据包起始字节的状态 */
                        Xmodem_State = XMODEM_RX_STATE_SOH;
                    }
                    /* CRC校验方式, 有2个字节的校验数据 */
                    else if((Xmodem_CheckType == 0) && (Xmodem_Index == 2))
                    {
                        Xmodem_Index = 0;

                        /* 对数据包序列号的合法性进行判断 */
                        if(Xmodem_Number[0] == ((Xmodem_Number[1] ^ 0xFF) & 0xFF))
                        {
                            uint16_t Result = 0;

                            Result   = Xmodem_CheckData[0];
                            Result <<= 8;
                            Result  |= Xmodem_CheckData[1];

                            /* 对数据包接收的有效数据进行CRC校验计算和判断 */
                            if(Result == Xmodem_CalcCheckCRC(Xmodem_Buffer, Xmodem_Length))
                            {
                                if((Address % FLASH_PAGE_SIZE) == 0)
                                {
                                    Xmodem_FLASH_ErasePage(Address / FLASH_PAGE_SIZE);
                                }

                                Xmodem_FLASH_PageProgram(Address, Xmodem_Buffer, Xmodem_Length);
                                Address += Xmodem_Length;


                                /* CRC校验计算正确, 发送ACK回复, 准备接收下一个数据包 */
                                Xmodem_USART_SendData(XMODEM_ACK);
                            }
                            else
                            {
                                /* CRC校验计算错误, 发送NAK回复, 要求重传本次的数据包 */
                                Xmodem_USART_SendData(XMODEM_NAK);
                            }
                        }
                        else
                        {
                            /* 数据包的序列号校验错误, 发送NAK回复, 要求重传本次的数据包 */
                            Xmodem_USART_SendData(XMODEM_NAK);
                        }

                        /* 接收状态切换到接收数据包起始字节的状态 */
                        Xmodem_State = XMODEM_RX_STATE_SOH;
                    }
                    else
                    {
                        /* do nothing */
                    }
                    break;

                default:
                    break;
            }
        }
        else
        {
            /* 接收方每间隔3秒钟尝试发送'C'或NAK信号, 通知发送方开始传输 */
            if(StartTryTimtout == 0)
            {
                if(Xmodem_CheckType)
                {
                    /* 发送NAK表示通知发送方以累加和的校验方式打包数据 */
                    Xmodem_USART_SendData(XMODEM_NAK);
                }
                else
                {
                    /* 发送'C'表示通知发送方以CRC校验的校验方式打包数据 */
                    Xmodem_USART_SendData('C');
                }
            }

            StartTryTimtout = (StartTryTimtout + 1) % XMODEM_TRY_TIMEOUT;
        }
    }
}
  • 主程序,实现检测按键进入Xmodem升级程序和跳转到应用程序执行的功能:
/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
int main(void)
{
    uint32_t PCLK1 = 0, PCLK2 = 0;

    System_Init();

    Xmodem_Init();

    printf("\r\n");
    printf("\r\n-----------------------------------------------------------");
    printf("\r\n                        Bootloader                         ");
    printf("\r\n-----------------------------------------------------------");
    printf("\r\n");

    printf("\r\nAPM32E103VCS MINIBOARD V1.0 %s %s\r\n", __DATE__, __TIME__);

    printf("\r\nSYSCLKFreq : %dHz", RCM_ReadSYSCLKFreq());
    printf("\r\nHCLKFreq   : %dHz", RCM_ReadHCLKFreq());

    RCM_ReadPCLKFreq(&PCLK1, &PCLK2);

    printf("\r\nPCLK1Freq  : %dHz", PCLK1);
    printf("\r\nPCLK2Freq  : %dHz", PCLK2);

    printf("\r\nADCCLKFreq : %dHz", RCM_ReadADCCLKFreq());

    printf("\r\n\r\n");

    while(1)
    {
        if(GPIO_ReadInputBit(GPIOA, GPIO_PIN_1) == BIT_RESET)
        {
            Xmodem_Running = 1;

            Xmodem_RxHandler();

            Xmodem_Running = 0;
        }
        else
        {
            if(*(volatile uint32_t *)(ApplicationAddress) != 0xFFFFFFFF)
            {
                JumpAddress = *(volatile uint32_t *)(ApplicationAddress + 4);
                JumpToApplication = (pFunction)JumpAddress;

                /* Initialize user application's Stack Pointer */
                __set_MSP(*(volatile uint32_t *)ApplicationAddress); 
                JumpToApplication();
            }
        }
    }
}

 

 

Application端:

  • 配置ROM的起始位置和大小:

  • 配置生成BIN文件烧录程序:

  • 配置Flash Download设置:

  • 配置程序偏移地下:

  • 应用程序端除了以上的配置,其它还是与之前的功能一样的设计……

 

 

运行结果:

先烧录Bootloader程序到MCU中,此时整个FLASH存储被擦除,只留下Bootloader程序;芯片上电启动后,Bootloader进行相应的初始化操作,判断是否有按键按下,如果有则进入Xmodem升级部分,通过调用上位软件菜单栏中的Send Xmodem来发送BIN文件数据,直到升级成功,会自动跳转到应用程序运行;如果没有按键按下,会判断应用程序空间是否存在有效的执行代码,如果有则跳转到应用程序执行,如果没有则继续等待按键检测去执行程序升级功能;

 

 

软件工程源代码:

IAP.zip (559.67 KB, 下载次数: 8)

 

 

后续:

本文仅实现了Xmodem直接下载程序的简单IAP应用功能,实际项目中可以参考这个进行双缓冲的IAP应用功能设计,保证应用程序的健壮性。

最新回复

本文仅实现了Xmodem直接下载程序的简单IAP应用功能,实际项目中可以参考这个进行双缓冲的IAP应用功能设计,保证应用程序的健壮性。 非常好!   详情 回复 发表于 2022-10-1 10:09
点赞 关注
个人签名We are a team and we work as a team !
 
 

回复
举报

7044

帖子

11

TA的资源

版主

沙发
 

本文仅实现了Xmodem直接下载程序的简单IAP应用功能,实际项目中可以参考这个进行双缓冲的IAP应用功能设计,保证应用程序的健壮性。

非常好!

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表