打印
[应用相关]

STM32H750_QSPI_W25QXX_XIP

[复制链接]
1410|13
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
最近在调试STM32H750片子,担心片内flash不够用,在QSPI  bank2外挂了 W25Q40CL做XIP,当然也可以copy到片上ram运行。总结途中遇到些问题,避免新手少走弯路。
        本例程基于原子哥的“STM32H750_W25Q256”例程修改而来,首先在这里非常感谢原子哥的分享!
        这里主要实现的工作有:
        1)新建appsystem工程,运行地址在0x90000000
        2)通过keil把appsystem在线烧录进W25Q40CL,需要制作烧录算法工具
        3)新建bootloader工程,并用bootloader启动appsystem,在片外W25Q40CL运行(XIP,不可仿真)
        4)用bootLoader从片外W25Q40CL复制appsystem到片内ram,并启动在ram运行(可仿真)
        其中第四点作为补充。




使用特权

评论回复
沙发
keaibukelian|  楼主 | 2021-7-3 11:10 | 只看该作者

(一、)新建appsystem工程(appsystem)
**
这就比较简单,直接上图:




**


使用特权

评论回复
板凳
keaibukelian|  楼主 | 2021-7-3 11:10 | 只看该作者
(二、)制作烧录算法工具(Keil_Flash_W25Q40)
**

        本人一向比较懒,喜欢拿来主义,之前没使用过类似flash,首先想到是在www上寻找相关例程,下载回来简单修改端口等参数即可使用,所以一搜索即找到了原子哥的例程,心情如雨后阳光明媚又灿烂!开始依葫芦画瓢:

        1)修改端口CS/CLK/IO0~IO3
        2)修改为bank2
        3)修改QSPI时钟
        4)修改存储容量大小
        5)修改时钟空闲时候极性(可选)



使用特权

