当前位置:   article > 正文

STM32H750_QSPI_W25QXX_XIP_stm32h750 xip

stm32h750 xip

**

STM32H750_QSPI_W25QXX_XIP_仿真

**

	最近在调试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运行(可仿真)
	其中第四点作为补充。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

心路历程

**
(一、)新建appsystem工程(appsystem)
**
这就比较简单,直接上图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

**
(二、)制作烧录算法工具(Keil_Flash_W25Q40)
**

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

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

在这里插入图片描述

//初始化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	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

针对硬件修改好端口,打开宏W25Q40CL并编译得出文件 Keil_Flash_W25Q40/STM32H750_W25Q40.FLM,满怀希望的把文件STM32H750_W25Q40.FLM复制到C:\Keil_v5\ARM\Flash\目录下,打开appsystem工程选择下载选项:
把原来默认STM32H750xx算法删掉,换成STM32H750_W25Q40
在这里插入图片描述
忐忑的点击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一直不成功!以后所有操作都不成功,导致一直在检测忙状态。
在这里插入图片描述

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

在这里插入图片描述
既然确定要对照命令表,那我们就要列出我们所用到的命令:
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)

STM32H750要配置的寄存器:
QUADSPI->CCR、QUADSPI->AR、QUADSPI->FCR
认真对照以上每一条命令的配置并用示波器测量输出信号,确保和手册相符,那恭喜你,工作已经完成一大半了!
在这里插入图片描述

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

**
(三、) 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模式
  	}	
}  
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寄存器
	}	
}
		

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

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

**
(四、)把片外程序搬运到片内ram运行
**
这样既可以把appsystem下载到W25Q40CL,又可以仿真appsystem!

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

在这里插入图片描述
在这里插入图片描述

3)bootloader修改宏为APPLICATION_ADDRESS_SRAM12
  • 1

在这里插入图片描述
片内RAM运行效果图:
在这里插入图片描述
仿真如图:
在这里插入图片描述

总结

	这几天调试,拿来主义确实方便,但也存在“懒人坑”,专坑像我这样的懒人,不过主要一步步往前推进,懒人坑也有懒人回填方法~
	以上只是本人一些心得体会,难免错误,望各位大神指正,向大家学习!
	
	稍后附上工程源码`


	****************************************
	
	这是首次编写CSDN博客,加上时间紧迫,匆匆而过~
	****************************************
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/124820
推荐阅读
相关标签
  

闽ICP备14008679号