本帖最后由 xiong57785 于 2023-4-2 14:25 编辑
@安小芯 @21IC 感谢国民技术举办的项目,感谢21ic提供的平台,让我学到不少知识,很抱歉,最后一天才提交,抱歉抱歉!话不多说,开始正题。
基于国民技术N32G45x的SD卡IAP升级开发活动
1. 活动规则功能: 1. 插入储存固件的SD卡 2. 自动/手动完成固件升级,通过LED灯或者打印反馈升级结果 要求:基于N32G45x系列芯片完成上述功能,提交内容需要包含详细的文字或者图片描述,如原理讲解、测试环境、操作流程、代码配置、代码演示、应用场景等,必要时需要视频演示成果。 2 IAP升级的原理
a. 不包含boot的启动流程 N32G45x 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入。此外 N32G45x 是基于 Cortex-M4F 内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临,N32G45x 的内部硬件机制亦会自动将 PC 指针定位到中断向量表处,并根据中断源取出对应的中断向量执行中断服务程序。 如上图,N32G45x 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号 ① 所示;在复位中断服务程序执行完之后,会跳转到 main 函数,如图标号 ② 所示;而 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断),此时 N32G45x 强制将 PC 指针指回中断向量表处,如图标号 ③ 所示;然后,根据中断源进入相应的中断服务程序,如图标号 ④ 所示;在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号 ⑤ 所示。这些跳转逻辑的代码就是在startup_n32g45x中实现的,举例如下: __Vectors DCD __initial_sp ; Top of Stack // 对应0x8000000,栈顶
DCD Reset_Handler ; Reset Handler // 对应复位中断向量表
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
.....
; Reset handler
Reset_Handler PROC ; // 对应复位中断程序入口地址
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main ;// 跳转到__main函数,__main函数中会调用main函数
BX R0
ENDP
b. 包含boot的启动流程 N32G45x 复位后,还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号 ① 所示,此部分同正常执行的程序一样;在执行完 IAP 以后(即将新的 APP 代码写入 N32G45x 的 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号 ② 和 ③ 所示,同样 main 函数为一个死循环,并且注意到此时 N32G45x 的 FLASH,在不同位置上,共有两个中断向量表。 在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍强制跳转到地址 0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号 ⑤ 所示;(这里如果我们没有设置新的中断向量表,则就会跳到boot的中断向量表,执行boot的函数,从而程序就会出错)程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号 ⑥ 所示;在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号 ⑧所示。 综上:boot中需设计跳转到app,app中需要设计中断向量表的位置 。 boot跳转代码: static int Goto_App(uint32_t add_address)
{
typedef void (*pfun)(void);
pfun jum_to_app = NULL;
/* Check if the stack top pointer is in the range 0x20000000 - 0x20024000 */
if (((*(__IO uint32_t*)add_address) <= 0x20024000) && ((*(__IO uint32_t*)add_address) >= 0x20000000))
{
/* disable irq */
__disable_irq();
/* Jump to user application */
jum_to_app = (pfun) *(__IO uint32_t*) (add_address + 4);
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) add_address);
jum_to_app();
}
else
{
printf("Error: Stack top address is 0x%x\n", (*(__IO uint32_t*)add_address));
}
return -1;
}
app中重新设计中断向量表: #ifdef APP_WITH_BOOT
SCB->VTOR = FLASH_BASE | APP_START_ADDR;
#endif
app中设置程序的起始地址,可以通过修改keil中下图:
图2-3 :启动地址设置
也可以通过修改分散加载文件的方法:
LR_IROM1 0x08011000 0x00010000 { ; load region size_region
ER_IROM1 0x08011000 0x00010000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00024000 { ; RW data
.ANY (+RW +ZI)
}
}
主控芯片采用的是N32G455VEL7,其内核是ARM Cortex-M4F, 主频144MHz, 内部flash为512KB,SRAM 144KB。 软件编译采用MDK-ARM V5.23,画图采用:A**D V22.0.2 。 SD卡采用MicroSD卡,也就是手机上用的TF卡(目前手头上只有2GB SD卡)。 SD接口采用SDIO方式,电路图如下:
花了亿点点时间画个电路板,打样,贴片,实物如下(修修补补,焊接的不太好,冏):
图3-3 实物图1
图3-4 实物图2
这个版本的电路ch340有点问题,可能还需要再打一板,修改后的电路图放附件了,有需要的自取。@—@. 4 软件实现4.1 软件需求实现通过SD卡完成系统升级,升级过程中出现任何问题,在不采用线刷的情况下,保证系统可恢复。 4.2 软件方案(操作流程)• 系统每次复位或重启,Boot中一开始就检测是否插入SD卡,如果插入SD卡,且升级文件(APP.bin)存在,则升级,升级完成后,启动系统。 这个方案比较简单,存储flash被分为Boot区域+Flag区域+APP区域。 图4-1 存储划分 Boot中一开始就检测按键KEY1是否被按下,是否插入SD卡,如果按键KEY1被按下且插入SD卡,且升级文件(APP.bin)存在,则升级,升级完成后,这里没有直接跳转APP,因为APP需要一个比较干净的启动环境(gpio都复位,中断清零,释放关闭等等),这里为了方便就重新软复位,然后跳转到APP。
图4-2 流程图
app中只需要设置中断向量表即可。 4.3 代码配置存储配置: 为了方便测试:Boot和APP都分配了64KFlash,Flag分配了4KFlash。 Boot: 0x08000000 - (0x08010000-1) Flag: 0x08010000 - (0x08011000-1)
App: 0x08110000 - (0x08021000-1) 4.3.1 bootBoot主函数: int main(void)
{
int status = 0, flag = 0;
/* LED Init */
LedInit(LED_PORT, LED1_PIN | LED2_PIN);
LedOff(LED_PORT, LED1_PIN | LED2_PIN);
/* Key Init */
KeyInit(KEY_PORT, KEY1_PIN | KEY2_PIN);
/* USART Init */
USART_Config();
/* SD scan init */
SDScanInit();
printf("\n\n=====Boot Start!=====\n");
if(KeyRead(KEY_PORT, KEY1_PIN) == 0)
{
Delay(0x28FFFF);
if(KeyRead(KEY_PORT, KEY1_PIN) == 0)
{
if(SD_UpdateApp() == 0)
{
printf("Update system OK\n");
}
else
{
printf("Update system failed\n");
}
LedOn(LED_PORT, LED1_PIN | LED2_PIN);
/* wait for key released */
while(KeyRead(KEY_PORT, KEY1_PIN) == 0);
Delay(0x28FFFF);
while(KeyRead(KEY_PORT, KEY1_PIN) == 0);
/* Software reset!!! */
NVIC_SystemReset();
}
}
flag = (*(__IO uint32_t*)(FLAG_START_ADDR));
if (flag == FLAG_APP) /* check and jump to APP */
{
printf("=====Boot End!=====\n");
status = Goto_App(APP_START_ADDR);
if(status == -1)
{
printf("Jum to app failed!\n");
Goto_Alarm();
}
}
else
{
printf("No APP, Wait for SD insert with app.bin inside!\n");
while(1)
{
if(SD_UpdateApp() == 0)
{
LedOn(LED_PORT, LED1_PIN | LED2_PIN);
printf("Update system OK!, please restart the board\n");
while(1);
}
}
}
}
SD卡升级的函数: static FATFS fs = {0};
static FILINFO fno;
static FIL fil; /* File object */
static BYTE buffer[FLASH_PAGE_SIZE] = {0}; /* File copy buffer */
static int SD_UpdateApp(void)
{
FRESULT fr;
UINT br; /* File read/write count */
int status = 0, app_flag = 0, sd_insert = 0;
uint32_t app_addr = 0;
if(KeyRead(SD_SCAN_PORT, SD_SCAN_PIN) == 0)
{
Delay(0x28FFFF);
if(KeyRead(SD_SCAN_PORT, SD_SCAN_PIN) == 0)
{
sd_insert = 1;
}
}
if(sd_insert == 0)
{
return -1;
}
/* mount file system */
fr = f_mount(&fs, "1:", 0); /* Mount a logical drive, 1:DEV_MMC */
if (fr != FR_OK)
{
printf("mount file system error!\n");
return -2;
}
/* search APP.bin */
fr = f_stat("1:app.bin", &fno);
if(fr == FR_OK)
{
printf("Start update system...\n");
fr = f_open(&fil, "1:app.bin", FA_READ);
if(fr) /* error */
{
printf("open 1:app.bin error!\n");
f_unmount("1:");
return -3;
}
status = flash_erase(FLAG_START_ADDR, 4);
if(status == -1)
{
printf("Erase flag error!\n");
f_close(&fil);
f_unmount("1:");
return -4;
}
app_addr = APP_START_ADDR;
for (;;)
{
memset(buffer, 0, FLASH_PAGE_SIZE);
fr = f_read(&fil, buffer, FLASH_PAGE_SIZE, &br); /* Read a chunk of data from the source file */
if (br == 0)
break; /* error or eof */
status = flash_write(app_addr, br, buffer);
if(status == -1)
{
printf("Wrie app(addr:0x%x) error!\n", app_addr);
f_close(&fil);
f_unmount("1:");
return -5;
}
app_addr += br;
}
app_flag = FLAG_APP;
status = flash_write(FLAG_START_ADDR, 4, (uint8_t *)&app_flag);
if(status == -1)
{
printf("reset flag app1 error!\n");
f_close(&fil);
f_unmount("1:");
return -6;
}
f_close(&fil);
f_unmount("1:");
return 0;
}
f_unmount("1:");
return -7;
}
flash擦除、写函数: int flash_write(uint32_t start_addr, uint32_t size, uint8_t *pvalue)
{
int i = 0;
/* Unlocks the FLASH Program Erase Controller */
FLASH_Unlock();
/* Erase */
for(i=0; i<size; i+=FLASH_PAGE_SIZE)
{
if (FLASH_COMPL != FLASH_EraseOnePage(start_addr + i))
{
printf("Flash EraseOnePage Error. Please Deal With This Error Promptly\r\n");
return -1;
}
}
/* Program */
for (i = 0; i < size; i += 4)
{
if (FLASH_COMPL != FLASH_ProgramWord(start_addr + i, (*(__IO uint32_t*)(pvalue + i))))
{
printf("Flash ProgramWord Error. Please Deal With This Error Promptly\r\n");
return -1;
}
}
/* Locks the FLASH Program Erase Controller */
FLASH_Lock();
/* Check */
for (i = 0; i < size; i += 4)
{
if ( (*(__IO uint32_t*)(pvalue + i)) != (*(__IO uint32_t*)(start_addr + i)))
{
printf("Flash Program Test Failed\r\n");
return -1;
}
}
return 0;
}
int flash_erase(uint32_t start_addr, uint32_t size)
{
int i = 0;
/* Unlocks the FLASH Program Erase Controller */
FLASH_Unlock();
/* Erase */
for(i=0; i<size; i+=FLASH_PAGE_SIZE)
{
if (FLASH_COMPL != FLASH_EraseOnePage(start_addr + i))
{
printf("Flash EraseOnePage Error. Please Deal With This Error Promptly\r\n");
return -1;
}
}
/* Locks the FLASH Program Erase Controller */
FLASH_Lock();
return 0;
}
4.3.2 app
主函数,比较简单,就是开头设置中断向量表,然后启动任务前打开全局中断 /**
* [url=home.php?mod=space&uid=247401]@brief[/url] Main program.
*/
int main(void)
{
#ifdef APP_WITH_BOOT
SCB->VTOR = FLASH_BASE | APP_START_ADDR;
#endif
LedInit(LED_PORT, LED1_PIN | LED2_PIN);
/*Turn off Led1, Led2*/
LedOff(LED_PORT, LED1_PIN | LED2_PIN);
KeyInit(KEY_PORT, KEY1_PIN | KEY2_PIN);
SDScanInit();
/* USART Init */
USART_Config();
OLED_Init();
printf("\n\n=====App Start!=====\n");
xQueueKey = xQueueCreate( 4, sizeof( int ) );
xTaskCreate( vLEDBlinkTask, "LEDx", ledSTACK_SIZE, NULL, ledTASK_PORI, ( TaskHandle_t * ) NULL );
xTaskCreate( vKEYScanTask, "KEY1", keySTACK_SIZE, NULL, keyTASK_PORI, ( TaskHandle_t * ) NULL );
xTaskCreate( vKEYHandleTask, "KEY2", 8*keySTACK_SIZE, NULL, keyTASK_PORI+1, ( TaskHandle_t * ) NULL );
xTaskCreate( vSDScanTask, "SDx", sdSTACK_SIZE, NULL, sdTASK_PORI, ( TaskHandle_t * ) NULL );
xTaskCreate( vOLEDTask, "OLEDx", oledSTACK_SIZE, NULL, oledTASK_PORI, ( TaskHandle_t * ) NULL );
__enable_irq();
vTaskStartScheduler();
while (1)
{
vTaskDelay( 500 );
}
}
4.3.3 移植相关
文件系统采用的是fatfs,移植就是实现diskio.c中的初始化和读写函数,具体如下: int MMC_disk_status(void)
{
return 0;
}
int MMC_disk_initialize(void)
{
Status = SD_Init(0, 3, 4);
if (Status != SD_OK)
{
log_debug("SD Card initialization failed!\r\n");
return -1;
}
// SD_Info(&SDCardInfo);
return 0;
}
int MMC_disk_read(uint8_t *buff, uint32_t sector, uint32_t count)
{
long long read_addr = 0;
int n = 0;
SD_Error Status = SD_OK;
read_addr = sector << 9; /* = sector * SD_BLOCK_SIZE */
if(((uint32_t)buff%4) != 0)
{
for(n=0; n<count; n++)
{
Status = SD_ReadBlock(Buf_RX, read_addr+SD_BLOCK_SIZE*n, SD_BLOCK_SIZE);
Status = SD_WaitReadOperation();
while (SD_GetStatus() != SD_TRANSFER_OK);
if (Status != SD_OK)
{
log_debug("SD Card read block failed!\r\n");
return -1;
}
memcpy(buff, Buf_RX, SD_BLOCK_SIZE);
buff += SD_BLOCK_SIZE;
}
}
else
{
if(count == 1)
Status = SD_ReadBlock(buff, read_addr, SD_BLOCK_SIZE);
else
Status = SD_ReadMultiBlocks(buff, read_addr, SD_BLOCK_SIZE, count);
Status = SD_WaitReadOperation();
while (SD_GetStatus() != SD_TRANSFER_OK);
if (Status != SD_OK)
{
log_debug("SD Card read block failed!\r\n");
return -1;
}
}
return 0;
}
int MMC_disk_write(const uint8_t *buff, uint32_t sector, uint32_t count)
{
long long write_addr = 0;
int n = 0;
SD_Error Status = SD_OK;
write_addr = sector << 9; /* = sector * SD_BLOCK_SIZE */
if(((uint32_t)buff%4) != 0)
{
for(n=0; n<count; n++)
{
memcpy(Buf_TX, buff, SD_BLOCK_SIZE);
Status = SD_WriteBlock(Buf_TX, write_addr+SD_BLOCK_SIZE*n, SD_BLOCK_SIZE);
buff += SD_BLOCK_SIZE;
Status = SD_WaitWriteOperation();
while (SD_GetStatus() != SD_TRANSFER_OK);
if (Status != SD_OK)
{
log_debug("SD Card write block failed!\r\n");
return -1;
}
}
}
else
{
if(count == 1)
Status = SD_WriteBlock((uint8_t *)buff, write_addr, SD_BLOCK_SIZE);
else
Status = SD_WriteMultiBlocks((uint8_t *)buff, write_addr, SD_BLOCK_SIZE, count);
while (SD_GetStatus() != SD_TRANSFER_OK);
if (Status != SD_OK)
{
log_debug("SD Card write block failed!\r\n");
return -1;
}
}
return 0;
}
app中还移植了freertos,可以参照之前的一篇文章:【N32G430开发板试用】体验+移植Freertos+开关机记录 - - 21ic电子技术开**坛
4.4 其他解释
a.如何确定boot的大小是否合适
关于boot和app的大小如何设置,解释如下: 先编写了个Boot,编译得到boot.map, 最下面内容如下: Total RO Size (Code + RO Data) 2848 (2.78kB)
Total RW Size (RW Data + ZI Data) 5384 ( 5.26kB)
Total ROM Size (Code + RO Data + RW Data) 2856 ( 2.79kB)
Total ROM Size就是所占用的Flash大小,不到3k,据此设定boot的flash大小分配为8k, 即Start=0x08000000,Size=0x2000 flag区域设计为4k(根据flashpage为0x800,想当然设置为page的两倍O(∩∩)O,反正flash够大), 即:Start=0x08002000,Size=0x1000, app编译后,查看app.map如下: Total RO Size (Code + RO Data) 10140 (9.90kB)
Total RW Size (RW Data + ZI Data) 82568 ( 80.63kB)
Total ROM Size (Code + RO Data + RW Data) 10328 ( 10.09kB)
据此设计app大小为64k. 即app1 :Start=0x08003000,Size=0x10000; app2 : Start=0x08013000,Size=0x10000 需要注意的是:每个区域最好是FlashPage的整数倍,方便对FlashPage的擦除,N32G45x的Flash_Page大小为0x800。 小技巧:这里可以在一个mdk工程中创建多个配置,每个配置参数可以不一样,还可以添加些宏定义,区分工程。
图4-3 多工程配置 b. 如何生成bin文件在keil中下图加入:$K\ARM\ARMCC\bin\fromelf.exe --bin --output=Bin\@L.bin !L 图4-4 bin文件生成 c. SD卡的分类关于SD卡的区别如下: • SDSC:Standard Capacity SD Memory Card,最高支持 2GB 容量,使用 FAT12,FAT16 文件系统。 • SDHC:High Capacity SD Memory Card,支持 4GB~32GB 容量,使用 FAT32 文件系统。 • SDXC:Extended Capacity SD Memory Card,支持 64GB~2TB 容量,使用 exFAT 文件系统。
图4-5 SD卡分类 由于手头只有SD 2G的小卡,属于SDSC,等后面有了大容量SD卡,再测试SDHC是否也可以。 c. 其他方案 其实也有其他方案:比如APP划分两个,APP1和APP2, 在APP1中升级APP2, 在APP2中升级APP1, boot只用来做跳转就行,这个适合网口升级,缺点是占用flash,APP1和APP2的分散加载文件不一样,升级时需要注意。 另一个方案是把升级和操作flash的代码放到ram中运行,这样就可以实现app自己擦除自己,自己更新自己, 这个用NXP测试过可以,N32的芯片后面有时间了,再试试。 4.5 代码演示Boot上电会打印Booot Start,LED1和LED2全灭。 app1.bin(升级程序1)会Blink LED1, 周期500ms。 app2.bin(升级程序2)会Blink LED2, 周期500ms。 升级完成会常亮LED1,LED2, 出错会LED1、LED2同时快闪,周期100ms。 1. 程序默认运行在APP1,Blink LED1。 2. 将app2.bin重命名为app.bin放到SD卡,插上SD卡,按Key1,按复位,等待升级结束,观测LED。 3. 将app1.bin重命名为app.bin放到SD卡,插上SD卡,按Key1,按复位,等待升级结束,观测LED。 视频链接(https://www.bilibili.com/video/BV1424y1j7oG/):
5. 应用场景目前该程序结合了电机控制一块来实现,可通过SD卡升级电机控制程序,方便以后产品封壳后升级的方便。 6. 一些坑1. pack安装不上,提示:Cannot install PackNationstech.N32G45x_DFP.1.0.5: Cannot find PDSC file at root directory of Packarchive 将Nationstech.N32G45xDFP.1.0.5.pack修改为Nationstech.N32G45xDFP.1.0.5.pack.zip, 然后解压缩,然后再压缩,然后再将压缩文件重命名为:Nationstech.N32G45x_DFP.1.0.5.pack就可以安装了。 2. boot转到app需要注意,要提供给app一个相对干净的启动环境,gpio还好,主要是中断相关的一定要关闭,否则就可能导致调到app就hardfault,如果不想一个个检查,可以像我一样,重新软启动下。
|