SPI是串行外设接口(Serial Peripheral Interface)的缩写。 大概资料,百度百科 https://baike.baidu.com/item/SPI%E6%8E%A5%E5%8F%A3/2527392
SPI通常使用4根线连接。 (1)MOSI – 主器件数据输出,从器件数据输入(master out slave in) (2)MISO – 主器件数据输入,从器件数据输出(master in slave out) (3)SCLK –时钟信号,由主器件产生。 (4)NSS – 从器件使能信号,由主器件控制,通常叫CS、片选。
SPI根据时钟极性CPOL与时钟相位CPHA的不同,有4钟工作模式。
SPI使用多个片选管脚就可连接多个从设备。
SPI控制器 从中可以看出:
1 移位寄存器只有一个,因为接受和发送是同时进行的。发送从右边出去,接收从左边进来。 2 4根引脚,MOSI、MISO、SCK、NSS(CS)
SPI的大概工作过程就是:
主设备将对应从设备CS拉低,使能从设备。
主设备输出时钟信号,主设备移位寄存器的数据按BIT从MOSI上输出,从设备收到BIT后,保存到自己的移位寄存器,同时将自己移位寄存器中的数据从MISO上输出,主设备收到后保存在移位寄存器中。 如此循环,8个BIT传输结束,进行读写操作,然后进行下一个字节的传输。
传输结束,将从设备片选拉高,结束。
SPI最快速度 在表格11中可以看到APB1的时钟最快42M,SPI3就挂载APB1上,为了达到最快的21M,必须使用2分频。
SPI分频
前面说到,SPI有4中工作模式
CPOL是时钟极性,如果为1,则是先输出低再输出高。0则相反。
CPHA是时钟相位,如果为1,则是180度。0则是0度。 通俗的说,如果是1,就在时钟的第二个边沿采样。 如果是0,就在时钟的第一个边沿采样。
时钟图
从上图也可以看到一个标准的SPI通信时序是怎么样的。 主设备输出时钟,并在MOSI上输出数据。 从设备在MISO上输出数据。 在通信期间,NSS(CS)保持低电平。
文档开头会描述性能,软件需要关心的是: (1)工作模式,本芯片支持Mode 0 和Mode3。 (2)1024个sector,每个sector有4K。每个sector都可以单独擦除。 (3)64个BLOCK,每个BLOCK 大小64K,也就是说,一个BLOCK有16个SECTOR。BOLOCK也可以整体擦除。 (4)可以page编程,一个page有256字节。
SPI FLASH特性
之后是PERFORMANCE跟SOFTWARE FEATURES,主要是一些参数特性,例如擦除时间等。一般来说不用太关注。除非系统有要求。 比较影响性能的也就是擦除时间。有些厂家的会比较慢。其实相对CPU速度来说,擦除FLASH是一个很慢的过程。 部分FLASH会有额外性能,例如有OTP区,有加密区等等。
FLASH的组织需要关注一下,特别是在FLASH进行替代的时候,组织模式一定要一样。也就是说BLOCK、SECTOR、PAGE的分布要一致。有些芯片只有前面2个BLOCK可以页操作,后续的只能sector操作。
SPI flash组织
之后需要认真关注的是命令。命令就是操作FLASH时主机发给FLASH的指令。如何使用后面会
说明。 SPI flash命令
时序分析 前面几个是SPI时序,一般不看,遇到问题挂示波器才会对比一下。 SPI falsh时序章节 但是到后面就是命令流程了,也就是说明命令如何使用,FLASH如何操作,本处挑两个说说
SPI falsh读状态 上图是读FLASH 的状态,操作过程就是:
主机拉低CS信号,使能FLASH。
主机将命令05发送给FLASH,这时候SO属于高阻状态,说明FLASH是不回数据的,主机也不需要FLASH回。主机会读到一个0XFF。
主机继续发送时钟,但是SI线上发送什么数据无所谓,通常我们发送0XFF。此时FLASH就会回数据给主机了。
SPI falsh写状态 上图是写状态的,大家应该能看懂了,整个过程FLASH没有在SO线上返回数据。
SPI falsh读数据 上图是读数据,与读状态不同的是,CPU要多发送24bit地址。 在FLASH的规格书中,图29说明了如何编程和擦除,其实也就是前面命令时序的组合。
SPI falsh擦除流程
规格书最后就是一些性能参数,封装等信息了。基本与编程无关。
除了以上特性之外,所有FLASH都共一个特性: FLASH上用于存储数据的每一个BIT,只能由1改写为0。 例如原来FLASH上某个BYTE=0xff,可以将其改写为0x00-0xff的任何值。如果某个BYTE=0x0f,就只能修改为0x00-0x0f之间的值。 擦除可以将一个page、sector、block一次性全部改写为0XFF。 因此,通常在写之前都会进行读出flash、改写数据、擦除flash、写回flash,一共四步操作。完整的流程比较消耗时间。根据特性可以进行优化,例如,知道数据是0XFF,就可以直接改写。
每次操作FLASH,都是先发一个命令,再进行数据通信。在发命令前,要有一个CS的下降沿。
核心板SPIFLASH原理图 这两个SPI FLASH都是接在SPI3控制器上,SPI3控制器还是外扩接口的SPI控制器,也即是说,一个SPI上可能接有3个设备或更多。 为什么要配置两片SPI FALSH?当然不是堆硬件,是为了模拟一个情景:多个SPI总线上挂载多个器件,如果你写的SPI跟SPI FLASH驱动不能适应这样的场景,我觉得不是一个好程序。我们提供的源码,就是要处理这种情况。
需要设计两个驱动,一个是cpu上的SPI控制器的驱动;另外一个则是板上外设FLASH的驱动。 分别命名为mcu_spi和dev_flash。具体代码见例程。 在做驱动前我们要认识以下概念:
SPI 控制器:STM32上的SPI3就是一个控制器。在程序中就是一些参数或者寄存器。 SPI驱动:为了用SPI3,我们会写一段代码,这段代码就是SPI驱动。
spi驱动是为了配套SPI控制器,在STM32中有多个SPI控制器,SPI驱动有几套?
SPI FLASH设备:设备就是实物,我们的硬件有两个SPI FLASH设备。 SPI FLASH驱动:我们写的代码,操作(读写)FLASH的代码,就是驱动。
我们有两个FLASH设备,写几个驱动?
SPI能干什么。
SPI属于全双工总线。 发送时钟信号,在发送数据的同时会收到数据。这个特性反映在SPI驱动上就是
发送出去一个字节,就会收到一个字节。 接收一个字节,就需要发送一个字节。 因此我们认为,SPI_WRITE或者SPI_READ这样单独读写的接口不符合SPI特性
SPI可以运行在不同的频率和模式。 SPI的CS可以控制。
用SPI的程序想要SPI干什么?
我们可能用SPI控制LCD,SPI FLASH,RF24L01等设备。 对于这些设备,有只写操作的,也有读写都要操作的。 对于CS管脚,在占用SPI控制器时,有可能要变化电平,例如SPI FALSH,在发送命令时就需要一个CS下降沿。
SPI驱动和SPI设备的关系
在我们的硬件上,只用一个SPI控制器SPI3,配合3根CS线。一套SPI驱动如何控制三个设备呢? 千万不要将CS的控制放到SPI FALSH驱动中。CS属于SPI控制器的一部分。
如果不将CS控制和SPI控制器绑定,配合多个CS时,编写代码很容易造成SPI控制器冲突。
要控制多个SPI设备,在接口传入一个参数表明操作哪个接口即可。
因此接口我们设置如下:
s32 mcu_spi_init(void); s32 mcu_spi_open(SPI_DEV dev, SPI_MODE mode, u16 pre); s32 mcu_spi_close(SPI_DEV dev); s32 mcu_spi_transfer(SPI_DEV dev, u8 *snd, u8 *rsv, s32 len); s32 mcu_spi_cs(SPI_DEV dev, u8 sta); 从上到下分别是:初始化,打开(占用),关闭(释放),传输,CS控制。
我们看看初始化代码
s32 mcu_spi_init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStruct; //初始化片选,系统暂时设定为3个SPI,全部使用SPI3 //DEV_SPI_3_1, 核心板上的SPI FLASH RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB,GPIO_Pin_14); //DEV_SPI_3_2, 底板的SPI FLASH RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOG, &GPIO_InitStructure); GPIO_SetBits(GPIOG,GPIO_Pin_15); //DEV_SPI_3_3, 核心板外扩SPI GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOG, &GPIO_InitStructure); GPIO_SetBits(GPIOG,GPIO_Pin_6); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//---PB3~5 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//---复用功能 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//---推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//---100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//---上拉 GPIO_Init(GPIOB, &GPIO_InitStructure);//---初始化 //配置引脚复用映射 GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI3); //PB3 复用为 SPI3 GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI3); //PB4 复用为 SPI3 GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI3); //PB5 复用为 SPI3 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);// ---使能 SPI3 时钟 // 复位SPI模块 SPI_I2S_DeInit(SPI_DEVICE); SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//---双线双向全双工 SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//---主模式 SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//---8bit帧结构 SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//----串行同步时钟的空闲状态为低电平 SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//---数据捕获于第1个时钟沿 SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //---SPI_NSS_Hard; 片选由硬件管理,SPI控制器不管理 SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; //---预分频 SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//---数据传输从 MSB 位开始 SPI_InitStruct.SPI_CRCPolynomial = 7;//---CRC 值计算的多项式 SPI_Init(SPI_DEVICE, &SPI_InitStruct); //SPI_SSOutputCmd(SPI_DEVICE, DISABLE); DevSpi3Gd = -1; return 0; }
6~30行,初始化使用SPI3控制器的3个SPI接口的CS脚,初始化为输出,并且输出高电平。 34~44,初始化SPI控制器3个引脚,也就是把IO配置为AF功能,并且配置为SPI3的AF功能。 46,打开SPI时钟。 48,复位SPI3控制器。 50~60,配置SPI3控制器,每个配置什么意思在代码注释都说明了。
根据硬件定义3个SPI设备号,3个SPI都使用SPI3控制器
typedef enum{ DEV_SPI_NULL = 0, DEV_SPI_3_1 = 0X31,//核心板上的SPI使用SPI3,定义为SPI_3_1 DEV_SPI_3_2, //底板板上的SPI使用SPI3,定义为SPI_3_2 DEV_SPI_3_3, //外扩的SPI定义为SPI_3_3}SPI_DEV;
传输是最关键函数 看代码,入口参数3个:
snd和rsv是指针,发送数据从snd取,接收到的数据保存到rsv; len是数据长度。 snd或者rsv其中一个可以为NULL,但是不能全部为0, 如果rsv为NULL,则意味着只是想发送数据,接收到的数据丢弃; 如果snd为空,则意味着只想接收数据,函数会默认发送0XFF: 实际应用中,有外设是不允许默认发送0XFF的,例如触摸控制芯片XPT2046, 如果在读数据时发送0XFF,会立刻启动XTP2046AD转换,造成读到的触摸参数错乱。 最新的SPI通信代码请从GIT上提取(上面的代码会持续更新)
s32 mcu_spi_transfer(u8 *snd, u8 *rsv, s32 len) { s32 i = 0; s32 pos = 0; u32 time_out = 0; u16 ch; if( ((snd == NULL) && (rsv == NULL)) || (len < 0) ) { return -1; } /* 忙等待 */ time_out = 0; while(SPI_I2S_GetFlagStatus(SPI_DEVICE, SPI_I2S_FLAG_BSY) == SET) { if(time_out++ > MCU_SPI_WAIT_TIMEOUT) { return(-1); } } /* 清空SPI缓冲数据,防止读到上次传输遗留的数据 */ time_out = 0; while(SPI_I2S_GetFlagStatus(SPI_DEVICE, SPI_I2S_FLAG_RXNE) == SET) { SPI_I2S_ReceiveData(SPI_DEVICE); if(time_out++ > 2) { return(-1); } } /* 开始传输 */ for(i=0; i < len; ) { // 写数据 if(snd == NULL)/*发送指针为NULL,说明仅仅是读数据 */ { //uart_printf("--1--"); SPI_I2S_SendData(SPI_DEVICE, 0xff); } else { ch = (u16)snd[i]; SPI_I2S_SendData(SPI_DEVICE, ch); //uart_printf("s%02x ", ch); } i++; // 等待接收结束 time_out = 0; while(SPI_I2S_GetFlagStatus(SPI_DEVICE, SPI_I2S_FLAG_RXNE) == RESET) { time_out++; if(time_out > MCU_SPI_WAIT_TIMEOUT) { return -1; } } // 读数据 if(rsv == NULL)/* 接收指针为空,读数据后丢弃 */ { //uart_printf("--2--"); SPI_I2S_ReceiveData(SPI_DEVICE); } else { ch = SPI_I2S_ReceiveData(SPI_DEVICE); rsv[pos] = (u8)ch; //uart_printf("r%02x ", ch); } pos++; } return i; }
13到21,等待SPI控制器不忙。
23-32,读走SPI控制器里面的缓存数据。
进入FOR循环开始传输。
判断输入参数snd,如果为空(0),就说明只是想读数据,没数据发送。那么我们就自作主张,发送一个0XFF过去。
发送的同时,控制器就在接收数据了,53到60行等待接收完毕。
62行开始是接收数据,如果接收缓冲为空(0),我们就仅仅将数据读出来丢弃掉。
SPI 驱动编写要考虑两方面:
SPI FLASH的功能实现。 SPI FALSH驱动架构。
FLASH功能包括,擦除,读,写,芯片ID等。 其中各操作还分page,sector,BLOCK,chip; 例如擦,可以擦1个sector,也可以擦1个BLOCK,或者是整片擦除。 读就没有要求,可以从任何地址读。 写也没有要求,可以从任何地址写。 但是为了方便APP使用,特别是文件系统,我们会封装sector擦、读、写函数。
设计SPI FLASH架构前要考虑的问题是:
多个FLASH挂在多个SPI上。我们的硬件就是两片FLASH挂在两个SPI接口上(同一个硬件SPI控制器)。
FALSH型号自动识别,就算硬件更换了Flash,驱动也不需要修改。
最终的SPI FLASH驱动可以做到,一套SPI FLASH驱动,可以处理多个挂在不同SPI上的不同flash芯片。 要实现这个目标,主要要实现下面3个小目标:
驱动就是一个软件模块。
对上提供操作接口。
对下要求提供挂参数:哪个SPI?什么芯片?
因此我们设计驱动如下:
接口
extern s32 dev_spiflash_readmorebyte(DevSpiFlash *dev, u32 addr, u8 *dst, u32 len);
extern s32 dev_spiflash_write(DevSpiFlash *dev, u8* pbuffer, u32 addr, u16 wlen);
extern s32 dev_spiflash_sector_erase(DevSpiFlash *dev, u32 sector_addr);
extern s32 dev_spiflash_sector_read(DevSpiFlash *dev, u32 sector, u8 *dst);
extern s32 dev_spiflash_sector_write(DevSpiFlash *dev, u32 sector, u8 *src);
extern s32 dev_spiflash_init(void);
extern s32 dev_spiflash_open(DevSpiFlash *dev, char* name);
extern s32 dev_spiflash_test(void);
各接口功能,看名称就知道啥意思了。
芯片参数定义
/*SPI FLASH 信息*/typedef struct{ char *name; u32 JID; u32 MID; /*容量,块数,块大小等信息*/ u32 sectornum;//总块数 u32 sector;//块大小 u32 structure;//总容量}_strSpiFlash;/* 常用的SPI FLASH 参数信息*/_strSpiFlash SpiFlashPraList[]={ {"MX25L3206E", 0XC22016, 0XC215, 1024, 4096, 4194304}, {"W25Q64JVSI", 0Xef4017, 0Xef16, 2048, 4096, 8388608}};
对于各种芯片,抽象定义一个结构体,结构体成员包含了芯片信息。 然后定义一个列表数组,表明当前支持的芯片型号。
设备树定义
/*SPI FLASH设备定义*/typedef struct{ char *name;//设备名称 SPI_DEV spi;//挂载在哪条SPI总线 _strSpiFlash *pra;//设备信息}DevSpiFlash;/* 设备树定义*/#define DEV_SPI_FLASH_C 2//总共有两片SPI FLASHDevSpiFlash DevSpiFlashList[DEV_SPI_FLASH_C]={ /*有一个叫做board_spiflash的SPI FLASH挂在DEV_SPI_3_2上,型号未知*/ {"board_spiflash", DEV_SPI_3_2, NULL}, /*有一个叫做board_spiflash的SPI FLASH挂在DEV_SPI_3_1上,型号未知*/ {"core_spiflash", DEV_SPI_3_1, NULL},};
设备树的意义就是告诉驱动,什么东西挂在什么地方。这样驱动就可以跟硬件剥离,也就能兼容更多硬件。 具体驱动设计请看源码,后续会对文档进行更详细更新 我们抽一个SPI FLASH驱动函数分析。
/** *@brief: dev_spiflash_readJTD *@details: 读FLASH JTD号 *@param[in] void *@param[out] 无 *@retval: */ static u32 dev_spiflash_readJTD(DevSpiFlash *dev) { u32 JID; s32 len = 1; u8 command = SPIFLASH_RDJID; u8 data[3]; mcu_spi_cs(dev->spi, 0); len = 1; mcu_spi_transfer(dev->spi, &command, NULL, len); len = 3; mcu_spi_transfer(dev->spi, NULL, data, len); mcu_spi_cs(dev->spi, 1); JID = data[0]; JID = (JID<<8) + data[1]; JID = (JID<<8) + data[2]; return JID; }
以上是读FLASH JTD函数
15行,拉低CS脚,使能对应的SPI FLASH设备。 17行,写数据,第3个参数为空,我们只是写命令,1个字节数据而已。 19行,读数据,读3个字节,第二个参数为空,函数将默认发送0XFF。 20行,拉高CS脚。
我们看看FLASH规格书中读JTD的时序图。
readjtd
驱动和设备分离的好处 这样的驱动架构有什么好处呢? 请看测试程序,测试程序传入一个名称,就可以操作对应FLASH了。不用关心FLASH是什么型号,挂在什么地方。更加不会去操作CS管脚。
void dev_spiflash_test_fun(char *name){ u32 addr; u16 tmp; u8 i = 1; u8 rbuf[4096]; u8 wbuf[4096]; u8 err_flag = 0; DevSpiFlash dev; s32 res; wjq_log(LOG_FUN, ">:-------dev_spiflash_test------- "); res = dev_spiflash_open(&dev, name); wjq_log(LOG_FUN, ">:-------%s------- ", dev.name); if(res == -1) { wjq_log(LOG_FUN, "open spi flash ERR "); while(1); } i = 0; for(tmp = 0; tmp < 4096; tmp++) { wbuf[tmp] = i; i++; } //sector 1 进行擦除,然后写,校验。 wjq_log(LOG_FUN, ">:-------test sector erase------- ", addr); addr = 0; dev_spiflash_sector_erase(&dev, addr); wjq_log(LOG_FUN, "erase..."); dev_spiflash_sector_read(&dev, addr, rbuf);;//读一页回来 wjq_log(LOG_FUN, "read..."); for(tmp = 0; tmp < dev.pra->sector; tmp++) { if(rbuf[tmp] != 0xff)//擦除后全部都是0xff { wjq_log(LOG_FUN, "%x=%02X ", tmp, rbuf[tmp]);//擦除后不等于0XFF,坏块 err_flag = 1; } } dev_spiflash_sector_write(&dev, addr, wbuf); wjq_log(LOG_FUN, "write..."); dev_spiflash_sector_read(&dev, addr, rbuf); wjq_log(LOG_FUN, "read..."); wjq_log(LOG_FUN, " >:test wr.. "); for(tmp = 0; tmp < dev.pra->sector; tmp++) { if(rbuf[tmp] != wbuf[tmp]) { wjq_log(LOG_FUN, "%x ", tmp);//读出来的跟写进去的不相等 err_flag = 1; } } if(err_flag == 1) wjq_log(LOG_FUN, "bad sector "); else wjq_log(LOG_FUN, "OK sector "); dev_spiflash_close(&dev);}
在main中初始化,按下按键则进行测试。
/* Infinite loop */ mcu_uart_open(3); wjq_log(LOG_INFO, "hello word! "); mcu_i2c_init(); mcu_spi_init(); dev_key_init(); //mcu_timer_init(); dev_buzzer_init(); dev_tea5767_init(); dev_dacsound_init(); dev_spiflash_init(); dev_key_open(); //dev_dacsound_open(); //dev_tea5767_open(); //dev_tea5767_setfre(105700); while (1) { /*驱动轮询*/ dev_key_scan(); /*应用*/ u8 key; s32 res; res = dev_key_read(&key, 1); if(res == 1) { if(key == DEV_KEY_PRESS) { //dev_buzzer_open(); //dev_dacsound_play(); dev_spiflash_test(); GPIO_ResetBits(GPIOG, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2| GPIO_Pin_3); //dev_tea5767_search(1); } else if(key == DEV_KEY_REL) { //dev_buzzer_close(); GPIO_SetBits(GPIOG, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2| GPIO_Pin_3); } } Delay(1); /*测试触摸按键*/ //dev_touchkey_task(); //dev_touchkey_test(); }
代码写好后,调用FLASH测试程序就死机。第一行调试信息都没有输出。一进函数就死,基本上都是堆栈溢出问题,俗称栈爆了。 一般都是局部变量申请太大,造成堆栈溢出,或者是进函数前堆栈已经临界,函数没申请多少局部变量也会造成溢出。 测试函数申请了8K局部变量造成死机,根本原因是我们没有根据工程实际情况初始化堆栈。
void dev_spiflash_test(void){ u32 addr; u16 tmp; u8 i = 1; u8 rbuf[4096]; u8 wbuf[4096]; u8 err_flag = 0;
堆栈在启动代码startup_stm32f40_41xxx.s的开头配置,默认仅仅配置了0X400字节栈。堆的默认配置也不大,都需要根据工程实际情况修改。
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
此处我们暂时将栈放大到16K,以便测试程序运行。 修改后测试程序正常运行。 类似的死机问题还有一个,而且经常会出现,那么就是一退出函数就死机。这样的问题通常都是因为在函数内操作内存越界,例如野指针啊,或者是写数组超出数组范围。
2
调试一个新IC外设,一般先调通能读芯片ID。 读ID失败,检查程序是否有笔误,如有,修正。 检查后,还是不行,怀疑硬件问题。 将SPI通信的4条PIN全部改为IO,全部输出高电平,用万用表检测。 然后全部输出低电平,用万用表检测。 同时检测FLASH其他管脚,发现WP电平不对。 检查原理图,发现原理图上WP脚连接有误,应该连接到VCC,但是原理图原来增加了1个下拉电阻用于调试,在焊接时不应该直接焊上,样板错误焊上了。 去掉电阻再量电压,1.7V,半高电平,估计芯片有问题了。 换另外一块样板,上电后测试,电平正常。
3
恢复调试过程对驱动跟测试程序的修改,上电,正常读出FLASH ID。
4
对sector 1进行擦,写,读操作,测试FLASH。测试结果:
hello word! board_spiflash jid:0xc22016 board_spiflash mid:0xc215 core_spiflash jid:0xef4017 core_spiflash mid:0xef16 :——-dev_spiflash_test——- spi flash type:MX25L3206E :——-board_spiflash——- :——-test sector erase——- erase…read…write…read… :test wr.. OK sector :——-dev_spiflash_test——- spi flash type:W25Q64JVSI :——-core_spiflash——- :——-test sector erase——- erase…read…write…read… :test wr.. OK sector
经测试,SPI最快设置为SPI_BaudRatePrescaler_4,也就是PCLK(84M)/4=21M,SPI3理论最快速度。也可以正常通信。 使用DSLOGIC逻辑分析仪抓到的波形
SPI falsh时序波形 放大后可以看到时钟频率时21.05M,但是同时也发现一个问题,在两个字节之间的间隔,很大,浪费通信时间,程序需要优化。
SPI falsh时序波形间隔大 当然,系统目前还没有跑其他设备,例如I2S,USB,网口等,等全部跑起来,可能就因为干扰,SPI只能降速了。
下一篇:SDIO-TF CARD