本帖最后由 xld0932 于 2024-3-19 11:50 编辑
#申请原创# @21小跑堂
做嵌入式UI设计的小伙伴,常常因为MCU内部存储资源限制,多数都是将显示资源存放在外部存储,有TF卡,也有SPI FLASH。对TF卡来说,显示资源更新操作方便,只需要把TF卡从板子上取下来,把显示资源拷贝进来,然后再插到开发板上就可以了,但TF卡成本贵呀,而且不断的插拔对于接口来说,也是一项考验。其次就是SPI FLASH,成本倒是便宜了不少,但更新显示资源是一件头疼的事,总不能SPI FLASH中的数据每变化一次,就要把SPI FLASH芯片从板子取下来,重新烧录一遍再焊上去?就算板子上预留了烧录点位,但操作起来还是很麻烦呀……SPI FLASH的烧录工具、MCU的调试工具等等,一堆工具、软件,操作也不省事。
有没有一种工具/办法可以解决SPI FALSH遇到的这些问题呢,降低成本、提升效率?同一个工具即可以做生产工具,也可以做调试工具;即可以离线烧录,也可以在线烧录?有!其实我们经常在用的J-LINK就具备这样的功能,具体怎么来操作/实现呢?下面来给大家分享一下以下几种方式:
- J-Flash SPI实现SPI FLASH数据更新
- 虚拟串口实现SPI FLASH数据更新
- 下载算法实现SPI FLASH数据更新
- J-Flash实现SPI FLASH数据更新
1、J-Flash SPI实现SPI FLASH数据更新
J-Link作为我们嵌入开发工具的一大调试利器,20PIN的接口功能很丰富,除了我们常用的JTAG、SWD调试接口功能外,还具备SPI、QSPI接口功能;我们可以通过在安装J-Link驱动时自动安装的J-Flash SPI软件,结合SPI、QSPI接口功能实现对SPI FLASH的离线烧录。
使用J-Link的SPI、QSPI接口功能下载SPI FLASH时,要么在硬件上需要预留SPI FLASH的下载引脚,要么可以通过专门的夹子治具夹住芯片进行烧录;J-Link有了SPI FLASH下载功能之后,就可以不用专门再去买SPI FLASH离线烧录工具了,对于开发人员,一个工具多个功能/用途,真的是很方便!首先我们还是先来了解一下J-Link对于SPI、QSPI接口功能的引脚定义吧,这样方便我们连接SPI FLASH,进行烧录。
J-Link SPI接口功能定义:
J-Link QSPI接口功能定义:
接下来我们使用J-Flash SPI软件来对SPI FLASH进行离线烧录,具体步骤如下:
1.1.准备硬件环境,我们准备了一个SPI FLASH模块,通过上述的J-Link SPI接口功能定义,使用杜邦线将SPI FLASH模块与J-Link连接起来,需要注意的是J-Link的VTref也需要供电哦
1.2.打开J-Flash SPI软件,如下图所示:
1.3.点击菜单栏Target->Connect,让J-Link连接SPI FLASH,确认连接是正确无误的:
1.4.等J-Link与SPI FLASH连接成功后,在左侧会显示SPI FLASH的相关信息:
1.5.点击菜单栏File->Open data file...打开一个准备好的烧录文件,并确认起始地址,我们默认是从0地址开始烧录哦:
1.6.点击菜单栏Target->Erase sectors,擦除SPI FLASH:
1.7.点击菜单柆Target->Program & Verify,对SPI FLASH进行编程(下载数据),并对其进行校验:
1.8.上述是将擦除、编程、校验这些动作分开操作的,其实有更便捷的操作哦,直接点击菜单栏Targer->Auto即可。
2、虚拟串口实现SPI FLASH数据更新
J-Link硬件版本在9或更高的版本时,具有VCOM功能,我们可以J-Link Commander或者J-Link Configurator对J-Link的虚拟串口功能进行打开和关闭操作,默认是处于关闭状态的哦。需要注意的是,当使用JTAG接口功能时是不能使用VCOM功能的哈!首先我们还是先来了解一下J-Link对于VCOM接口功能的引脚定义吧……
接下来我们通过不同的J-Link硬件版本来测试VCOM的功能:
2.1.通过J-Link Commander打开/关闭VCOM
连接J-Link后,打开J-Link Commander软件,输入“vcom enable”即为打开VCOM功能,输入“vcom disable”即为关闭VCOM功能,在操作完成后,需要重新连接J-Link,才可以正常使用VCOM功能哦
2.2.通过J-Link Configurator打开/关闭VCOM
连接J-Link后,打开J-Link Configurator软件,此时在Connected via USB会显示当前连接的J-Link以及其状态:
双击相应的J-Link后会弹出Configuration对话框,在Virtual COM-Port栏,通过选择Enable/Disable来打开或关闭VCOM功能,然后点击OK确认,同样也需要重新连接J-Link,才可以正常使用VCOM功能
2.3.检查VCOM连接/识别状态
2.4.VCOM通讯测试
通过将Rx和Tx引脚连接起来,建议回环测试,同时需要注意的是VTref引脚需要供电哦,要不然VCOM功能是不会工作的,所以为了测试我们可以将VTref与5V-Supply连接在一起进行通讯测试:
那J-Link的虚拟串口跟我们的SPI FLASH数据更新有什么关系呢?关注我写过帖子的小伙伴,就会发现,在之前多篇分享中都用到了Xmodem通讯协议,基于串口通讯,通过上位机软件经过Xmodem协议,将数据传输到MCU,然后MCU解析Xmodem协议,提取有效的数据进行处理;所以J-Link的VCOM起到一个传输媒介的作用,通过MCU将接收到的数据写入到SPI FLASH来实现SPI FLASH数据的更新,具体的实现可以参考之前分享的帖子哦!
3、下载算法实现SPI FLASH数据更新
在我们硬件没有预留SPI FLASH烧录接口时,觉得通过引线的方式将烧录接口引出来不方便;又觉得通过MCU实现Xmodem协议太麻烦,或者说MCU的资源不够用;那通过下载算法来实现SPI FLASH的数据更新,确实是一个不错的选择!
通常提到下载算法,大家想到的就是对MCU的下载编程;其实通过MCU作为媒介,可以借助MCU的外设资源对挂载的外部其它器件进行数据传输/编程操作,比如说可以通过I2C接口实现对EEPROM的数据写入,可以通过SPI接口实现对SPI FLASH的数据写入等等。接下来我们就来讲解一下SPI FLASH下载算法实现的步骤:
3.1.我们使用MM32G0001开发板作为演示,官方的Mini-G0001开发板板载了一个8M-bit的SPI FLASH,便于我们进行验证
3.2.在实现SPI FLASH下载算法之前,我需要通过常规的应用来验证SPI的初始化、对SPI FLASH的读、写、擦除等操作,待这些功能验证OK了,然后将这些功能代码直接移植到下载算法中就可以了,此处的验证工程在后面的附件可以下载到哦
3.3.我们在Keil安装目录,或者是从CMSIS包中拷贝一份下载算法空工程,然后将刚刚验证好的SPI FLASH操作函数,根据下载算法的功能函数进行填充;需要注意的是下载算法的运行是通过Keil将相应的功能函数Load到SRAM运行的,SRAM其本身的存储空间正常来说是远远小于FLASH存储空间的,所以受SRAM存储空间大小限制,建议下载算法都通过寄存器的方式来实现!其次是对于MCU来说,有些应用是需要开户看门狗的,这是特别需要注意的地方,在下载算法中一定要对看门狗进行判断和处理,不然当SPI FLASH数据理大时,下载到中途MCU突然复位了,就会导致SPI FLASH下载失败或者异常了!!!
3.4.下载算法FlashDev.c配置
在FlashDevice结构体中,根据SPI FLASH的规格书定义了Page的大小、Sector的大小、SPI FLASH芯片存储空间大小,其次为了区分SPI FLASH和其它MCU外设,将SPI FLASH器件起始地址进行了自定义,对Device Name进行了自定义,如下所示:
struct FlashDevice const FlashDevice =
{
FLASH_DRV_VERS, // Driver Version, do not modify!
"Mini-G0001_ZD25WQ80", // Device Name
EXTSPI, // Device Type
0xC0000000, // Device Start Address
0x00100000, // Device Size in Bytes (256kB)
256, // Programming Page Size
0, // Reserved, must be 0
0xFF, // Initial Content of Erased Memory
100, // Program Page Timeout 100 mSec
3000, // Erase Sector Timeout 3000 mSec
// Specify Size and Address of Sectors
0x001000, 0x000000, // Sector Size 4kB (256 Sectors)
SECTOR_END
};
3.5.下载算法Init函数
在Init函数中,我们根据Mini-G0001开发板硬件设计原理图,对SPI和GPIO的进行了配置
/*
* Initialize Flash Programming Functions
* Parameter: adr: Device Base Address
* clk: Clock Frequency (Hz)
* fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
* Return Value: 0 - OK, 1 - Failed
*/
int Init(unsigned long adr, unsigned long clk, unsigned long fnc)
{
/* Add your Code */
RCC->AHBENR_b.mGPIOA = 1;
RCC->APB1ENR_b.mSPI1 = 1;
SPI1->RXDNR = 0;
SPI1->GCTL_b.MODE = 1; /* Master */
SPI1->CCTL_b.LSBFE = 0; /* MSB */
SPI1->CCTL_b.CPHA = 1;
SPI1->CCTL_b.CPOL = 0;
SPI1->GCTL_b.DW8_32 = 0;
SPI1->CCTL_b.SPILEN = 1;
SPI1->EXTCTL_b.EXTLEN = 8;
SPI1->SPBRG_b.SPBRG = 2;
SPI1->GCTL_b.NSS = 0;
SPI1->GCTL_b.TXEN = 1;
SPI1->GCTL_b.RXEN = 1;
/* PA15 : AF0, SPI1_NSS */
GPIOA->CRH_b.MODE15 = 1;
GPIOA->CRH_b.CNF15 = 2;
GPIOA->AFRH_b.AFR15 = 0;
/* PA8 : AF0, SPI1_SCK */
GPIOA->CRH_b.MODE8 = 1;
GPIOA->CRH_b.CNF8 = 2;
GPIOA->AFRH_b.AFR8 = 0;
/* PA9 : AF0, SPI1_MOSI */
GPIOA->CRH_b.MODE9 = 1;
GPIOA->CRH_b.CNF9 = 2;
GPIOA->AFRH_b.AFR9 = 0;
/* PA2 : AF3, SPI1_MISO */
GPIOA->CRL_b.MODE2 = 0;
GPIOA->CRL_b.CNF2 = 2;
GPIOA->ODR_b.ODR2 = 1;
GPIOA->AFRL_b.AFR2 = 3;
SPI1->GCTL_b.SPIEN = 1;
SPI1->NSSR_b.NSS = 1;
// Test if IWDG is running (IWDG in HW mode)
if ((FLASH->OBR & FLASH_OBR_WDG_SW_Msk) == 0)
{
IWDG->KR = 0x5555; // Enable write access to IWDG_PR and IWDG_RLR
IWDG->PR = 0x06; // Set prescaler to 256
IWDG->RLR = 4095; // Set reload value to 4095
}
return (0); // Finished without Errors
}
3.6.下载算法EraseSector函数
/*
* Erase Sector in Flash Memory
* Parameter: adr: Sector Address
* Return Value: 0 - OK, 1 - Failed
*/
int EraseSector(unsigned long adr)
{
uint8_t Status = 0;
IWDG->KR = 0xAAAA; // Reload IWDG
/* Add your Code */
SPI1->NSSR_b.NSS = 0;
SPIFLASH_RxTxData_Polling(0x06);
SPI1->NSSR_b.NSS = 1;
SPI1->NSSR_b.NSS = 0;
SPIFLASH_RxTxData_Polling(0x20);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 16) & 0x000000FF);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 8) & 0x000000FF);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 0) & 0x000000FF);
SPI1->NSSR_b.NSS = 1;
do
{
SPI1->NSSR_b.NSS = 0;
SPIFLASH_RxTxData_Polling(0x05);
SPIFLASH_RxTxData_Polling(0xFF);
Status = SPIFLASH_RxTxData_Polling(0xFF);
SPI1->NSSR_b.NSS = 1;
IWDG->KR = 0xAAAA; // Reload IWDG
}
while (Status & 0x01);
return (0); // Finished without Errors
}
3.7.下载算法ProgramPage函数
/*
* Program Page in Flash Memory
* Parameter: adr: Page Start Address
* sz: Page Size
* buf: Page Data
* Return Value: 0 - OK, 1 - Failed
*/
int ProgramPage(unsigned long adr, unsigned long sz, unsigned char *buf)
{
/* Add your Code */
uint32_t i = 0;
uint8_t Status = 0;
IWDG->KR = 0xAAAA; // Reload IWDG
SPI1->NSSR_b.NSS = 0;
SPIFLASH_RxTxData_Polling(0x06);
SPI1->NSSR_b.NSS = 1;
SPI1->NSSR_b.NSS = 0;
SPIFLASH_RxTxData_Polling(0x02);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 16) & 0x000000FF);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 8) & 0x000000FF);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 0) & 0x000000FF);
for (i = 0; i < sz; i++)
{
SPIFLASH_RxTxData_Polling(buf[i]);
}
SPI1->NSSR_b.NSS = 1;
do
{
SPI1->NSSR_b.NSS = 0;
SPIFLASH_RxTxData_Polling(0x05);
SPIFLASH_RxTxData_Polling(0xFF);
Status = SPIFLASH_RxTxData_Polling(0xFF);
SPI1->NSSR_b.NSS = 1;
IWDG->KR = 0xAAAA; // Reload IWDG
}
while (Status & 0x01);
return (0); // Finished without Errors
}
3.8.下载算法Verify函数
unsigned long Verify(unsigned long adr, unsigned long sz, unsigned char *buf)
{
uint8_t Data = 0;
uint32_t i = 0;
IWDG->KR = 0xAAAA; // Reload IWDG
SPI1->NSSR_b.NSS = 0;
SPIFLASH_RxTxData_Polling(0x0B);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 16) & 0x000000FF);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 8) & 0x000000FF);
SPIFLASH_RxTxData_Polling((uint8_t)(adr >> 0) & 0x000000FF);
SPIFLASH_RxTxData_Polling(0xFF);
for (i = 0; i < sz; i++)
{
Data = SPIFLASH_RxTxData_Polling(0xFF);
if (Data != buf[i])
{
break;
}
}
SPI1->NSSR_b.NSS = 1;
return (adr + i);
}
这个函数的实现与否,取决于在下载时是否勾选Verify选项,如果需要勾选那就一定要实现Verify函数,否则会导致下载失败!
没有实现Verify函数,却勾选了Verify下载选项时,错误提示如下所示:
在这些下载算法函数都实现完成后,就可以通过编译来生成FLM下载算法文件了,我们将生成的下载算法文件拷贝到“C:\Keil_v5\ARM\Flash”目录下即可。接下来就是如何配置Keil的应用工程,通过这个下载算法,来更新SPI FLASH中的数据了……首先我们需要准备一下数据源,如下所示:
其中定义了这个数据源的起始存放地址,就是我们下载算法配置中的器件起始地址。对于Keil工程的配置我们有2种方式,看你们操作习惯可自行选择:
方式1:
在工程Option设置中的Target选项卡中,配置IROM2,Start为0xC0000000,Size为0x100000,这个配置与下载算法保持一致;在Linker选项卡中默认选择“Use Memory Layout from Target Dialog”:
右击spiflash_data.c配置文件选项,在Memory Assignment中将Code/Const指向IROM2即可:
方式2:
在工程Option设置中的Linker选项卡中不选择“Use Memory Layout from Target Dialog”:
在Scatter File一栏,点击Edit对.sct文件进行编辑,添加LR_IROM2段,如下所示:
3.9.在这些都配置完成后,将刚刚的下载算法添加到应用工程中,编译下载:
3.10.通过应用程序查看通过下载算法更新SPI FLASH中的数据内容:
4、J-Flash实现SPI FLASH数据更新
通过方式1结合硬件设计预留烧录接口,我们可以实现离线烧录SPI FLASH、通过方式2结合J-Link虚拟串口+程序设计,我们可以实现离线烧录SPI FLASH、通过方式3下载算法,我们可以实现在线烧录SPI FLASH;对于生产来说,都是离线烧录的方式,在线烧录只适用于开发调试阶段,更方便于开发人员。
那我们硬件上没有预留烧录接口、芯片资源也很紧张,不允许添加更多的程序,那能不能实现离线烧录呢?
当然可以,我们借助方式3的烧录算法,再结合J-Flash软件,可以实现通过MCU作为媒介,实现离线烧录SPI FLASH的功能,具体步骤如下:
首先我们需要将SPI FLASH设备及下载算法添加到J-Flash识别列表当中去,我们在“C:\Users\XXXXX\AppData\Roaming\SEGGER”文件夹中新建“JLinkDevices”文件夹,在“JLinkDevices”下再创建“ZD25WQ80”文件夹,这个文件夹就是我们Mini-G0001板载SPI FLASH的型号,接着在“ZD25WQ80”文件夹下将刚刚的下载算法文件添加进行来,然后再创建一个空的XML文件,如下所示:
在XML文件中添加如下信息,此时J-Flash添加一个自定义设备就完成啦:
4.1.我们打开J-Flash软件
4.2.在Create New Project窗口中,点击Target device后面的点点点,选择我们刚刚添加的新设备后,勾选Flash banks中的0xC0000000一栏,如下所示:
4.3.打开我们的测试烧录文件,起始设置默认为0xC0000000,如下所示:
4.4.点击菜单栏Target->Connect,连接J-Link:
4.5.点击菜单柆Target->Manual Programming->Erase Sectors,擦除SPI FLASH:
在擦除完成后,通过程序读取SPI FLASH中的部分内容,都已经被清除成0xFF数值了:
4.6.点击菜单柆Target->Manual Programming->Program,对SPI FLASH进行编程:
4.7.我们通过代码读取SPI FLASH中的内容进行比较,看数据是否正确写入,我们选取了0x00005200这个偏移地址进行比对:
5、附件
Mini-G0001原理图:
Mini-G0001_SCH.PDF
(1.2 MB)
SPI FLASH规格书:
ZD25WQ80B.PDF
(1.71 MB)
下载算法:
_Template_Flash.zip
(82.31 KB)
J-Flash添加新设备文件:
JLinkDevices.zip
(5.33 KB)
SPI FLASH读写擦除验证工程:
MM32G0001_SPI_FLASH_BASE.zip
(490.76 KB)
方式1更新SPI FLASH数据:
MM32G0001_SPI_FLASH_MODE1.zip
(491.34 KB)
方式2更新SPI FLASH数据:
MM32G0001_SPI_FLASH_MODE2.zip
(491.57 KB)
读取SPI FLASH中的数据:
MM32G0001_SPI_FLASH_READ.zip
(588.76 KB)
|
超级贴心,巨详细的J-Link下载SPI FLASH的4种方式。J-Link下载SPI FLASH 有困难,一篇文章教你搞定!