为什么没有把所有的驱动文件都包含进去呢?这是由于 FlashLoader 需尽可能的小,如果把所有的驱动都添加进去的话,下载算法就会变得很大很大,要知道,下载算法没有启动文件和主函数,编译器不知道哪些函数没有用到,因此不会进行优化,将没有用到的函数删除。因此仅添加 GPIO,QSPI 和 RCC 三个必须使用到的驱动,如有必要,甚至可以写好下载算法后,将这三个驱动文件中没有用到的函数也进行删除。
QSPI Flash芯片的驱动开发
要想将代码下载到 QSPI Flash 中,就要根据 QSPI Flash 手册中的对应要求来操作 QSPI Flash,为此,需要为 QSPI Flash 芯片设计驱动,当然,为了减少代码量,仅实现必要的功能。
QSPI Flash 支持单线,双线和四线操作,本文为方便编程,仅描述实现单线模式的操作,且认为 QSPI Flash 没有对任何块实施写保护等操作,同时,QSPI Flash 没有进入所谓的 QPI 模式。当然,上述特殊需求也能够在 Flashloader 中实现,但本文不再详细赘述如何实现。
在 FlashPrg.c 文件中设计并添加 QspiFlash_Init()、QspiFlash_IsBusy()、QspiFlash_EnableWrite()、QspiFlash_EraseSector()、QspiFlash_WritePage() 函数作为 QSPI Flash 芯片的驱动函数。
QspiFlash_Init() 函数
设计函数QspiFlash_Init()用于初始化,该函数包括 GPIO 的初始化,QSPI 模块的初始化,QSPI 直接模式读的初始化,由于仅使用单线模式,故无需额外打开 QSPI Flash 的四线模式。以上初始化的需求如下:
GPIO 的初始化
配置 GPIO 使其作为 QSPI 的信号线,供 QSPI 外设与 QSPI Flash 进行
通信。
QSPI 模块的初始化
使其能够与 QSPI Flash 进行通信,实现读数据和写数据的功能。
QSPI 直接模式读的初始化
为了使系统能够通过访问地址的形式直接读取到 QSPI Flash 中的数据,调试器下载程序后,会通过访问地址的形式读取 QSPI Flash 中的数据,与下载的数据进行对比,实现校验功能。
需要注意的是,GPIO 要根据 QSPI 实际使用到的引脚进行配置,QSPI Flash 的初始化要根据 QSPI Flash 的数据手册进行调整,可能部分 QSPI Flash 在读写数据之前,会有一些针对 QSPI Flash 的初始化操作。
void QspiFlash_Init(void)
{
/* enable periph clock. */
RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOA
| RCC_AHB1_PERIPH_GPIOB
| RCC_AHB1_PERIPH_GPIOG
| RCC_AHB1_PERIPH_QSPI
, true);
/* enable gpio. */
GPIO_Init_Type gpio_init;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
gpio_init.Pins = GPIO_PIN_3; /* QSPI_DA1. */
GPIO_Init(GPIOA, &gpio_init);
GPIO_PinAFConf(GPIOA,gpio_init.Pins, GPIO_AF_10);
gpio_init.Pins = GPIO_PIN_3 | GPIO_PIN_10; /* QSPI_DA2 & QSPI_CS. */
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB,gpio_init.Pins, GPIO_AF_10);
gpio_init.Pins = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8; /* QSPI_DA0, QSPI_SCK & QSPI_DA3. */
GPIO_Init(GPIOG, &gpio_init);
GPIO_PinAFConf(GPIOG,gpio_init.Pins, GPIO_AF_10);
/* enable qspi. */
QSPI_Init_Type qspi_init;
qspi_init.SpiMode = QSPI_SpiMode_3;
qspi_init.SckDiv = 8u;
qspi_init.CsHighLevelCycles = 9u;
qspi_init.RxSampleDelay = 1u;
QSPI_Init(QSPI, &qspi_init);
/* enable qspi direct xfer mode. */
QSPI_DirectXferConf_Type qspi_direct_conf;
qspi_direct_conf.CmdBusWidth = QSPI_BusWidth_1b;
qspi_direct_conf.CmdValue = 0x0B; /* fast read. */
qspi_direct_conf.AddrBusWidth = QSPI_BusWidth_1b;
qspi_direct_conf.AddrWordWidth = QSPI_WordWidth_24b;
qspi_direct_conf.AltBusWidth = QSPI_BusWidth_None;
qspi_direct_conf.DummyCycles = 8u;
qspi_direct_conf.DataBusWidth = QSPI_BusWidth_1b;
QSPI_EnableDirectRead(QSPI, &qspi_direct_conf);
}
QspiFlash_IsBusy() 函数
QspiFlash_IsBusy() 函数通过读取 QSPI Flash 的寄存器值,确定 QSPI Flash 是否处于忙碌状态。在向 QSPI Flash 中写入数据之后,需要使用该函数等待 QSPI Flash 将写进去的数据完全烧写到 Flash 单元中。
判断本文使用的 QSPI Flash 是否忙碌的方法是:读取这个 QSPI Flash 寄存器 2 中的第一个比特位是否为1,若为1 则是忙碌状态。
具体的判断方法,还是需要根据使用的 QSPI Flash 手册来确定判断方法。
bool QspiFlash_IsBusy(void)
{
QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);
QSPI_IndirectXferConf_Type xfer_conf;
xfer_conf.CmdBusWidth = QSPI_BusWidth_1b;
xfer_conf.CmdValue = 0x05; /* get reg1 value. */
xfer_conf.AddrBusWidth = QSPI_BusWidth_None;
xfer_conf.AltBusWidth = QSPI_BusWidth_None;
xfer_conf.DummyCycles = 0u;
xfer_conf.DataBusWidth = QSPI_BusWidth_1b;
xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
xfer_conf.DataLen = 1;
QSPI_SetIndirectReadConf(QSPI, &xfer_conf);
while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
{}
uint32_t reg1 = QSPI_GetIndirectData(QSPI);
if (0 == (reg1 & 0x01) )
{
return false;
}
else
{
return true;
}
}
QspiFlash_EnableWrite() 函数
QspiFlash_EnableWrite() 函数主要是为了进行写使能操作,保证数据可写入。
每次进行任何写操作时,都需要执行一次写使能操作。需要注意,每一次写操作完成后,写使能就会关闭(当然,还需视具体芯片而定)。
void QspiFlash_EnableWrite(bool enable)
{
QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);
QSPI_IndirectXferConf_Type xfer_conf;
xfer_conf.CmdBusWidth = QSPI_BusWidth_1b;
if (true == enable)
{
xfer_conf.CmdValue = 0x06; /* enbale write. */
}
else
{
xfer_conf.CmdValue = 0x04; /* disable write. */
}
xfer_conf.AddrBusWidth = QSPI_BusWidth_None;
xfer_conf.AltBusWidth = QSPI_BusWidth_None;
xfer_conf.DummyCycles = 0u;
xfer_conf.DataBusWidth = QSPI_BusWidth_None;
QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);
while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
{}
}
QspiFlash_EraseSector() 函数
QspiFlash_EraseSector() 函数执行擦除扇区操作。
void QspiFlash_EraseSector(uint32_t addr)
{
QspiFlash_EnableWrite(true);
QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);
QSPI_IndirectXferConf_Type xfer_conf;
xfer_conf.CmdBusWidth = QSPI_BusWidth_1b;
xfer_conf.CmdValue = 0x20; /* sector erase. */
xfer_conf.AddrBusWidth = QSPI_BusWidth_1b;
xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
xfer_conf.AddrValue = addr & 0x00FFFFFF;
xfer_conf.AltBusWidth = QSPI_BusWidth_None;
xfer_conf.DummyCycles = 0u;
xfer_conf.DataBusWidth = QSPI_BusWidth_None;
QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);
while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
{}
while(QspiFlash_IsBusy() ) /* wait for erase done. */
{}
QspiFlash_EnableWrite(false);
}
QspiFlash_WritePage() 函数
QspiFlash_WritePage() 函数用于执行写数据操作。
注意,擦数据是按块(或者叫做扇区)擦除,写操作是按页进行写,页的大小是小于块的大小的。块大小和页大小已经在前面的 FlashDev.c 中定义好了,调试下载器会按照上面定义的大小进行块擦除和页写入。
void QspiFlash_WritePage(uint32_t addr, uint32_t size, uint8_t * data)
{
QspiFlash_EnableWrite(true);
QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);
QSPI_IndirectXferConf_Type xfer_conf;
xfer_conf.CmdBusWidth = QSPI_BusWidth_1b;
xfer_conf.CmdValue = 0x02; /* sector erase. */
xfer_conf.AddrBusWidth = QSPI_BusWidth_1b;
xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
xfer_conf.AddrValue = addr & 0x00FFFFFF;
xfer_conf.AltBusWidth = QSPI_BusWidth_None;
xfer_conf.DummyCycles = 0u;
xfer_conf.DataBusWidth = QSPI_BusWidth_1b;
xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
xfer_conf.DataLen = size;
QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);
while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
{
if (size 》 0 && 0 != (QSPI_GetStatus(QSPI) & QSPI_STATUS_FIFO_EMPTY) )
{
QSPI_PutIndirectData(QSPI, *data++);
size--;
}
}
while(QspiFlash_IsBusy() ) /* wait for erase done. */
{}
QspiFlash_EnableWrite(false);
}
修改FlashPrg.c文件
在 MM32F5270_QSPI_FlashLoader 工程的 FlashPrg.c 文件中,根据用户手册对于 Flash 描述和 Flash 控制器的特性,适配编程算法。
使用上述QSPI Flash芯片驱动开发函数对FlashPrg.c文件中的模板函数进行填充。
Init()函数
初始化函数除了初始化 QSPI Flash 本身,还需要初始化 MCU 的时钟,使其工作在片内 RC 振荡器提供的 8M 时钟环境下。
int Init (unsigned long adr, unsigned long clk, unsigned long fnc)
{
/* Switch to HSI. */
RCC-》CR |= RCC_CR_HSION_MASK; /* Make sure the HSI is enabled. */
while ( RCC_CR_HSIRDY_MASK != (RCC-》CR & RCC_CR_HSIRDY_MASK) )
{
}
RCC-》CFGR = RCC_CFGR_SW(0u); /* Reset other clock sources and switch to HSI. */
while ( RCC_CFGR_SWS(0u) != (RCC-》CFGR & RCC_CFGR_SWS_MASK) ) /* Wait while the SYSCLK is switch to the HSI. */
{
}
/* Reset all other clock sources. */
RCC-》CR = RCC_CR_HSION_MASK;
/* Disable all interrupts and clear pending bits. */
RCC-》CIR = RCC-》CIR; /* clear flags. */
RCC-》CIR = 0u; /* disable interrupts. */
QspiFlash_Init();
return (0); /* Finished without Errors. */
}
UnInit()函数
无需任何操作。
UnInit() 函数在下载程序的操作完成后被调用,如果 Flashloader 操作的是片内 Flash,可能会在该函数内实现一个对片内 Flash 上锁的操作,保证 Flash 中的数据不会被意外修改,而本文使用的 QSPI Flash 不需要类似的操作,因此这个函数不用做任何改动,当然,也得视具体的 QSPI Flash 而定。
EraseChip()函数
QSPI Flash 本身具有擦除整颗芯片数据的指令,但为了减少代码量,可以使用循环调用擦除扇区的方法来擦除整颗芯片的数据。
int EraseChip (void) { for (uint32_t i = 0; i 《 0x01000000; i+= 0x1000) { QspiFlash_EraseSector(i); } return (0); /* Finished without Errors. */}
EraseSector() 函数
此函数的目的是擦除扇区。
需要注意的是,Flash_EraseSector() 需要传入的参数是 QSPI Flash 芯片内的地址,而不是在 MCU 上映射的地址,因此需要使用 0x00FFFFFF 掩码取有效地址。
int EraseSector (unsigned long adr)
{
QspiFlash_EraseSector(adr & 0x00FFFFFF);
return (0); /* Finished without Errors. */
}
ProgramPage()函数
此函数的目的是将程序写入 Flash 中。
int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf)
{
QspiFlash_WritePage(adr,sz,buf);
return (0); /* Finished without Errors. */
}
总结
完成上述下载算法所需的函数后,下载算法也就制作完成了。使用这个下载算法,就可以实现下载程序到 QSPI Flash 的操作,但本文还没有讲该怎么实现这个可以存储在 QSPI Flash 中的应用程序,也没有讲解该怎么执行这个程序,所以暂时还没有办法验证这个下载算法是否可用,不过后续也将会讲解如何跳转执行存储在 QSPI Flash 中的应用程序,和如何让应用程序能够存储在 QSPI Flash 中。
原作者:灵动MM32