您的位置:首页 > 教育 > 锐评 > 怎么查找自己的企业邮箱_网页视频解析下载_开发一个网站需要多少钱_网站推广方案范例

怎么查找自己的企业邮箱_网页视频解析下载_开发一个网站需要多少钱_网站推广方案范例

2024/10/5 20:22:34 来源:https://blog.csdn.net/Teminator_/article/details/142670597  浏览:    关键词:怎么查找自己的企业邮箱_网页视频解析下载_开发一个网站需要多少钱_网站推广方案范例
怎么查找自己的企业邮箱_网页视频解析下载_开发一个网站需要多少钱_网站推广方案范例

目录

  • 一、概述
  • 二、IAP 实现
  • 三、IAP 程序
    • 1、串口部分
    • 2、iap 程序
    • 3、内部 flash 读写
    • 4、main 程序


IAP(In Application Programming在应用编程)是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写。简单来说,就是开发者代码出 bug 了或者添加新功能了,能够利用预留的通讯接口,对代码进行升级。

UART、SPI、IIC、USB 等等,当然还有 WIFI、4G、Bluetooth 等无线通讯手段,都可以作为 IAP 升级的方式,今天主要介绍如何使用串口对固件进行升级。

这里有一点需要特殊注意,就是在 MCU 中,有一个特殊区域被称为 System memory 。在这块区域中存放了 ST 公司自己的 Bootloader 程序,它是在 MCU 出厂时,有 ST 固化到芯片中的,后续不能再更改。其中的 Bootloader 程序也可以对 MCU 进行升级(DFU 对芯片的编程应该就是用的这个 Bootloader)。而且,芯片不同,BootLoader 的功能也是有区别的。ST 官网对于这些也是有详细文档的。下图为部分芯片 BootLoader 版本及功能:

一、概述

在学习 IAP 之前,最好先了解一下 SMT32 芯片的启动过程,可以参考:STM32 芯片启动过程。

这里再简单说一下,权威指南讲到:芯片复位后首先会从向量表里面取出两个值:

  • 0x0000 0000 地址取出 MSP(主堆栈寄存器)的值
  • 0x0000 0004 地址取出 PC(程序计数器)的值
  • 然后取出第一条指令执行

不过,STM32 比较特殊,它对地址做了一个重定向(由 MCU 启动配置决定的),一般它是将 0x0000 0000 地址映射到 0x0800 0000,也就是说:

  • 0x0800 0000 地址取出 MSP(主堆栈寄存器)的值
  • 0x0800 0004 地址取出 PC(程序计数器)的值
  • 然后取出第一条指令执行

为什么要设置到 0x0800 0000,而不直接使用 0x0000 0000

因为 STM32 不仅可以从内部 Flash 启动,还可以从系统存储器(可以实现串口 ISP,USB DFU 等程序下载方式,这个程序是 ST 固化好的程序代码)和从内部 SRAM 启动,
我们将内部 Flash 安排到 0x0000 0000 显然是不行的。这样会导致系统存储器或者内部 SRAM 无法重映射到 0x0000 0000 了。

二、IAP 实现

为了实现 IAP,整个程序分为两个部分:

  • Bootloader:引导程序,接收来自串口的固件包并写入 Flash(擦除和写入) 完成升级
  • App:用户程序

两者在 Flash 中的结构如下:

如下图所示流程中:

  • STM32F407 复位后,还是从 0x08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示;
  • 在执行完 IAP 以后(即将新的 APP 代码写入 STM32F407 的 FLASH,灰底部分。新程序的复位中断向量起始地址为 0x08000004+N+M ),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main 函数,如图标号②和③所示,同样 main 函数为一个死循环,并且注意到此时 STM32F407 的 FLASH,在不同位置上,共有两个中断向量表。

在 main 函数执行过程中,如果 CPU 得到一个中断请求:

  1. PC 指针仍然会强制跳转到地址 0x08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;
  2. 程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;
  3. 在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号⑥所示。

三、IAP 程序

1、串口部分

首先,串口是至关重要的一部分,毕竟数据是通过串口传递过来的。

首先是定义了串口数据接收缓冲区的大小为 120 kb,下面的 UART_RX_BUF_BIN 即数据缓冲区,UART_RX_CNT 记录了 UART_RX_BUF_BIN 数组的大小。

