STM32Cube 学习之十五:SDIO+FATFS+IAP
为了简单起见,本篇的实验在上一篇例程基础上进行修改。
本篇例程参考了正点原子的“串口 IAP 实验”,在此声明感谢!
第一部分:IAP 程序
Step1.在用户代码区 0 增加一个凼数,设置 PA0 为输入,作为按键输入接口,PF9、PF10 作为输出,
控制 LED。并在 main 凼数中用户代码区 2 中调用。
Step2.修改 main 凼数。以下是 main 凼数的全部代码。
main 凼数的基本流程是,上电后 LED 闪烁表示 IAP 程序正在运行,10 秒之内如果有按键按下,
则从 SD 卡读取根目录下的 APP_code.bin 文件,烧写到 APP_ADDRESS_IN_FLASH 地址中,然后跳
转执行 APP。如果没有按键按下,则 10 秒后自动跳转执行 APP。
其中和 IAP 相关操作有两个关键凼数,即 iap_write_appbin()和 iap_load_app(),一个用于更新
APP,一个用于加载 APP。这两个凼数在 iap.c 和 iap.h 中实现。
Step3.实现 IAP 凼数。
iap.h 文件内容:
#ifndef __IAP_H__
#define __IAP_H__
#include "stm32f4xx_hal.h"
typedef void (*iapfun)(void);
void iap_load_app(uint32_t appxaddr);
uint8_t iap_write_appbin(const uint32_t appxaddr, const char *fname);// 在指定地址开始,写入 bin
#endif
// 定义一个函数类型的参数.
// 跳转到 APP 程序执行
iap.c 文件内容:
MSR MSP, r0 // set Main Stack value
BX r14
#include "stm32f4xx_hal.h"
#include "iap.h"
#include "stmflash.h"
#include "ff.h"
// 设置栈顶地址
// addr:栈顶地址
__asm void MSR_MSP(uint32_t addr)
{
}
iapfun jump2app;
/*
功能:从 SD 卡读取.bin 文件,写入指定 FLASH 地址中。
输入:appxaddr,应用程序在 FLASH 中的起始地址;fname,应用程序 bin 文件名.
返回:擦除或写入失败,返回 1;打开文件失败,返回 2;所有操作成功,返回 0.
*/
uint8_t iap_write_appbin(const uint32_t appxaddr, const char *fname)
{
FRESULT res;
FIL xFile;
uint32_t real_read_len;
union {
uint8_t dat8[2048];
uint32_t dat32[512]; // 2K 字节缓存
}iapbuf;
uint8_t res2;
uint32_t t;
uint32_t fwaddr;
uint32_t appsize;
res = f_open(&xFile, fname, FA_READ); // 打开 APP bin 文件
if( FR_OK != res ) { // 如果失败,返回 2
return 2;
}
appsize = f_size(&xFile); // 获取文件大小
res2 = STMFLASH_Erase(appxaddr, appxaddr + appsize); // 擦除 FLASH
if (res2) { // 如果失败,返回 1
f_close(&xFile);
return 1;
}
fwaddr = appxaddr; //当前写入的地址
res = f_read(&xFile, iapbuf.dat8, 2048, &real_read_len); // 一次读 2048 字节
if (FR_OK == res) {
if (2048 == real_read_len) {
res2 = STMFLASH_Write(fwaddr, iapbuf.dat32, 512);
} else {
res2 = STMFLASH_Write(fwaddr, iapbuf.dat32, (real_read_len >> 2));
for(t=0; t < appsize; t += 2048)
{
}
if (res2) {
f_close(&xFile);
return 1;
}
fwaddr += 2048; // 偏移 2048
} else {
f_close(&xFile);
return 1;
}
}
f_close(&xFile);
return 0;
}
/*
功能:跳转到应用程序段
输入:appxaddr,用户代码起始地址
*/
void iap_load_app(uint32_t appxaddr)
{
if(((*(__IO uint32_t*)appxaddr) & 0x2FFE0000) == 0x20000000) { // 检查栈顶地址是否合法.
jump2app = (iapfun)*(__IO uint32_t*)(appxaddr+4);
MSR_MSP(*(__IO uint32_t*)appxaddr); // 初始化 APP 堆栈指针(用户代码区的第一个字用于存放栈顶地
// 用户代码区第二个字为程序开始地址(复位地址)
址)
jump2app();
}
}
其中,有两个 FLASH 操作的函数,STMFLASH_Erase()和 STMFLASH_Write(),分别是擦除和写入操作。
// 跳转到 APP.
这两个函数在 stmflash.c 和 stmflash.h 中实现。
Step4.实现 FLASH 操作凼数。
stmflash.h 文件内容:
#ifndef __STMFLASH_H__
#define __STMFLASH_H__
#include "stm32f4xx_hal.h"
//FLASH 扇区的起始地址
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000)
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000)
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000)
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000)
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000)
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000)
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000)
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000)
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000)
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000)
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000)
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000)
//FLASH 扇区的大小
//扇区 0 起始地址, 16 Kbytes
//扇区 1 起始地址, 16 Kbytes
//扇区 2 起始地址, 16 Kbytes
//扇区 3 起始地址, 16 Kbytes
//扇区 4 起始地址, 64 Kbytes
//扇区 5 起始地址, 128 Kbytes
//扇区 6 起始地址, 128 Kbytes
//扇区 7 起始地址, 128 Kbytes
//扇区 8 起始地址, 128 Kbytes
//扇区 9 起始地址, 128 Kbytes
//扇区 10 起始地址,128 Kbytes
//扇区 11 起始地址,128 Kbytes
#define FLASH_SECTOR_0_SIZE ((uint32_t)0x4000) //扇区 0, 16 Kbytes
#define FLASH_SECTOR_1_SIZE ((uint32_t)0x4000) //扇区 1, 16 Kbytes
#define FLASH_SECTOR_2_SIZE ((uint32_t)0x4000) //扇区 2, 16 Kbytes
#define FLASH_SECTOR_3_SIZE ((uint32_t)0x4000) //扇区 3, 16 Kbytes
#define FLASH_SECTOR_4_SIZE ((uint32_t)0x10000)
#define FLASH_SECTOR_5_SIZE ((uint32_t)0x20000)
#define FLASH_SECTOR_6_SIZE ((uint32_t)0x20000)
#define FLASH_SECTOR_7_SIZE ((uint32_t)0x20000)
#define FLASH_SECTOR_8_SIZE ((uint32_t)0x20000)
#define FLASH_SECTOR_9_SIZE ((uint32_t)0x20000)
#define FLASH_SECTOR_10_SIZE ((uint32_t)0x20000)
#define FLASH_SECTOR_11_SIZE ((uint32_t)0x20000)
// 擦除指定地址空间的内容,注意擦除是按扇区操作的
uint8_t STMFLASH_Erase(uint32_t st_addr, uint32_t end_addr);
// 从指定地址开始写入指定长度的数据
uint8_t STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite);
#endif
//扇区 4, 64 Kbytes
//扇区 5, 128 Kbytes
//扇区 6, 128 Kbytes
//扇区 7, 128 Kbytes
//扇区 8, 128 Kbytes
//扇区 9, 128 Kbytes
//扇区 10,128 Kbytes
//扇区 11,128 Kbytes
stmflash.c 文件内容:
if(addr
//非法地址
HAL_FLASH_Unlock();
//解锁
uint32_t PAGEError = 0;
HAL_FLASH_Lock(); //上锁
if((WriteAddr < FLASH_BASE) || (WriteAddr % 4))return 1;
for (i=0; i < NumToWrite; i++) {
// 写入单位,WORD,即 4 字节
if (HAL_OK != HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr, pBuffer[i])) return 1;
WriteAddr += 4; // 每次写 4 字节,因此地址增加 4
}
return 0;
}
/*
功能:擦除从 st_addr 到 end_addr 的 FLASH 空间。
输入:st_addr,起始地址,必须为 4 的整数倍;end_addr,结束地址。
返回:正确返回 0;错误返回 1。
*/
uint8_t STMFLASH_Erase(uint32_t st_addr, uint32_t end_addr)
{
FLASH_EraseInitTypeDef EraseInitStruct;
if((st_addr < FLASH_BASE) || (st_addr % 4))return 1;
st_sector = STMFLASH_GetFlashSector(st_addr); // 起始扇区
end_sector = STMFLASH_GetFlashSector(end_addr); // 结束扇区
return 0;
}
HAL_FLASH_Unlock();
if (HAL_OK != HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError)) { // 擦除 FLASH
}
HAL_FLASH_Lock(); //上锁
uint8_t st_sector=0, end_sector=0;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInitStruct.Banks
EraseInitStruct.Sector = st_sector;
EraseInitStruct.NbSectors = end_sector - st_sector + 1;
EraseInitStruct.VoltageRange= FLASH_VOLTAGE_RANGE_3;
= FLASH_BANK_1;
//非法地址
//解锁
return 1;
Step5. 定义 APP 数组。
因为要在 FLASH 中划出空间存放 APP 代码,所以要定义一个 const 数组,并指定其在 FLASH 中
的空间。该步骤在 APP_code.c 和 APP_code.h 文件中实现。
APP_code.h 文件内容:
APP_code.c 文件内容:
本例中,如上代码将 APP_code 数组定义到扇区 4 为起始地址,数组大小为 320k,即 APP 最大
可用空间为 320k,占用扇区 4、5、6。而扇区 0~3 留给 IAP 程序或者其他用途。
至此,IAP 程序设计完成。目前,APP 代码为空。在完成 APP 并生成相应 bin 文件之后,用
winhex 转换成 C 代码格式,复制到 APP_code[]数组,即可将 APP 代码和 IAP 代码一起烧录。
第二部分:APP 程序
APP 程序只要在普通程序基础上进行几个简单修改即可。下面以 UART 串口为例。
Step1.创建 UART 程序:详细过程请参考本系列笔记第二篇。
Step2.添加串口发送代码。
添加如上代码,为后面实验方便,使用预编译处理,通过修改红框中的条件决定串口输出内容。
条件为 0 输出 Hello World!,条件丌为 0 输出 Hello!。
Step3. 设置 APP 程序起始地址。
打开目标选项设置窗口,在 Target 页面 IROM1 中,将 Start 地址设置成 APP 地址,本例中为
0x80100000,即扇区 4 的地址。Size 地址进行相应的修改,Start 从原来的 0x80000000 改为
0x80100000,增加了 0x100000,Size 要减小 0x100000。本例中改为 0x700000。
Step4.设置向量表位置。
在 main 凼数的开始处设置向量表位置,如下图:
这里设置的向量表位置必须和 IAP 凼数给 APP 指定的位置一致。
Step5.设置编译链接转换工具。
打开目标选项设置窗口,在 User 页面中如下图,勾选 Run#1,并在命令栏输入转换工具路径即
命令”D:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o OBJ\APP_code.bin UART\UART.axf”。
其中后面的.axf 文件名称必须和 Output 页面的设置一致,如下图。而.bin 文件是输出文件,路
径和名称可以是任意的,本例输出到本工程目录下的 OBJ 文件夹(如果没有就自动创建),输出文件
名称为 APP_code.bin。