完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
MCU:STM32F107
开发环境:CubeMX V6.1.1+Keil MDK V5.26 一。使用CubeMX配置STM32 1.FreeRTOS配置,如果只是做一些验证或者测试,基本上使用默认配置即可: 2.USB外设配置: USB_OTG配置为USB_HOST: 开启USB中断,注意中断优先级设为5,有时默认的中断优先级为0,由于引入RTOS处理USB且RTOS管理的中断优先级为5级以下,0优先级的USB中断会导致程序卡死: 配置USB_HOST所支持的设备类型。由于我的程序内USB还做通讯使用,所以配置支持所有类设备,如果只做U盘读取,配置支持MSC类即可,需要注意的是USB任务堆栈别太小,否则读写U盘时容易卡死: 3.FATFS配置: 有了USB接口、有了U盘,那么剩下的就是使用文件系统来读写U盘了,所以我们对U盘的读写都是通过FATFS文件系统的API来完成的。 同样,几乎使用默认配置即可,不过要想支持中文,CODE_PAGE一定要设为GBK简体中文。 其他的一些外设配置、时钟配置不多说,最后生成代码前,要把系统的堆栈空间设大一些,否则同样容易卡死,或者大容量的U盘可能会无法正常识别: 点击生成代码,剩下的就得啃代码了。 二.U盘IAP原理 IAP就是让我们能够脱离下载器(例如常用的ST-Link,J-Link等),在MCU的Flash里写入新的程序。常见的iap方式有串口iap、网口iap、U盘iap等,今天我们要实现的的就是U盘iap方式。这几种iap方式,都是通过一个加载程序,将要更新的程序文件(一般为BIN文件)写入到Flash中,我们称这个加载程序为Bootloader(简称BOOT)程序,要更新的程序称为Application(简称APP)程序,BOOT只负责加载,APP实现我们的应用功能。所以,带有IAP功能的单片机其实运行的是有两个程序,BOOT+APP,这两个程序都在Flash中,BOOT程序放在Flash的起始地址,APP程序放到BOOT程序地址后面,上电后单片机先运行的是BOOT程序,在BOOT程序里判断是否需要更新APP程序,如果需要更新就把更新文件写入到存放APP程序地址的Flash中,如果不需要更新就直接跳转至APP程序。 例如手上用到的这个单片机Flash内存大小为128KB,Flash地址为0x8000000~0x8020000。BOOT占用内存大小为32KB,起始地址0X8000000,也就是MCU的Flash起始地址;APP程序占用94KB的内存,起始地址为0x8008800,也就是BOOT内存区域的终点地址。上电后程序先从0x8000000开始运行BOOT程序,如果不要更新程序就跳转至0x8008800继续运行APP程序,所以从BOOT程序切换至APP程序就是一个PC指针跳转的过程,至于如何跳转,后面介绍。 U盘iap的方式,U盘就是充当扩展的外部内存的角色,存放需要更新的BIN文件,程序更新过程中MCU先将U盘里的BIN文件读取到MCU内部的Ram中,然后再将Ram中的数据写入到Flash中,过程如下: 如果更新文件比较大,超过MCU的RAM大小,可以将更新文件分割成若干部分,然后从前往后依次读写即可。 三.U盘IAP过程 1.对MCU内存进行分区。前面讲到要想实现IAP那么MCU就得运行两个程序,要想运行两个程序就得对MCU内存分区来存放这两个程序。使用MDK很容易实现MCU内存分区,如下图: 这个Start地址就是当前程序要下载到Flash中的起始地址,前面讲到APP的起始地址为0x808800,所以这儿就设置为0x808800. 2.设置APP程序的向量表偏移地址。 由于我们程序的起始地址改了,那么中断向量的地址当然也要跟着改变,否则程序无法正常进入中断,而中断地址的改变就是通过向量偏移量来实现的,程序的起始地址从0x8000000变为0x8008800,地址偏移了0x8800,那么中断向量同样偏移了0x8800,所以定义VECT_TAB_OFFSET为0x8800即可。新版的CubeMX重定义偏移量是需要先定义USER_VECT_TAB_ADDRESS。BOOT程序的地址还是从0x8000000开始的,无需设置中断向量偏移。 3.编写BOOT程序里的Flash读写函数。 MCU内部Flash的读写不像读写Ram那样简单,需要对Flash进行相关配置,Flash的擦写函数以及初始化函数参照了官方BSP里的IAP代码: /*----------------------------------------------------------------------------- * 函 数 名 : FLASH_If_Init() * 函数功能 : Flash初始化 * 输 入 : 无 * 输 出 : 无 -----------------------------------------------------------------------------*/ void FLASH_If_Init(void) { HAL_FLASH_Unlock(); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR); HAL_FLASH_Lock(); } /*----------------------------------------------------------------------------- * 函 数 名 : FLASH_If_Erase() * 函数功能 : Flash擦除函数 * 输 入 : 无 * 输 出 : 无 -----------------------------------------------------------------------------*/ uint32_t FLASH_If_Erase(uint32_t start) { uint32_t NbrOfPages = 0; uint32_t PageError = 0; FLASH_EraseInitTypeDef pEraseInit; HAL_StatusTypeDef status = HAL_OK; HAL_FLASH_Unlock(); NbrOfPages = (USER_FLASH_END_ADDRESS - start)/PAGE_SIZE; pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES; pEraseInit.PageAddress = start; pEraseInit.Banks = FLASH_BANK_1; pEraseInit.NbPages = NbrOfPages; status = HAL_FLASHEx_Erase(&pEraseInit, &PageError); HAL_FLASH_Lock(); if (status != HAL_OK) { return FLASHIF_ERASEKO; } return FLASHIF_OK; } /*----------------------------------------------------------------------------- * 函 数 名 : FLASH_If_Write() * 函数功能 : Flash写函数 * 输 入 : destination:当前要写入的Flash起始地址 p_source:指向储存的待更新数据 length:本次写入Flash的数据长度 * 输 出 : 无 -----------------------------------------------------------------------------*/ uint32_t FLASH_If_Write(uint32_t destination, uint32_t *p_source, uint32_t length) { uint32_t i = 0; HAL_FLASH_Unlock(); for (i = 0; (i 《 length) && (destination 《= (USER_FLASH_END_ADDRESS-4)); i++) { if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, destination, *(uint32_t*)(p_source+i)) == HAL_OK) { if (*(uint32_t*)destination != *(uint32_t*)(p_source+i)) { return(FLASHIF_WRITINGCTRL_ERROR); } destination += 4; } else { return (FLASHIF_WRITING_ERROR); } } AppAddresssCurren = destination; HAL_FLASH_Lock(); return (FLASHIF_OK); } 这里用到AppAddresssCurren这个变量来记录本次Flash写入数据后的地址,以便下次写Flash时就从这个地址开始继续往后面写。 4.读取U盘内的更新文件,写入MCU的Flash中。 有了Flash的操作函数后,剩下的就是读取U盘里的更新文件,然后写入到Flash中,函数如下: #define PAGE_SIZE ((uint16_t)0x400) /*1 Kbytes Size of page*/ #define FLASH_SIZE ((uint32_t)0x20000) /*128 KBytes Flash*/ #define APPLICATION_ADDRESS ((uint32_t)0X08008800) #define USER_FLASH_END_ADDRESS ((uint32_t)0x08020000) #define USER_FLASH_SIZE ((uint32_t)0x00017800) /*94 KBytes User flash size*/ #define RAM_BUFFER_SIZE ((uint32_t)50*1024) /*KBytes*/ /*----------------------------------------------------------------------------- * 函 数 名 : AppWrite() * 函数功能 : U盘IAP函数 * 输 入 : 无 * 输 出 : 无 -----------------------------------------------------------------------------*/ uint8_t RAM_Buffer[RAM_BUFFER_SIZE] ={0x00}; void AppWrite(void) { FRESULT res; retUSBH = FATFS_LinkDriver(&USBH_Driver, USBHPath); AppAddresssCurren = APPLICATION_ADDRESS; res = f_mount(&USBHFatFS, (TCHAR const*)USBHPath, 0); if(res != FR_OK) { Error_Handler(); } res = f_open(&USBHFile,“TestApp1.BIN”, FA_READ); if(res != FR_OK) { Error_Handler(); } res = f_read(&USBHFile, RAM_Buffer, sizeof(RAM_Buffer), (void *)&APP_Size); if(res != FR_OK) { Error_Handler(); } if(((10*1024)《APP_Size) && (APP_Size《RAM_BUFFER_SIZE)) { FLASH_If_Erase(APPLICATION_ADDRESS); FLASH_If_Write(AppAddresssCurren, (uint32_t*) RAM_Buffer, APP_Size/4); } f_close(&USBHFile); FATFS_UnLinkDriver(USBHPath); } 这里我们定义了一个RAM_Buffer[RAM_BUFFER_SIZE]数组来存放读取的BIN文件,数组的大小根据时间的Ram大小决定,我这里是64KB的所以定义了50KB的Ram来读取BIN文件,然后将读取的BIN文件通过FLASH_If_Write()函数写入Flash中,完成外部新的程序的更新至Flash中。 5.跳转。 前面说了从BOOT切换至APP程序其实就是PC指针的一次跳转,这个跳转通过下面这个函数实现的,参照官方BSP里的IAP例程: typedef void (*pFunction)(void); pFunction JumpToApplication; /*----------------------------------------------------------------------------- * 函 数 名 : JumpToAPP() * 函数功能 : 程序跳转函数 * 输 入 : 无 * 输 出 : 无 -----------------------------------------------------------------------------*/ void JumpToAPP(void) { __disable_irq(); if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000) { JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4); JumpToApplication = (pFunction) JumpAddress; __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); JumpToApplication(); } } 在跳转前需要调用__disable_irq()函数来关闭中断,否则如果BOOT里开的有中断(比如说我这里开的有定时中断和串口中断),跳转至APP后会卡死。 /*****************************************************************************/ 到这里我们整个IAP流程就讲完了,自己动手做一做还是蛮简单的。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1752 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1611 浏览 1 评论
1052 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
721 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1666 浏览 2 评论
1924浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
711浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
560浏览 3评论
583浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
544浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-18 13:29 , Processed in 0.828470 second(s), Total 77, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号