// uart.h
#define RX_BUFFER_SIZE 120*1024extern uint8_t 	UART_RX_BUF_BIN[RX_BUFFER_SIZE];
extern uint32_t UART_RX_CNT;

下面是 USART1 的中断处理函数,当有数据发送过来时,就会执行这段代码:

// uart.c
uint8_t UART_RX_BUF_BIN[RX_BUFFER_SIZE] __attribute__ ((at(0X20001000)));    
uint32_t UART_RX_CNT=0;void USART1_IRQHandler(void)
{if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){uint8_t data = USART_ReceiveData(USART1);if (UART_RX_CNT < RX_BUFFER_SIZE){UART_RX_BUF_BIN[UART_RX_CNT] = data;UART_RX_CNT++;}}
}

可以看到,这里将接收到的数据放到了 UART_RX_BUF_BIN 缓冲区中,方便后面写入到 Flash 中。注意这里:

__attribute__ ((at(0X20001000)))

通过 __attribute__ 将缓冲区放到地址 0X20001000

2、iap 程序

// iap.h
typedef  int (*entry_t)(void);
#define FLASH_APP1_ADDR		0x08010000void bl_iap_load_app(uint32_t appxaddr);	
void bl_iap_write_app_bin(uint32_t appxaddr,uint8_t *appbuf,uint32_t applen);
void MSR_MSP(uint32_t addr);
// iap.c
uint32_t iapbuf[512];void board_lowlevel_deinit(void)
{/* 关闭全局中断 */__disable_irq(); /* 关闭滴答定时器,复位到默认值 */SysTick->CTRL = 0;SysTick->LOAD = 0;SysTick->VAL  = 0;/* 设置所有时钟到默认状态,使用HSI时钟 */RCC_Deinit();/* 关闭所有中断,清除所有字段挂起标志 */for (int i = 0; i < 8; ++i){NVIC->ICER[i] = 0xFFFFFFFF;NVIC->ICPR[i] = 0xFFFFFFFF;}/* 使能全局中断 */__enable_irq();
}/******************************************************************************* @brief      向Flash写入应用程序  * * @param[in]  addr    :     要写入的地址* @param[in]  buf     :     要写入的数据* @param[in]  size    :     数据大小* * @return     none* 
******************************************************************************/
void bl_iap_write_app_bin(uint32_t addr, u8 *buf, uint32_t size)
{uint32_t t;u16 i = 0;uint32_t fwaddr = addr; // 当前写入的地址for (t = 0; t < size; t += 4){iapbuf[i++] = (uint32_t)(buf[t + 3] << 24) |(uint32_t)(buf[t +2] << 16)  | (uint32_t)(buf[t + 1] << 8)  | (uint32_t)(buf[t]);if (i == 512){i = 0;bl_norflash_write(fwaddr, iapbuf, 512);fwaddr += 2048; // 偏移2048  512*4=2048}}if (i) {bl_norflash_write(fwaddr, iapbuf, i); // 将最后的一些内容字节写进去.}
}/******************************************************************************* @brief      跳转到应用程序段* * @param[in]  addr    :    用户代码起始地址* * @return     none* 
******************************************************************************/
void bl_iap_load_app(uint32_t addr)
{if ( ( ( *(volatile uint32_t *)addr ) & 0x2FFE0000 ) != 0x20000000 ) // 检查栈顶地址是否合法.{printf("Stack pointer is not valid!\r\n");return;}uint32_t _sp = *(volatile uint32_t*)(addr + 0);uint32_t _pc = *(volatile uint32_t*)(addr + 4);entry_t app_entry = (entry_t)_pc;           // 用户代码区第二个字为程序开始地址(复位地址)MSR_MSP(_sp);  							    // 初始化APP堆栈指针board_lowlevel_deinit();					// 关中断app_entry();                                // 跳转到APP.
}__asm void MSR_MSP(uint32_t addr)
{MSR MSP, r0;BX r14;
}

有一点需要注意,在由 IAP 跳转到 APP 时, 一定注意把 IAP 中开启的外设全部关闭(包括 SysTick 中断),否则在刚进入 APP 中时,如果产生中断将导致死机等问题。

3、内部 flash 读写

// flash.h
#define STM32_FLASH_BASE 0x08000000 	//STM32 FLASH的起始地址uint32_t bl_norflash_read_word(uint32_t addr);
void bl_norflash_write(uint32_t write_addr,uint32_t *data,uint32_t size);	
void bl_norflash_read(uint32_t read_addr,uint32_t *data,uint32_t size);