评论回复
地板
keaibukelian|  楼主 | 2021-7-3 11:11 | 只看该作者
//初始化QSPI接口
//返回值:0,成功;
//       1,失败;
u8 QSPI_Init(void)
{
#if 1
        // 这里用的是QSPI bank2
        // CS:        PC11
        // CLK:        PB2
        // IO0:        PE7
        // IO1:        PE8
        // IO2:        PE9
        // IO3:        PE10
       
        u32 tempreg=0;

        RCC->AHB4ENR|=1<<0; //使能GPIOA时钟
        RCC->AHB4ENR|=1<<1; //使能GPIOB时钟
        RCC->AHB4ENR|=1<<2; //使能GPIOC时钟
        RCC->AHB4ENR|=1<<3; //使能GPIOD时钟
        RCC->AHB4ENR|=1<<4;        //使能GPIOE时钟
        RCC->AHB3ENR|=1<<14; //QSPI时钟使能
       
        // led PA3
        GPIO_Set(GPIOA,1<<3,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_PU);
        GPIO_Pin_Set(GPIOA, 1 << 3, 1);
       
        // RS485_TX PC7
        GPIO_Set(GPIOC,1<<7,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_PU);
        GPIO_Pin_Set(GPIOC, 1 << 7, 1);
        GPIO_Pin_Set(GPIOC, 1 << 7, 1);

        // BANK2
       
        //PB2 QUADSPI1_CLK
        GPIO_Set(GPIOB,1<<2,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_PU);
        //PC11 QUADSPI1_BK2_NCS
        GPIO_Set(GPIOC,1<<11,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_PU);
        // 这里一定要PP, OD即使上拉驱动能力不够
        //PE7, PE8, PE9, PE10 QUADSPI1_BK2_IO0, IO1, IO2, IO3
        GPIO_Set(GPIOE,0x0F << 7,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_NONE);

        // Table 8. Port A alternate functions
        GPIO_AF_Set(GPIOB,2,9); //PB2,AF9
        GPIO_AF_Set(GPIOC,11,9); //PC11,AF9
        GPIO_AF_Set(GPIOE,7,10); //PE7,AF10
        GPIO_AF_Set(GPIOE,8,10); //PE8,AF10
        GPIO_AF_Set(GPIOE,9,10); //PE9,AF10
        GPIO_AF_Set(GPIOE,10,10); //PE10,AF10

       
        RCC->AHB3RSTR|=1<<14; //复位QSPI
        RCC->AHB3RSTR&=~(1<<14); //停止复位QSPI
        if(QSPI_Wait_Flag(1<<5,0,0XFFFF)==0)//等待BUSY空闲
        {
                //QSPI时钟默认来自rcc_hclk3(由RCC_D1CCIPR的QSPISEL[1:0]选择)
                tempreg=(3-1)<<24;                //设置QSPI时钟为AHB时钟的1/2,即200M/2=100Mhz,10ns // 240/3:80MHz
                tempreg|=(4-1)<<8;                //设置FIFO阈值为4个字节(最大为31,表示32个字节)
                tempreg|=1<<7;                        // 0:选择FLASH1, 1:选择FLASH2
                tempreg|=0<<6;                        //禁止双闪存模式
                tempreg|=1<<4;                        //采样移位半个周期(DDR模式下,必须设置为0)
                QUADSPI->CR=tempreg;        //设置CR寄存器
                tempreg=(19-1)<<16;                //设置FLASH大小为2^25=32MB               
                tempreg|=(5-1)<<8;                //片选高电平时间为5个时钟(10*5=50ns),即手册里面的tSHSL参数
                tempreg|=1<<0;                        //Mode3,空闲时CLK为高电平
                QUADSPI->DCR=tempreg;        //设置DCR寄存器
                QUADSPI->CR|=1<<0;                //使能QSPI
        }else
                return 1;
       
        return 0;
#else
        u32 tempreg=0;
        RCC->AHB4ENR|=1<<1;                    //使能PORTB时钟          
        RCC->AHB4ENR|=1<<5;                    //使能PORTF时钟          
        RCC->AHB3ENR|=1<<14;                   //QSPI时钟使能
        GPIO_Set(GPIOB,1<<2,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_PU);        //PB2复用功能输出       
        GPIO_Set(GPIOB,1<<6,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_PU);        //PB6复用功能输出       
        GPIO_Set(GPIOF,0XF<<6,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_HIGH,GPIO_PUPD_PU);        //PF6~9复用功能输出       
           GPIO_AF_Set(GPIOB,2,9);                //PB2,AF9
        GPIO_AF_Set(GPIOB,6,10);        //PB6,AF10
        GPIO_AF_Set(GPIOF,6,9);                //PF6,AF9
        GPIO_AF_Set(GPIOF,7,9);                //PF7,AF9
        GPIO_AF_Set(GPIOF,8,10);        //PF8,AF10
        GPIO_AF_Set(GPIOF,9,10);        //PF9,AF10
       
        RCC->AHB3RSTR|=1<<14;                //复位QSPI
        RCC->AHB3RSTR&=~(1<<14);        //停止复位QSPI
        if(QSPI_Wait_Flag(1<<5,0,0XFFFF)==0)//等待BUSY空闲
        {
                //QSPI时钟默认来自rcc_hclk3(由RCC_D1CCIPR的QSPISEL[1:0]选择)
                tempreg=(2-1)<<24;                //设置QSPI时钟为AHB时钟的1/2,即200M/2=100Mhz,10ns
                tempreg|=(4-1)<<8;                //设置FIFO阈值为4个字节(最大为31,表示32个字节)
                tempreg|=1<<7;                        // 0:选择FLASH1, 1:选择FLASH2
                tempreg|=0<<6;                        //禁止双闪存模式
                tempreg|=1<<4;                        //采样移位半个周期(DDR模式下,必须设置为0)
                QUADSPI->CR=tempreg;        //设置CR寄存器
                tempreg=(25-1)<<16;                //设置FLASH大小为2^25=32MB
                tempreg|=(5-1)<<8;                //片选高电平时间为5个时钟(10*5=50ns),即手册里面的tSHSL参数
                tempreg|=1<<0;                        //Mode3,空闲时CLK为高电平
                QUADSPI->DCR=tempreg;        //设置DCR寄存器
                QUADSPI->CR|=1<<0;                //使能QSPI
        }else return 1;
        return 0;
#endif       
}


使用特权

评论回复
5
keaibukelian|  楼主 | 2021-7-3 11:11 | 只看该作者
针对硬件修改好端口,打开宏W25Q40CL并编译得出文件 Keil_Flash_W25Q40/STM32H750_W25Q40.FLM,满怀希望的把文件STM32H750_W25Q40.FLM复制到C:\Keil_v5\ARM\Flash\目录下,打开appsystem工程选择下载选项:
把原来默认STM32H750xx算法删掉,换成STM32H750_W25Q40



使用特权

评论回复
6
keaibukelian|  楼主 | 2021-7-3 11:12 | 只看该作者
忐忑的点击download开始下载appsystem到W25Q40CL!成败在此一举! 事实证明,侥幸的心很难找到灵魂的归宿!
下载失败了!


首先,寻找硬件问题:
1)是不是端口配置错误?电源是否正常?(再次确认无误)
2)是不是端口物理连接不通?(配置成IO口输出脉冲,用示波器测量,全部畅通无阻)
3)是否驱动能力不足必须外加上拉?(上电默认是SPI模式,WP和HOLD应该需要外上拉稳定些,为了确保全部外加4.7K上拉)

