【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
前面我们学习mcu,一般都是模板和模块之间的接口,比如说串口、usb、eth这种。还有一种接口,更多的是芯片和芯片之间的接口,比如说spi、iic、iis、sdio这种,这其中尤其是以spi最为典型。spi本身是一种总线协议,但是总线上面传输什么格式数据,这是不一定的。另外,spi对接的,可能是norflash,可能是nand flash,可能是wifi,可能是ad/da芯片,也可能是一块屏幕。
所以学习spi的时候,一般需要分成两部分。第一部分,就是mcu上面的spi应该如何配置、如何发命令;另外一部分,就是和mcu对接的spi芯片,需要spi发什么样的命令才能进行通信。今天我们spi对接的就是一块norflash芯片。
1、基本电路
学习前,我们可以简单了解一下norflash的芯片接口,其实也不复杂,主要就是四根线。一个是cs,一个是sck,一个是miso(master in slave out),一个是mosi(master out slave in)。
2、配置spi基本属性
spi配置方面,主要就是两个部分。一个是设置spi的gpio属性,对这四个pin进行设置;还有一个就是配置spi的基本属性。前面gpio的设置方法,我们都比较熟悉,今天看下spi的设置方法。
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(FLASH_SPI, &SPI_InitStructure);
SPI_Cmd(FLASH_SPI, ENABLE);
3、利用spi实现flash协议
spi配置好了之后,这个时候就可以结合norflash芯片手册来编写代码了。编写的代码,要严格按照flash给出的时序来做。出现问题的话,也只有拿着协议手册、示波器和代码,来做比对分析了,
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
SPI_FLASH_WriteEnable();
SPI_FLASH_WaitForWriteEnd();
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(W25X_SectorErase);
SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
SPI_FLASH_SendByte(SectorAddr & 0xFF);
SPI_FLASH_CS_HIGH();
SPI_FLASH_WaitForWriteEnd();
}
这个函数其实就是根据norflash的时序来做的。它的主要功能是对某一个sector进行erase操作。首先写使能,等待写使能结束。接着cs拉低,依次输入erase命令、sector的24位地址,cs拉高。最后等待写结束。整个操作结束,对应的sector就erase好了。这里面W25X_SectorErase就是flash特有的命令,类似的命令还有这些,
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_ReadData 0x03
#define W25X_FastReadData 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_PageProgram 0x02
#define W25X_BlockErase 0xD8
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID 0xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JedecDeviceID 0x9F
如果是其他的spi芯片,一般也会给出对应的命令和时序要求的。一开始大家自己不会写,其实没有关系,只要看懂别人的代码就可以。因为大多数norflash、nandflash的命令都是差不多的。后期编写其他spi芯片,也只需要看懂别人的示例代码,自己会改写就好了。
4、测试方法
测试还是比较简单的,首先读取device id,接着读取flash id。如果flash id读取没有问题,先erase一个sector的数据,接着依次write数据、read数据。read出来的数据,要和之前write的数据进行比较。没问题,则代表数据ok;有问题,则代表写入失败。最后则是flash下电处理。
5、编译验证
一起都准备好之后,就可以开始烧入了。运行的时候,串口提示读取不到flash id,看了一下代码,确认是作者把cs的a3当然了a4,修改一下
#define FLASH_CS_PIN GPIO_Pin_4
#define FLASH_CS_GPIO_PORT GPIOA
#define FLASH_CS_GPIO_CLK RCC_AHB1Periph_GPIOA
重新编译烧入,这个时候可以读到flash id,但是测试没有开始,单步调试一下,发现id不匹配。可能是因为作者后期替换了flash芯片,我们添加一下测试的flash id,
#define sFLASH_ID 0xEF4016 //W25Q32
#define pFLASH_ID 0x856016 //P25Q32
#define qFLASH_ID 0x684016
判断逻辑也重新修改下,
if(FlashID == sFLASH_ID||FlashID == pFLASH_ID||FlashID == qFLASH_ID)
{
/* ...... */
}
至此,测试可以顺利进行了。不过测试的时候,发现flash id有的时候可以读出来;有的时候会失败。不太清楚失败的原因是flash原因,还是读的太快了。这个时候我们把延时放大一点,至少读不出来的现象没有了,
DeviceID = SPI_FLASH_ReadDeviceID();
Delay( 20000 );
FlashID = SPI_FLASH_ReadID();
重新编译后,上电、复位、测试,输出的结果是这样的,
6、总结
从存储的角度来说,有的时候mcu生成的文件比较大,或者是一部分数据mcu放不下,例如字体、图片、配置文件,这些都需要放在norflash、nandflash里面,这都是常有的事情。建议大家好好掌握一下。