本实验将使用 STM32F4 的 SPI 来读取外部 SPI FLASH 芯片(W25Q128),实现类似上次实验的功能。
1)配置相关引脚的复用功能,使能 SPI1 时钟。
使能 SPI1 时钟的方法为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能 SPI1 时钟
复用 PB3,PB4,PB5 为 SPI1 引脚的方法为:
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3 复用为 SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4 复用为 SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5 复用为 SPI1
同时我们要设置相应的引脚模式为复用功能模式:
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
2)初始化 SPI1,设置 SPI1 工作模式等。
这一步全部是通过 SPI1_CR1 来设置,我们设置 SPI1 为主机模式,设置数据格式为 8 位, 然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 37.5Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。
在库函数中初始化 SPI 的函数为:
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
SPI_InitTypeDef 的定义:
typedef struct
{
uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS;
uint16_t SPI_BaudRatePrescaler;
uint16_t SPI_FirstBit;
uint16_t SPI_CRCPolynomial;
}SPI_InitTypeDef;
第一个参数 SPI_Direction 是用来设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行 发和串行收方式,这里我们选择全双工模式 SPI_Direction_2Lines_FullDuplex。
第二个参数 SPI_Mode 用来设置 SPI 的主从模式,这里我们设置为主机模式 SPI_Mode_Master,
第三个参数 SPI_DataSiz 为 8 位还是 16 位帧格式选择项,这里我们是 8 位传输,选择 SPI_DataSize_8b。
第四个参数 SPI_CPOL 用来设置时钟极性,我们设置串行同步时钟的空闲状态为高电平所以我 们选择 SPI_CPOL_High。
第五个参数 SPI_CPHA 用来设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升 或下降)数据被采样,可以为第一个或者第二个条边沿采集,这里我们选择第二个跳变沿,所以选择 SPI_CPHA_2Edge
第六个参数 SPI_NSS 设置 NSS 信号由硬件(NSS 管脚)还是软件控制,这里我们通过软件控 制 NSS 关键,而不是硬件自动控制,所以选择 SPI_NSS_Soft。
第七个参数 SPI_BaudRatePrescaler 很关键,就是设置 SPI 波特率预分频值也就是决定 SPI 的时 钟的参数,从 2 分频到 256 分频 8 个可选值,初始化的时候我们选择 256 分频值 SPI_BaudRatePrescaler_256, 传输速度为 84M/256=328.125KHz。
第八个参数 SPI_FirstBit 设置数据传输顺序是 MSB 位在前还是 LSB 位在前,这里我们选择SPI_FirstBit_MSB 高位在前。
第九个参数 SPI_CRCPolynomial 是用来设置 CRC 校验多项式,提高通信可靠性,大于 1 即可。
3)使能 SPI1。
这一步通过 SPI1_CR1 的 bit6 来设置,以启动 SPI1,在启动之后,我们就可以开始 SPI 通讯了。库函数使能 SPI1 的方法为:
SPI_Cmd(SPI1, ENABLE); //使能 SPI1外设
4)SPI 传输数据
通信接口当然需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
固件库提供的接受数据函数原型为:
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
5)查看 SPI 传输状态
在 SPI 传输过程中,我们经常要判断数据是否传输完成,发送区是否为空等等状态,这是通过函数 SPI_I2S_GetFlagStatus 实现的,判断发送是否完成的 方法是:
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);
W25QXX_Read 函数,该函数用于从 W25Q128 的指定地址读 出指定长度的数据。
- //读取 SPI FLASH
- //在指定地址开始读取指定长度的数据
- //pBuffer:数据存储区
- //ReadAddr:开始读取的地址(24bit)
- //NumByteToRead:要读取的字节数(最大 65535)
- void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
- {
- u16 i;
- W25QXX_CS=0; //使能器件
- SPI1_ReadWriteByte(W25X_ReadData); //发送读取命令
- SPI1_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址
- SPI1_ReadWriteByte((u8)((ReadAddr)>>8));
- SPI1_ReadWriteByte((u8)ReadAddr);
- for(i=0;i
- {
- pBuffer[i]=SPI1_ReadWriteByte(0XFF); //循环读数
- }
- W25QXX_CS=1;
- }
由于 W25Q128 支持以任意地址(但是不能超过 W25Q128 的地址范围)开始读取数据,所以,在发送 24 位地址之后,程序就可以开始循环读数据了, 其地址会自动增加的,不过要注意,不能读的数据超过了 W25Q128 的地址范围哦!否则读出来的数据,就不是想要的数据了。
W25QXX_Write函数,这个函数比较长就不再展示,里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内 的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是 否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定 长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长 度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此 循环,直到写入结束。这里还定义了一个 W25QXX_BUFFER 的全局变量,用于擦除时缓 存扇区内的数据。
main函数
- int main(void)
- {
- u8 key;
- u16 i=0;
- u8 datatemp[SIZE];
- u32 FLASH_SIZE;
- u16 id = 0;
-
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
- delay_init(168); //初始化延时函数
- uart_init(115200); //初始化串口波特率为115200
- LED_Init(); //初始化LED
- KEY_Init(); //按键初始化
- W25QXX_Init(); //W25QXX初始化
- while(1)
- {
- id = W25QXX_ReadID();
- if (id == W25Q128 || id == NM25Q128)
- break;
- printf("W25Q128 Check Failed!\r\n");
- delay_ms(500);
- printf("Please Check!\r\n");
- delay_ms(500);
- LED0=!LED0; //DS0闪烁
- }
- printf("W25Q128 Ready!\r\n");
- FLASH_SIZE=16*1024*1024; //FLASH 大小为16字节
- while(1)
- {
- key=KEY_Scan(0);
- if(key==KEY1_PRES)//KEY1按下,写入W25Q128
- {
- printf("Start Write W25Q128....\r\n");
- W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE); //从倒数第100个地址处开始,写入SIZE长度的数据
- printf("W25Q128 Write Finished!\r\n"); //提示传送完成
- }
- if(key==KEY0_PRES)//KEY0按下,读取字符串并显示
- {
- printf("Start Read W25Q128.... \r\n");
- W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE); //从倒数第100个地址处开始,读出SIZE个字节
- printf("The Data Readed Is: "); //提示传送完成
- printf("%s\r\n",datatemp); //显示读到的字符串
- }
- i++;
- delay_ms(10);
- if(i==20)
- {
- LED0=!LED0;//提示系统正在运行
- i=0;
- }
- }
- }
运行视频
SPI