再次尝试,依然Download failed!!! 此时测量:
CS:有循环的拉低片选
CLK:有对应CS片选的时钟信号80MHz正确
IO0:有对应CS高低跳变(且认为是可用信号)
IO1:有对应CS高低跳变(且认为是可用信号)
IO2:一直保持高电平(且认为是不可用信号)对应pin WP,怀疑没配置为QSPI
IO3:一直保持高电平(且认为是不可用信号)对应pin HOLD,怀疑没配置为QSPI

接着,进而看W25QXX初始化工作:发现读取flash id一直不成功!以后所有操作都不成功,导致一直在检测忙状态。




使用特权

评论回复
7
keaibukelian|  楼主 | 2021-7-3 11:12 | 只看该作者
        其中,为了正确读取ID,折腾了不少功夫。原子哥例程是W25Q256(32MB),我用的是W25Q40CL(512KB),我很愿意相信两者命令兼容的!我都怀疑是我的flash芯片挂掉了?为了心里那点侥幸,果断换一片flash,结果不用猜,依旧。
        此时万般无奈,仅剩下没照对手册命令了(最烦人的,所以一直没有做),但是到了此步田地,除了一条条对照手册命令,别无他求!果然,一对照两者有些不兼容地方!
        W25Q40CL操作总结大体如下:
        1)没有复位命令
        2)单线读取ID
        3)只有两个状态寄存器,其写使能命令不同,且两个状态寄存器要一起写
        4)并无特殊进入QSPI的命令,只需写状态寄存器的EQ位即刻进入QSPI




使用特权

评论回复
8
keaibukelian|  楼主 | 2021-7-3 11:13 | 只看该作者
既然确定要对照命令表,那我们就要列出我们所用到的命令:
1)读取ID命令:0x90
2)读取状态寄存器命令:S1:0x05;S2:0x35
3)写入状态寄存器使能命令:0x50
4)写入状态寄存器命令:0x01
5)退出QSPI命令:0xFF (进入QSPI是写S2的EQ)
6)扇区擦除命令:0x20
7)整片擦除命令:0xC7
8)扇区写入命令:0x32 (FastReadQuad)
9)扇区读取命令:0x6B (PageProgramQuad)


使用特权

评论回复
9
keaibukelian|  楼主 | 2021-7-3 11:13 | 只看该作者
STM32H750要配置的寄存器:
QUADSPI->CCR、QUADSPI->AR、QUADSPI->FCR
认真对照以上每一条命令的配置并用示波器测量输出信号,确保和手册相符,那恭喜你,工作已经完成一大半了!


提示下载成功,但是否真的把数据正确的写入了呢?虽然下载选项有勾选 Verify,但这里肯定还是要主动回读验证了(此处省略)。接着就要把appsystem运行起来。


使用特权

评论回复
10
keaibukelian|  楼主 | 2021-7-3 11:13 | 只看该作者
(三、) bootloader引导运行appsystem(bootloader)
**
STM32H750不支持从外部flash启动,但可以在外部flash运行程序(XIP),所以必须要片内flash做个bootloader引导跳转到外部flash运行程序,相信同学们都很熟悉了。要注意的这里跳转到外部flash地址(0x90000000)之前先初始化QSPI,并设置为内存映射模式(可直接访问,硬件机制帮我们发送读取命令,此模式只读不可写)。上码:


#ifdef         APPLICATION_ADDRESS_SRAM12
u8 appXIPBuff[0x40000] __attribute__((section(".ARM.__at_0x30000000"))); // 256K
#endif

