stm32模拟固件升级

本文详细介绍了STM32F103C8T6单片机的Flash分区策略,包括bootloader区、更新标志位区、A区和B区的设置,以及如何通过十六进制表示和管理不同区域。同时涵盖了固件更新流程和中断处理的示例代码。
摘要由CSDN通过智能技术生成

 一、分区

      首先要对flash进行分区,模拟固件升级的场景。一般来说分为bootloader区、A区、B区,我又加了一个更新标志位的区。

      我使用的是stm32f103c8t6,flash是64k,sram是20k。查询数据手册可以看到flash和sram的起始地址分别为:0x08000000、0x0x20000000。(该图flash为128k,我的是64k)

      明确起始地址后,还要知道容量是怎么转换为十六进制的。如flash大小是64k个字节,如何转换成十六进制呢?单片机的地址是按字节编址的,也就是说flash大小为64*1024=65536字节,十进制的65536转换成十六进制就是0x10000,因此,flash的大小就是0x10000,即从0x08000000-0x0800FFFF。

二、bootloader区

      这个区主要就是用来进行上电检测的,读取更新标志区的标志位,来判断程序从A区还是B区启动。我将这个区大小设置为19k,随便设置的,可自行调整。转换成十六进制是0x4C00。需要注意的是每个区的sram均使用全部容量即可,即20k。下图是bootloader程序的keil对应设置。

       除此之外还要注意,keil的下载设置,要设置为部分擦除,防止下载程序时影响其他分区,如下图。

       下面为bootloader区的核心代码,主要就是初始化串口1、跳转程序。

#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "flash.h"
#define A				1U
#define B				0U
/*
bootloader地址:         0x8000000 - 0x8004BFF
更新标志位地址:          0x8004C00 - 0x8004FFF
A区地址:				0x8005000 - 0x800A7FF
B区地址:				0x800A800 - 0x800FFFF
*/
void SystemClock_Config(void);

typedef __IO uint32_t  vu32;
 
//声明指针函数
void (*jump2app)();
 
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(uint32_t appxaddr)
{
	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		jump2app=(void(*)())*(vu32*)(appxaddr+4);	//用户代码区第二个字为复位中断地址
		__set_MSP(*(vu32*)appxaddr);				//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		__disable_irq();							//关闭中断,防止跳转过程中产生中断
		jump2app();									//跳转到APP.
	}
}

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	//输出提示信息
	printf("This is bootloader\r\n");
	HAL_Delay(500);
	//跳转
	if (flag_flash_read() == A)
	{
		printf("Gonna to jump to A!\r\n");
		HAL_Delay(500);
		iap_load_app(0x8005000);
	}
	else if (flag_flash_read() == B)
	{
		printf("Gonna to jump to B!\r\n");
		HAL_Delay(500);
		iap_load_app(0x800A800);
	}
	else
	{
		printf("jump error\r\n");
	}
}

三、更新标志区

      这个区主要就是用来各个区直接通信的,只存放了一个uint8_t的标志位。我将这个区大小设置为1k,随便设置的,可自行调整。这样bootloader+更新标志区一共是20k,占用0x5000大小的flash。

四、A区

      这个区主要就是用来存放运行的代码区。我将这个区大小设置为22k,随便设置的,可自行调整。也就是占用0x5800大小的flash。即从0x8005000到0x800A7FF。下图是A区程序的keil对应设置。

 

       下面为A区的核心代码,主要就是初始化串口1、使用串口1的接收中断来模拟更新检测。

#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "flash.h"

#define A				1U
#define B				0U

extern uint8_t etr_uart1_flag;		//串口1中定义的,初始为0,接收中断中修改为1
/*
bootloader地址:        0x8000000 - 0x8004BFF
更新标志位地址:         0x8004C00 - 0x8004FFF
A区地址:				0x8005000 - 0x800A7FF
B区地址:				0x800A800 - 0x800FFFF
*/
void SystemClock_Config(void);

//第一个参数是flash基地址0x08000000,第二个是偏移量
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)
{ 
  SCB->VTOR = NVIC_VectTab | Offset;
}

int main(void)
{
	__enable_irq();			//开启中断
  HAL_Init();
  SystemClock_Config();
	NVIC_SetVectorTable(0x08000000, 0x5000);	//设置中断偏移
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	//输出提示信息
	printf("Here is the app_a region\r\n");
	//模拟固件更新
	//实际场景中,下面代码应该是固件接收中断标志更改后,才执行
	//这里采用外部中断,b5引脚接按键,来模拟固件接收中断
	while (1)
	{
		if (etr_uart1_flag)			//一旦检测到更新,就开始更新固件
		{
			printf("检查到新版本,下载至B区中...\r\n");
			HAL_Delay(5000);
			printf("固件下载完成,校验中...\r\n");
			HAL_Delay(5000);
			printf("固件校验完成,准备安装...\r\n");
			flag_flash_write(B);
			HAL_Delay(2000);
			printf("固件安装完成,geng请重启...\r\n");
			etr_uart1_flag = 0;
		}
	}
}

      下面是代码中的串口1的配置代码。

#include "usart.h"
uint8_t etr_uart1_flag = 0;
UART_HandleTypeDef huart1;

