[极海M3内核 APM32E103VET6S MINI开发板]06.基于Xmodem实现IAP在线升级功能
[复制链接]
APM32E103VET6S内置了512KB的FLASH空间,每一页的大小为2KB,我们通过USART接口实现Xmodem传输协议与PC端的软件进行数据传输,再结合对MCU FLASH的读、写、擦除等操作实现IAP在线程序升级的功能,具体实现步骤如下:
Bootloader端:
/* 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;
}
/*******************************************************************************
* @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端:
- 应用程序端除了以上的配置,其它还是与之前的功能一样的设计……
运行结果:
先烧录Bootloader程序到MCU中,此时整个FLASH存储被擦除,只留下Bootloader程序;芯片上电启动后,Bootloader进行相应的初始化操作,判断是否有按键按下,如果有则进入Xmodem升级部分,通过调用上位软件菜单栏中的Send Xmodem来发送BIN文件数据,直到升级成功,会自动跳转到应用程序运行;如果没有按键按下,会判断应用程序空间是否存在有效的执行代码,如果有则跳转到应用程序执行,如果没有则继续等待按键检测去执行程序升级功能;
软件工程源代码:
后续:
本文仅实现了Xmodem直接下载程序的简单IAP应用功能,实际项目中可以参考这个进行双缓冲的IAP应用功能设计,保证应用程序的健壮性。
|