void GoToUserSystem(void)
{
       
        pFunction Jump_To_Application;
        u32 JumpAddress;
       
        _DI();

#ifdef        APPLICATION_ADDRESS
       
        JumpAddress = *(volatile u32*)(EXT_FLASH_ADDRESS+4);
        Jump_To_Application = (pFunction) JumpAddress;
        __set_PSP(*(volatile u32*) EXT_FLASH_ADDRESS);        
        __set_CONTROL(0);        //设置使用主堆栈指针
        //Initialize user application's Stack Pointer
        __set_MSP(*(volatile u32*) EXT_FLASH_ADDRESS);
       
#elif         APPLICATION_ADDRESS_SRAM12

        JumpAddress = *(volatile u32*)(XIP_SRAM12_ADDRESS+4);
        Jump_To_Application = (pFunction) JumpAddress;
        __set_PSP(*(volatile u32*) XIP_SRAM12_ADDRESS);        
        __set_CONTROL(0);        //设置使用主堆栈指针
        //Initialize user application's Stack Pointer
        __set_MSP(*(volatile u32*) XIP_SRAM12_ADDRESS);
       
#else

        JumpAddress = *(volatile u32*)(UserProgramAddressEntry+4);
        Jump_To_Application = (pFunction) JumpAddress;
        __set_PSP(*(volatile u32*) UserProgramAddressEntry);        
        __set_CONTROL(0);        //设置使用主堆栈指针
        //Initialize user application's Stack Pointer
        __set_MSP(*(volatile u32*) UserProgramAddressEntry);
       
#endif

        __DSB();
        __ISB();
       
        Jump_To_Application();
}
int main(void)
{       
        MPU_Config();
  CPU_CACHE_Enable();

        NVIC_SetPriorityGrouping(MY_NVIC_PRIORITYGROUP);
  SystemClock_Config();
       
        LED_Init();
        uart1_init(115200);
        myprintf("\r\nboot:%s,%s\r\n", __DATE__, __TIME__);
       
        W25QXX_Init();
       
        // 4线传输数据; 24位地址; 4线传输地址; 1线传输指令; 4周期
        QSPI_Config_Mmap(0xEB, 0x03, (3 << 6) | (2 << 4) | (3 << 2) | (1 << 0), 4);
        myprintf("QSPI_Config_Mmap\r\n");

#ifdef         APPLICATION_ADDRESS_SRAM12
        memcpy(appXIPBuff, (u8*)(EXT_FLASH_ADDRESS), 0x40000); // 256K
#endif

                myprintf("goto SYSTEM\r\n");
                GoToUserSystem();
               
          while (1)
          {
                                Delayus(1000*1000);
                    LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_3);
                                myprintf("boot\r\n");
                }
}
void W25QXX_Init(void)
{
        u16 k = 0;
               
        QSPI_Init();                                                                        //初始化QSPI
        W25QXX_TYPE=W25QXX_ReadID();        //读取FLASH ID. //必须1 Line
        printf("ID:%X\r\n",W25QXX_TYPE);
        if(W25QXX_TYPE == W25Q40)     //SPI FLASH为W25Q40=0xEF12
          {
                W25QXX_Qspi_Enable(); //使能QSPI模式
          }       
}  


使用特权

评论回复
11
keaibukelian|  楼主 | 2021-7-3 11:14 | 只看该作者
void QSPI_Config_Mmap(u8 cmd,u32 fmode,u8 mode,u8 dmcycle)
{
        u32 tempreg=0;       

        if(QSPI_Wait_Flag(1<<5,0,0XFFFF)==0)        //等待BUSY空闲
        {
                //printf("QSPI_Send_CMD:0x%X add:0x%X mode:0x%X cycle:%d\r\n",cmd, addr, mode, dmcycle);
                tempreg=0<<31;                                                // 0:禁止DDR模式
                tempreg|=0<<28;                                                // 0:每次都发送指令
                tempreg|=fmode<<26;                                        // 0:间接写模式3:Memory-mapped mode
                tempreg|=((u32)mode>>6)<<24;                //设置数据模式
                tempreg|=(u32)dmcycle<<18;                        //设置空指令周期数

                tempreg|=(u32)QSPI_ALTERNATE_BYTES_8_BITS<<16;
                tempreg|=(u32)QSPI_ALTERNATE_BYTES_4_LINE<<14;
               
                tempreg|=((u32)(mode>>4)&0X03)<<12;        //设置地址长度
                tempreg|=((u32)(mode>>2)&0X03)<<10;        //设置地址模式
                tempreg|=((u32)(mode>>0)&0X03)<<8;        //设置指令模式
                tempreg|=cmd;                                                //设置指令
                QUADSPI->CCR=tempreg;                                //设置CCR寄存器
        }       
}
               




到这里,工程appsystem就可以把镜像烧录到W25Q40CL,工程bootloader把镜像烧录进片内128K flash,复位系统就能运行bootloader并跳转到片外flash地址0x90000000运行appsystem程序了,此时用示波器测量W25Q40CL的数据或者时钟,你会发现一直在读取。这个时候你会不会想,真TM累,cpu本来就已经够累的了,还要每次从外面觅食(读取外部falsh获取程序),主要是这通道太慢了,只有80MHz clock,就算W25Q40CL可以支持104MHz clock依然远远不够啊!对了!可以搬运到片内ram运行!


使用特权

评论回复
12
keaibukelian|  楼主 | 2021-7-3 11:15 | 只看该作者
(四、)把片外程序搬运到片内ram运行
**
这样既可以把appsystem下载到W25Q40CL,又可以仿真appsystem!

        1)准备SRAM1和SRAM2共256KB空间做代码运行区0x30000000~0x30040000
        2)appsystem编译地址选择0x30000000,下载选项不变,依然选STM32H750_W25Q40,首地址0x90000000改为0x30000000






3)bootloader修改宏为APPLICATION_ADDRESS_SRAM12





使用特权

评论回复
13
keaibukelian|  楼主 | 2021-7-3 11:15 | 只看该作者
片内RAM运行效果图:

使用特权

评论回复
14
keaibukelian|  楼主 | 2021-7-3 11:15 | 只看该作者
仿真如图:

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

63

主题

4095

帖子

5

粉丝