void MX_USART1_UART_Init(void)
{
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct;
	__HAL_RCC_USART1_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();
	//tx
	GPIO_InitStruct.Pin = GPIO_PIN_9;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	//rx
	GPIO_InitStruct.Pin = GPIO_PIN_10;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	HAL_NVIC_SetPriority(USART1_IRQn, 10, 1);
	HAL_NVIC_EnableIRQ(USART1_IRQn);
}

int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
	return (ch);
}
/* USER CODE END 1 */
int fgetc(FILE *f)
{
	int ch;
	HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);
	return (ch);
}

void USART1_IRQHandler(void)
{
	uint8_t ch = 0;
	if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
	{
		ch = (uint16_t) READ_REG(huart1.Instance->DR);
		if (ch == 'b')        //输入b即可在A区运行时,出发更新操作
			etr_uart1_flag = 1;
	}
}

      下面是flah的读写函数,因为就用了一页来写,stm32f103c8中flash的64k分别划分为64页,每页1k。

#include "flash.h"
#define FLAG_FLASH_ADDR			0x8004C00    //更新标志区的起始地址

void flag_flash_write(uint8_t data)
{
	FLASH_EraseInitTypeDef Flash_EraseInitStruct;
	uint32_t pageError = 0;
	//解锁
	HAL_FLASH_Unlock();
	Flash_EraseInitStruct.PageAddress = FLAG_FLASH_ADDR;
	Flash_EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
	Flash_EraseInitStruct.NbPages = 1;
	Flash_EraseInitStruct.Banks = FLASH_BANK_1;
	//擦除第20个页,也就是我们存放标志位的页
	if (HAL_FLASHEx_Erase(&Flash_EraseInitStruct, &pageError) != HAL_OK)
	{
		printf("erase error\n");
	}
	FLASH_WaitForLastOperation(50000);
	//写入
	HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, FLAG_FLASH_ADDR, data);
	//锁定
	HAL_FLASH_Lock();
}

uint8_t flag_flash_read(void)
{
	return *(uint32_t*)FLAG_FLASH_ADDR;
}

五、B区

      这个区主要就是用来存放另一个运行的代码区。我将这个区大小也设置为22k,随便设置的,可自行调整。也就是占用0x5800大小的flash。即从0x800A800到0x800FFFF。下图是B区程序的keil对应设置。

       至于B区代码,和A区基本一致,唯一要改的就是,B区的主函数中,更新程序的部分这句flag_flash_write(B);改为:flag_flash_write(A);

六、运行结果展示

       注意!!!为了防止第一次程序打印jump error

       注意!!!为了防止第一次程序打印jump error

       注意!!!为了防止第一次程序打印jump error

       因为第一次上电时,更新标志区的值可能不是0也不是1,这个时候就会bootloader就会打印jump error。我们可以先把更新标志区写个A进去。具体做法是,在bootloader的main函数中调用flag_flash_writer(A),然后把这个程序烧录进去,上电就会把更新标志区初始化为1,即默认跳转A区。之后,再注释或删掉flag_flash_writer(A),然后再烧录一遍bootloader程序。之后就可以正常运行了。

       下载好对应程序后,连接串口助手,复位单片机,输出如下信息:

        可以看到程序默认从A区启动,此时输入'b',程序即模拟检测到固件更新,待提示如下信息后,复位单片机,即重启后便跳转至b区启动。

        此时接着再输入'a',程序即模拟再次检测到固件更新,待提示如下信息后,复位单片机,即重启后又跳转至a区启动。循环往复,模拟固件更新。

至此,整个工作完成,也算是对自己近几天学习的记录与总结。

其中代码的细节完全靠自己的想法设计的,所以可能存在很多漏洞,也希望大家多多指点 

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32F407模拟U盘固件升级是通过将STM32F407单片机作为一个虚拟的U盘设备,在电脑上进行固件升级操作的过程。 首先,我们需要连接STM32F407开发板和电脑,通过USB接口将STM32F407连接到电脑上。 然后,在STM32F407的固件中,需要实现USB设备功能,并设置为虚拟U盘设备。这需要使用STM32CubeMX软件进行配置,选择USB Device模块,并配置为Mass Storage设备模式。 接着,需要在固件中编写相应的应用程序,实现固件升级的功能。这包括接收来自电脑的固件升级文件,将文件数据保存在STM32F407的存储器中。 在固件中,还需要实现固件校验的功能,确保升级文件的完整性和正确性。可以通过计算文件的CRC校验码,并将其与预先计算好的校验码进行比较。 在固件升级的过程中,首先用户需要将电脑上的固件升级工具运行起来,并选择固件升级的选项。然后,电脑会识别STM32F407模拟的U盘设备,并将升级文件传输到STM32F407上。 在固件传输完成后,STM32F407会进行固件校验,确认升级文件的完整性和正确性。如果校验通过,STM32F407会将升级文件写入Flash存储器中,完成固件升级。 最后,在固件升级完成后,STM32F407会重新启动,加载新的固件程序,并开始运行。 总的来说,STM32F407模拟U盘固件升级是通过将STM32F407作为虚拟U盘设备,在电脑上进行固件升级的过程,需要实现USB设备功能和固件校验功能,确保升级文件的完整性和正确性。这种方案可以方便快捷地进行固件升级,提高开发效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值