内部 flash 的读写操作比较简单。不过,需要注意的是,写操作要注意写之前要保证是没有写过的区域即可。

typedef struct
{uint32_t sector;uint32_t size;
} sector_desc_t;// stm32f4 每个分区的大小描述
static const sector_desc_t sector_descs[] =
{{FLASH_Sector_0, 16 * 1024},{FLASH_Sector_1, 16 * 1024},{FLASH_Sector_2, 16 * 1024},{FLASH_Sector_3, 16 * 1024},{FLASH_Sector_4, 64 * 1024},{FLASH_Sector_5, 128 * 1024},{FLASH_Sector_6, 128 * 1024},{FLASH_Sector_7, 128 * 1024},{FLASH_Sector_8, 128 * 1024},{FLASH_Sector_9, 128 * 1024},{FLASH_Sector_10, 128 * 1024},{FLASH_Sector_11, 128 * 1024},
};uint32_t bl_norflash_read_word(uint32_t faddr)
{return *(volatile uint32_t *)faddr;
}/******************************************************************************* @brief      获取某个地址所在的flash扇区* * @param[in]  addr     :   flash地址* * @return     uint16_t :   0~11,即addr所在的扇区* 
******************************************************************************/
static uint16_t bl_norflash_get_flash_sector(uint32_t addr)
{uint32_t address = STM32_FLASH_BASE;for (uint16_t sector = 0; sector < sizeof(sector_descs) / sizeof(sector_desc_t); ++sector){if (addr < address + sector_descs[sector].size) {return sector_descs[sector].sector;}address += sector_descs[sector].size;}printf("Flash sector not found!");return FLASH_Sector_11;
}/******************************************************************************* @brief 	   从指定地址开始写入指定长度的数据* * @param[in]  write_addr    :    起始地址(此地址必须为4的倍数!!)* @param[in]  data  	     :    要写入的数据* @param[in]  size    		 : 	  写入数据的大小* * @return     none* * @note       1. 该函数对OTP区域也有效!可以用来写OTP区(0X1FFF7800~0X1FFF7A0F)!* 			   2. 因为STM32F4的扇区太大了,没办法本地保存扇区数据,所以本函数*                写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以*                写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里*                没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写.*    
******************************************************************************/
void bl_norflash_write(uint32_t write_addr, uint32_t *data, uint32_t size)
{if (write_addr < STM32_FLASH_BASE || write_addr % 4) {  // 非法地址printf("Please check the WriteAddr!");return;}					 FLASH_Status status = FLASH_COMPLETE;uint32_t addr_begin = 0;uint32_t addr_end = 0;FLASH_Unlock();				 // 解锁FLASH_DataCacheCmd(DISABLE); // FLASH擦除期间,必须禁止数据缓存/*****************************************************************************/addr_begin = write_addr;					  // 写入的起始地址addr_end = write_addr + size * 4; 	  // 写入的结束地址if (addr_begin < 0X1FFF0000)				  // 只有主存储区,才需要执行擦除操作!!{while (addr_begin < addr_end) // 扫清一切障碍.(对非FFFFFFFF的地方,先擦除){if (bl_norflash_read_word(addr_begin) != 0XFFFFFFFF) // 有非0XFFFFFFFF的地方,要擦除这个扇区{status = FLASH_EraseSector(bl_norflash_get_flash_sector(addr_begin), VoltageRange_3); // VCC=2.7~3.6V之间!!if (status != FLASH_COMPLETE) {printf("Flash erase error!");break; // 发生错误了}}elseaddr_begin += 4;}}if (status == FLASH_COMPLETE){while (write_addr < addr_end) // 写数据{if (FLASH_ProgramWord(write_addr, *data) != FLASH_COMPLETE) // 写入数据{printf("Flash write error!");break; // 写入异常}write_addr += 4;data++;}}/*****************************************************************************/FLASH_DataCacheCmd(ENABLE); // FLASH擦除结束,开启数据缓存FLASH_Lock();				// 上锁
}/******************************************************************************* @brief 	   从指定地址开始读出指定长度的数据* * @param[in]  read_addr    :    起始地址  * @param[in]  data  	    :    存放读取数据* @param[in]  size    		:    要读取数据的大小* * @return     none* 
******************************************************************************/
void bl_norflash_read(uint32_t read_addr, uint32_t *data, uint32_t size)
{if (read_addr < STM32_FLASH_BASE || data == NULL || size == 0){printf("Please check the ReadAddr or the size!");return;}uint32_t i;for (i = 0; i < size; i++){data[i] = bl_norflash_read_word(read_addr); // 读取4个字节.read_addr += 4;							  // 偏移4个字节.}
}

4、main 程序

下面是 main 函数逻辑:

  1. Bootloader 等待 10s
    1. 10s 内如果没有通过串口发送 “yes”,则自动引导进入用户程序
    2. 如果发送了 ”yes“,则会等待用户发送新的固件
      • 等待固件发送完成后,先判断改固件地址信息是否准确
      • 正确则继续执行将其写入 Flash
      • 最后进入用户程序
	uint32_t time = 0;      // 计时(10s)uint32_t oldcount = 0;  // 旧的串口接收数据值uint32_t applenth = 0;  // 接收到的app代码长度uint8_t start_flag = 0; // 开始标志start_printf();while (1){if (UART_RX_CNT && start_flag == 0){if (UART_RX_BUF_BIN[0] == 'y' && UART_RX_BUF_BIN[1] == 'e' && UART_RX_BUF_BIN[2] == 's'){start_flag = 1;printf("请发送更新固件包\r\n");}UART_RX_CNT = 0;}if (UART_RX_CNT && start_flag == 1){if (oldcount == UART_RX_CNT) // 新周期内,没有收到任何数据,认为本次数据接收完成.{applenth = UART_RX_CNT;oldcount = 0;UART_RX_CNT = 0;printf("用户程序接收完成!\r\n");printf("程序包长度: %d Bytes\r\n", applenth);if (applenth){printf("开始更新固件包......\r\n");if ( ( ( *(__IO uint32_t *)(0X20001000 + 4) ) & 0xFF000000 ) == 0x08000000 ) // 判断是否为0X08XXXXXX.{bl_iap_write_app_bin(FLASH_APP1_ADDR, UART_RX_BUF_BIN, applenth); // 更新FLASH代码printf("地址为(0X20001000 + 4): %X\r\n", *(__IO uint32_t *)(0X20001000 + 4));printf("固件包更新完成\r\n");}else{printf("地址错误: %X!!!\r\n", *(__IO uint32_t *)(0X20001000 + 4));}}if ( ( ( *(__IO uint32_t *)(FLASH_APP1_ADDR + 4) ) & 0xFF000000 ) == 0x08000000 ) // 判断是否为0X08XXXXXX.{printf("开始执行Flash应用程序\r\n");bl_iap_load_app(FLASH_APP1_ADDR); // 执行FLASH APP代码}else{printf("地址错误: %X\r\n!!!", *(__IO uint32_t *)(FLASH_APP1_ADDR + 4));}}elseoldcount = UART_RX_CNT;}time++;bl_delay_ms(10);if (time % 100 == 0 && start_flag == 0)printf("倒计时 %d s......\r\n", 11 - time / 100);if (time >= 1000)time = 1000;if (time == 1000 && start_flag == 0){printf("开始运行应用程序\r\n");if ( ( ( *(__IO uint32_t *)(FLASH_APP1_ADDR + 4) ) & 0xFF000000 ) == 0x08000000 ) // 判断是否为0X08XXXXXX.{printf("开始执行FLASH应用程序\r\n");bl_iap_load_app(FLASH_APP1_ADDR); // 执行FLASH APP代码}else{printf("地址错误!!!\r\n");}}}

逻辑比较简单,就不多说了。

下面写一个用户程序来验证一下:

int main(void)
{NVIC_SetPriorityGrouping(NVIC_PriorityGroup_4);/***********************************/NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x10000);/***********************************/RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);bl_led_init();bl_uart_init();bl_tim4_init();printf("\r\n");TIM4_Init(1000 - 1, 8400 - 1);return 0;
}void TIM4_IRQHandler(void)
{if(TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)  //溢出中断{bl_led_toggle(GPIO_Pin_5);TIM_ClearITPendingBit(TIM4, TIM_IT_Update);  //清除中断标志位}  printf("timer 4\r\n");
}

一定要注意这段代码:

NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x10000);

这里设置了用户程序的中断向量表的偏移地址为 0x10000,如果不设置这个偏移地址,就无法进入定时器4 中断服务函数,LED 就不会闪烁。

另外,同时还要注意设置好用户程序的地址。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com