STM32固件开发:使用Python和XMODEM协议进行固件升级详解
本文章针对有嵌入式编程基础的技术人员,Python制作上位机软件工具需要会使用Pycharm工具。文章不再具体描述嵌入式开发的基础和Python的基础语法等问题,有些语法会在文章中间说明,其中Python制作的软件升级工具成功升级STM32F103CT6和STC8H8K64U的固件,着重讲解Python+PYQT5+STM32F103C8T6使用XMODEM协议升级固件的开发过程。
目录
一: XMODEM的STM32F103C8初始化代码。
XMODEM的原理就不多说了,各个博主都有分享,本代码通过Windows系统自带的超级终端测试,本人使用Python编写的代码也测试过,都能正常使用。很多人可能有疑问,为何Windows超级终端可以使用,为何还要自己写个工具呢?原因如下:
1: Windows系统超级终端不能自由的发数据,例如发单个字节或者多少个字节。
2: Windows系统超级终端使用不友好,对于非专业技术人员,有难度,更别说让客户使用了。
3: 一般嵌入式产品做好后,后续需要维护升级,可以通过发命令方式进入Boot。根据个人喜好,也可以在代码中添加自己的调试命令,Windows超级终端对这方面不大友好,而通过的串口工具,例如SSCOM又不具备下载升级功能。
1.1:代码整体框架
Main.C代码如下:
#include "main.h"
void SystemClock_Config(void);
int main(void)
{
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_AFIO);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
LL_GPIO_AF_DisableRemap_SWJ();
SystemClock_Config();
InitLedGPIO(); // 初始化LED的GPIO
CheckAPPFirmware(); // 检测是否有固件
Delay1us(30000); // 延时
InitUsart1(); // 初始化串口1
InitTimer3(); // 初始化定时器3
InitXModem(); // 初始化XMODEM的参数
while (1)
{
LEDTask(); // LED任务
XModemTask(); // XMODEM任务
}
}
1.2: SystemClock_Config()代码
这部分代码由STM32生成,简单配置时钟频率即可
void SystemClock_Config(void)
{
LL_FLASH_SetLatency(LL_FLASH_LATENCY_2);
while(LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_2)
{
}
LL_RCC_HSI_SetCalibTrimming(16);
LL_RCC_HSI_Enable();
/* Wait till HSI is ready */
while(LL_RCC_HSI_IsReady() != 1)
{
}
LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSI_DIV_2, LL_RCC_PLL_MUL_16);
LL_RCC_PLL_Enable();
/* Wait till PLL is ready */
while(LL_RCC_PLL_IsReady() != 1)
{
}
LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_2);
LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
/* Wait till System clock is ready */
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
{
}
LL_Init1msTick(64000000); // 配置Tick时钟频率为64MHz
LL_SetSystemCoreClock(64000000); // 配置系统主时钟为64MHz
}
1.3:InitLedGPIO() 代码
void InitLedGPIO(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOC);
LL_GPIO_ResetOutputPin(LED_GPIO_Port, LED_Pin);
GPIO_InitStruct.Pin = LED_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_MEDIUM;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; // 此模式可选其它
LL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
LedOff(); //关闭LED灯,进入boot后,会快速闪烁
}
1.4: CheckAPPFirmware()代码
检查是否需要更新固件。
void CheckAPPFirmware(void)
{
u16 APPData;
APPData = Flash_ReadData16Bit( FLASH_APP_CHECK_ADDRESS );
if((APPData == APP_NEED_TO_UPDATE_DATA) || (APPData == 0xFFFF) ) //检查到Flash升级标识,表示需要升级
{
return;
}
BootloaderJumpToApp();
}
1.5: InitUsart1()串口1初始化代码
void InitUsart1(void)
{
LL_USART_InitTypeDef USART_InitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1);
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
GPIO_InitStruct.Pin = LL_GPIO_PIN_9;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LL_GPIO_PIN_10;
GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
NVIC_EnableIRQ(USART1_IRQn);
USART_InitStruct.BaudRate = BAUDRATE;
USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;
USART_InitStruct.StopBits = LL_USART_STOPBITS_1;
USART_InitStruct.Parity = LL_USART_PARITY_NONE;
USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;
USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16;
LL_USART_Init(USART1, &USART_InitStruct);
LL_USART_ConfigAsyncMode(USART1);
LL_USART_DisableIT_TC( USART1);
LL_USART_DisableIT_TXE(USART1);
LL_USART_EnableIT_RXNE(USART1);
LL_USART_Enable(USART1);
}
1.6: InitTimer3()代码
// 在Xmodem中,不使用定时器3中断,这里仅示例定时器的配置,1ms定时器中断
void InitTimer3(void)
{
LL_TIM_InitTypeDef TIM_InitStruct = {0};
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3);
NVIC_SetPriority(TIM3_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
NVIC_EnableIRQ(TIM3_IRQn);
TIM_InitStruct.Prescaler = 6400-1; //100us
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM_InitStruct.Autoreload = 10; //计数器重装载值,10*100us,产生1ms定时中断
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
LL_TIM_Init(TIM3, &TIM_InitStruct);
LL_TIM_EnableARRPreload(TIM3);
LL_TIM_SetClockSource(TIM3, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_SetTriggerOutput(TIM3, LL_TIM_TRGO_RESET);
LL_TIM_DisableMasterSlaveMode(TIM3);
LL_TIM_EnableIT_UPDATE(TIM3);
LL_TIM_EnableCounter(TIM3);
LL_SYSTICK_EnableIT();
SystemTime = 0; //此变量做系统时钟实,在Tick时钟对此变量+1操作
}
1.7: InitXModem()代码
void InitXModem(void)
{
InitXModemParameter();
Flash_ErasePages(FLASH_APP_START_ADDRESS, STM32_FLASH_PAGES- BOOT_LOADER_PAGE_SIZE);
}
void InitXModemParameter(void)
{
XModemStructure.XModemReceiveDataPackTimeStart = DISABLE;
XModemStructure.XModemReceiveDataPackSuccess = ERROR;
XModemStructure.XModemDataErrorTimes = 0;
XModemStructure.XModemReceiveDataPackTime = 0;
XModemStructure.XModemReceiveDataPackEnable = DISABLE;
XModemStructure.XModemPackDataLength = 0;
XModemStructure.XModemDataPackNumber = 0;
memset(&XModemStructure.XModemPackDataBuffer,0,XMODEM_PACK_DATA_SIZE);
//XModemStructure.XModemProcessStep = YM_STEP_NULL;
XModemStructure.XModemProcessStep = XM_STEP0_REQUEST_START_DATA;
}
二:中断代码分析
2.1: Tick时钟代码
void SysTick_Handler(void)
{
SystemTime++;
if(XModemStructure.XModemReceiveDataPackTimeStart == ENABLE) // 串口接收数据超时,启动下一包数据时清零
XModemStructure.XModemReceiveDataPackTime++;
}
2.2: 定时器3产生1ms时钟代码
void TIM3_IRQHandler(void)
{
LL_TIM_ClearFlag_UPDATE( TIM3 );
}
2.3: 串口1中断代码
串口中断接收数据,并做简单的数据处理,确保数据不丢失,包及时处理。
上位机只在接收到下位机的ACK后才发送下包数据,所以可以在串口做简单的数据处理。
void USART1_IRQHandler(void)
{
u8 Datatemp;
if( LL_USART_IsActiveFlag_RXNE(USART1) == RESET ) //未检测到串口中断
return;
Datatemp = LL_USART_ReceiveData8(USART1); // 收串口数据
if( XModemStructure.XModemPackDataLength == 0 ) //当前缓存无数据,表示收到新的数据包
{
if(Datatemp == XMODEM_SOH) // 接收一包数据起始字符
XModemStructure.XModemReceiveDataPackEnable = ENABLE; // 允许后续正常接收数据
if(Datatemp == XMODEM_EOT) // 收到传输结束符
{
XModemStructure.XModemProcessStep = XM_STEP4_RECEIVE_EOT;
XModemStructure.XModemPackDataBuffer[XModemStructure.XModemPackDataLength] = XMODEM_EOT;
XModemStructure.XModemReceiveDataPackSuccess = SUCCESS;
}
if( Datatemp == XMODEM_ETB ) //传输快结束
{
XModemStructure.XModemProcessStep = XM_STEP6_RECEIVE_ETB;
XModemStructure.XModemPackDataBuffer[XModemStructure.XModemPackDataLength] = XMODEM_ETB;
XModemStructure.XModemReceiveDataPackSuccess = SUCCESS;
}
if( Datatemp == XMODEM_CAN ) // 收到取消传输符号
{
XModemStructure.XModemProcessStep = XM_STEP_RECEIVE_CANNEL;
XModemStructure.XModemReceiveDataPackSuccess = SUCCESS;
}
}
if( XModemStructure.XModemReceiveDataPackEnable == ENABLE ) //收到数据帧头信息后,才接收并缓存数据
{
XModemStructure.XModemPackDataBuffer[XModemStructure.XModemPackDataLength] = Datatemp;
XModemStructure.XModemPackDataLength += 1;
}
if( XModemStructure.XModemPackDataLength == XMODEM_PACK_DATA_SIZE ) //收到133字节的数据包
{
XModemStructure.XModemReceiveDataPackSuccess = SUCCESS;
XModemStructure.XModemReceiveDataPackEnable = DISABLE; // 暂停接收数据
}
}
三: 主循环代码
3.1: LED任务代码
void LEDTask(void)
{
static u32 LEDTaskTime;
if( (SystemTime – LEDTaskTime) < LED_TASK_TIME ) //LED任务时间到; #define LED_TASK_TIME 100
return;
LL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin); // LED引脚取反
LEDTaskTime = SystemTime; // 更新LED任务时间点
}
3.2: XModemTask()任务代码
Xmodem采用状态机的方式接收数据,这样思路清晰,方便阅读代码和维护。
void XModemTask(void)
{
ErrorStatus XmodemReceiveDataPackSuccess;
u8 XModemStep;
if( XModemStructure.XModemReceiveDataPackTime > XMODEM_TIME_OUT) // 收帧数据包超时,3秒超时
{
InitXModemParameter();
return;
}
XmodemReceiveDataPackSuccess = XModemStructure.XModemReceiveDataPackSuccess;
XModemStep = XModemStructure.XModemProcessStep;
switch( XModemStep )
{
case XM_STEP0_REQUEST_START_DATA: //第一步:向主机请求数据包
{
XModemStep0Process();
break;
}
case XM_STEP1_RECEIVE_PACK_DATA:
XModemStep1Process( XmodemReceiveDataPackSuccess );
break;
case XM_STEP2_RESPOND_PACK_ACK:
XModemStep2Process();
break;
case XM_STEP3_RESPOND_PACK_NACK:
XModemStep3Process();
break;
case XM_STEP4_RECEIVE_EOT:
XModemStep4Process(XmodemReceiveDataPackSuccess);
break;
case XM_STEP5_RESPOND_EOT_ACK:
XModemStep5Process();
break;
case XM_STEP6_RECEIVE_ETB:
XModemStep6Process(XmodemReceiveDataPackSuccess);
break;
case XM_STEP7_RECEIVE_DATA_SUCCESS:
XModemStep7Process();
break;
case XM_STEP_RECEIVE_CANNEL:
XModemStepCancelProcess();
break;
default:
break;
}
}
3.2.1: XMODEM请求发送数据状态。
XMODEM状态:XM_STEP0_REQUEST_START_DATA,此状态表示请求上位机发送数据。
void XModemStep0Process(void)
{
XModemStructure.XModemReceiveDataPackTimeStart = ENABLE; //接收一帧数据超时计时开启
XModemStructure.XModemReceiveDataPackEnable = DISABLE; //接收正确的帧头信息才允许収数据,避免収到其它数据
XModemStructure.XModemReceiveDataPackTime = 0; //帧接收数据计时器清零
XModemStructure.XModemPackDataLength =0; //帧数据长度初始化为0
memset(&XModemStructure.XModemPackDataBuffer,0,XMODEM_PACK_DATA_SIZE); //缓存数组清零
XModemStructure.XModemReceiveDataPackSuccess = ERROR; //成功接收一帧数据标记,初始化为ERROR
XModemStructure.XModemProcessStep = XM_STEP1_RECEIVE_PACK_DATA; //XModem当前处于何种工作状态
XModemStructure.XModemDataErrorTimes =0; //数据出错次数清零,初始化为0
USART1Send8Bit(XMODEM_C); //向主机请求数据,发送信号“C”
}
3.2.2:XMODEM接收数据
XMODEM状态:XM_STEP1_RECEIVE_PACK_DATA,此状态表示接收上位机数据。
void XModemStep1Process(ErrorStatus XmodemReceiveOneFrameDataSuccess)
{
ErrorStatus XModemDataSuccess; // 接收数据包成功标记
if( XmodemReceiveOneFrameDataSuccess == ERROR ) // 未接收到数据包
return;
XModemDataSuccess = AnalyzeOfficialDataPack(); //处理数据包
if( XModemDataSuccess == ERROR ) //收到数据有误,请求重发
{
XModemStructure.XModemDataErrorTimes++; //数据包错误计数
if( XModemStructure.XModemDataErrorTimes > XMODEM_ERROR_TIMES ) //数据错误超过设定次数
{
XModemStructure.XModemProcessStep = XM_STEP0_REQUEST_START_DATA; //重新回第一步
return;
}
XModemStructure.XModemProcessStep = XM_STEP3_RESPOND_PACK_NACK; //重发数据
return;
}
XModemStructure.XModemReceiveDataPackTimeStart = DISABLE; //接收数据超时计时关闭
XModemStructure.XModemProcessStep = XM_STEP2_RESPOND_PACK_ACK; //响应收到正确数据
}
ErrorStatus AnalyzeOfficialDataPack(void) // 分析正式数据包
{
u8 HeadData;
u16 i,FlashData,FlashPageOder,FlashPageNumber;
u32 FlashAddress,FlashPageAddress,PackStartAddress;
//ErrorStatus WriteFlashError;
FlashStatusTypedef FlashStatus;
HeadData = XModemStructure.XModemPackDataBuffer[XMODEM_PACK_SOH_POS];
if( HeadData != XMODEM_SOH ) //数据包以XMODEM_SOH开头,否则就不是数据包
return ERROR;
HeadData = XModemStructure.XModemPackDataBuffer[XMODEM_PACK_ORDER_CODE_POS] & XModemStructure.XModemPackDataBuffer[XMODEM_PACK_ORDER_nCODE_POS];
if( HeadData != 0x00 ) //数据包序号与序号反码是否正确
return ERROR;
if( XModemDataCRC() == ERROR ) //数据做CRC校验
return ERROR;
FlashPageOder = XModemStructure.XModemDataPackNumber % XMODEM_PACKS_OF_ONE_FLASH_PAGE; // 1K=1024Bytes=128Bytes*8
FlashPageNumber = (u16)(XModemStructure.XModemDataPackNumber / XMODEM_PACKS_OF_ONE_FLASH_PAGE); // 计算当前页码,8个数据=1页数据(1024Bytes)
if( FlashPageOder == 0 )
{
FlashPageAddress = FlashPageNumber*STM32_FLASH_PAGE_SIZE; // 接收1K字节后,算出Flash的相对地址
FlashStatus = Flash_EraseOnePage(FLASH_APP_START_ADDRESS + FlashPageAddress); // 先擦除需要编写入数据的页
if( FlashStatus != FLASH_STATUS_COMPLETE )
LedOn();
}
PackStartAddress = XModemStructure.XModemDataPackNumber*XMODEM_REAL_DATA_SIZE; // 包数据在Flash的起始地址
i=0;
while( i < XMODEM_REAL_DATA_SIZE )
{
FlashAddress = FLASH_APP_START_ADDRESS + PackStartAddress + i;
FlashData = (u16)((XModemStructure.XModemPackDataBuffer[XMODEM_PACK_DATA_START_POS+i+1]<<8) & 0xFF00) | // 缓存高8位数据
(u16)((XModemStructure.XModemPackDataBuffer[XMODEM_PACK_DATA_START_POS+i]) & 0x00FF); // 缓存低8位 数据
Flash_WriteData16Bit(FlashAddress,FlashData);
//WriteFlashError = Flash_WriteData16Bit(FlashAddress,FlashData); //数据写入Flash
//if( WriteFlashError == ERROR )
// return ERROR;
i += 2;
}
XModemStructure.XModemDataPackNumber += 1;
return SUCCESS;
}
ErrorStatus XModemDataCRC(void) //已测试通过
{
u16 i,j,CRCData;
u16 DataTemp;
i=0;
CRCData = 0;
while( i < XMODEM_REAL_DATA_SIZE )
{
CRCData = CRCData ^ ((int) (XModemStructure.XModemPackDataBuffer[i+XMODEM_PACK_DATA_START_POS] << 8));
j = 8 ;
do
{
if ( CRCData & 0x8000 )
CRCData = (CRCData << 1) ^ 0x1021;
else
CRCData = CRCData << 1;
}
while (–j);
i++;
}
DataTemp = (int)(XModemStructure.XModemPackDataBuffer[XMODEM_PACK_DATA_CRCH_POS]<<8) | (int)(XModemStructure.XModemPackDataBuffer[XMODEM_PACK_DATA_CRCL_POS]);
if( CRCData == DataTemp )
return SUCCESS;
DataTemp = (int)(XModemStructure.XModemPackDataBuffer[XMODEM_PACK_DATA_CRCL_POS]<<8) | (int)(XModemStructure.XModemPackDataBuffer[XMODEM_PACK_DATA_CRCH_POS]);
if( CRCData == DataTemp )
return SUCCESS;
//return ERROR;
return SUCCESS; // 此处关闭CRC校验
}
3.2.3:XMODEM反馈数据包ACK
XMODEM状态:XM_STEP2_RESPOND_PACK_ACK,此状态表示向上位机反馈ACK信号。
void XModemStep2Process(void)
{
XModemStructure.XModemReceiveDataPackTimeStart = ENABLE; //超时计时开启
XModemStructure.XModemReceiveDataPackEnable = DISABLE;
XModemStructure.XModemReceiveDataPackTime = 0; //超时计时器清零
XModemStructure.XModemPackDataLength =0; //接收到的数据长度初始化为0
memset(&XModemStructure.XModemPackDataBuffer,0,XMODEM_PACK_DATA_SIZE);
XModemStructure.XModemReceiveDataPackSuccess = ERROR; //初始化为ERROR
XModemStructure.XModemDataErrorTimes = 0; //收到错误数据清零
XModemStructure.XModemProcessStep = XM_STEP1_RECEIVE_PACK_DATA; //继续接收数据
USART1Send8Bit(XMODEM_ACK); //发送“ACK”信号,确认上包数据正确
}
3.2.4:XMODEM反馈数据包NACK
XMODEM状态:XM_STEP3_RESPOND_PACK_NACK,此状态表示向上位机反馈NACK信号。
void XModemStep3Process(void)
{
XModemStructure.XModemReceiveDataPackTimeStart = ENABLE; //超时计时开启
XModemStructure.XModemReceiveDataPackEnable = DISABLE;
XModemStructure.XModemReceiveDataPackTime = 0; //超时计时器清零
XModemStructure.XModemPackDataLength =0; //接收到的数据长度初始化为0
memset(&XModemStructure.XModemPackDataBuffer,0,XMODEM_PACK_DATA_SIZE);
XModemStructure.XModemReceiveDataPackSuccess = ERROR; //初始化为ERROR
XModemStructure.XModemProcessStep = XM_STEP1_RECEIVE_PACK_DATA; //继续接收数据
USART1Send8Bit(XMODEM_NACK); //发送“NACK”信号,请求重传数据包
}
3.2.5:XMODEM反馈收到结束符
XMODEM状态:XM_STEP4_RECEIVE_EOT,此状态表示向上位机反馈收到结束符。
void XModemStep4Process(ErrorStatus XmodemReceiveOnePackDataSuccess)
{
if( XmodemReceiveOnePackDataSuccess == ERROR )
return;
if( XModemStructure.XModemPackDataBuffer[XMODEM_PACK_SOH_POS] != XMODEM_EOT )
return;
InitXModemParameter();
XModemStructure.XModemProcessStep = XM_STEP5_RESPOND_EOT_ACK;
}
3.2.6:XMODEM收到结束符后,反馈NAK信号
XMODEM状态:XM_STEP5_RESPOND_EOT_ACK,此状态表示收到结束符后,向上位机反馈NAK信号。
void XModemStep5Process(void)
{
InitXModemParameter();
//XModemStructure.XModemProcessStep = XM_STEP6_RECEIVE_ETB; //超级终端不发送ETB
XModemStructure.XModemProcessStep = XM_STEP7_RECEIVE_DATA_SUCCESS;
USART1Send8Bit(XMODEM_ACK);
}
3.2.7:XMODEM收到第二个结束符
XMODEM状态:XM_STEP6_RECEIVE_ETB,此状态表示收到上位机第二个结束符。
void XModemStep6Process(ErrorStatus XmodemReceiveOnePackDataSuccess)
{
if( XmodemReceiveOnePackDataSuccess == ERROR )
return;
if(XModemStructure.XModemPackDataBuffer[XMODEM_PACK_SOH_POS] != XMODEM_ETB)
return;
USART1Send8Bit(XMODEM_ACK); //结束XModem传输数据
InitXModemParameter();
XModemStructure.XModemProcessStep = XM_STEP7_RECEIVE_DATA_SUCCESS;
}
3.2.8:XMODEM成功接收完整的文件
XMODEM状态:XM_STEP7_RECEIVE_DATA_SUCCESS,此状态表示成功接收完整的文件。
void XModemStep7Process(void)
{
InitXModemParameter();
ReceiveXModemPackSuccess();
}
void ReceiveXModemPackSuccess(void)
{
USART1Send8Bit(0x41);
USART1Send8Bit(0x42);
USART1Send8Bit(0x43);
USART1Send8Bit(0x44);
XModemStructure.XModemProcessStep = XM_STEP_NULL;
SetFirmwareSuccessSign(); // 设置Firmware成功标记
BootloaderJumpToApp(); //跳转到APP
}
void BootloaderJumpToApp( void ) // 跳转部分代码
{
u32 JumpAddress;
CloseIQHard();
if(((*(vu32*)FLASH_APP_START_ADDRESS)&0x2FFE0000) == 0x20000000) //检查栈顶地址是否合法.
{
__set_CONTROL(0); //特权模式
__ASM("CPSID I"); //关中断
JumpAddress = *(vu32*)(FLASH_APP_START_ADDRESS+4); //用户代码区第二个字为程序开始地址(复位地址)
JumpToApp = (IapFunction)JumpAddress;
__set_MSP(*(vu32*)FLASH_APP_START_ADDRESS); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
JumpToApp(); //跳转到APP.
}
}
void CloseIQHard(void) //关闭所有中断和端口时钟
{
LL_GPIO_DeInit(GPIOA);
LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_GPIOA); //关使能PA端口时钟
LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_AFIO); //关辅助时能时钟
LL_TIM_DisableCounter(TIM3);
__set_PRIMASK(1);
__disable_irq(); // 关闭总中断
}
3.2.9:XMODEM收到取消传输
XMODEM状态:XM_STEP_RECEIVE_CANNEL,此状态表示收到取消传输文件。
void XModemStepCancelProcess(void)
{
InitXModemParameter();
}
四: 关于APP代码和Keil C的设置
4.1 APP代码设置
APP的main()函数入口代码设置。
/*************************** 需要设置 system_stm32f1xx.c 中的偏移地址。********************
#define VECT_TAB_OFFSET 0x2000 //APP起始地址,0x2000为APP的起始地址,0~0x2000时BootLoader空间
************************************************************************************/
#ifdef BOOTLOADER
SetVectorTableAandIQR();
#endif
void SetVectorTableAandIQR(void)
{
//SCB->VTOR = FLASH_BASE | APP_FLASH_ADDRESS; //修改中断向量表
WRITE_REG(SCB->VTOR, FLASH_BASE | APP_FLASH_ADDRESS);
__ASM("CPSIE I"); //开中断
}
4.2 Keil C设置
五: Windows端测试
介绍使用两种工具的测试情况:
一:使用Windows超级终端,但在APP运行时,需要通过串口发送升级指令,然后切换到超级终端升级固件。
二:使用Python制作的工具,可以发送命令和接收数据,并升级固件,对用户友好。
4.1: 超级终端测试
APP代码采集AD数据,通过串口发送。
发送升级命令: 0xAA 0xBB 0xCC后进入Boot模式,SSCOM接收到发送数据请求。
关闭串口,转到超级终端升级固件。
配置好超级终端的串口参数后,收到字符“C”,如下图。
选择传输协议和文件,文件只支持.bin格式。
点击“发送”,如下图
发送成功后跳转到APP,间隔1S发送AD采样的数据。
4.2: Python串口工具测试
使用Python制作的工具发送文件
首先配置串口参数,如下图。
打开串口后,收到APP发送的AD数据,如下图。
点击“发送数据”按钮,串口收到数据后进入Boot状态,并发送请求数据。
点击“打开文件”按钮,选择要升级的固件,仅支持.bin文件,如下图。
文件打开后,点击“升级固件”按钮,进入升级固件流程,如下图。
右上角可观测升级固件进度,升级完成后会弹出对话框。
升级完成后,APP发送AD采样的数据。
至此,BootLoader功能完成,有误地方欢迎留言指正,一起学习。
作者:hhccxw