通过SPI的方式,实现对外部flash(W25Q128)的读与写,写入的内容读出后在TFTLCD上显示出来。SPI方式可以控制FLASH,EEPROM,虽然前面的博客时使用IIC来控制EEPROM(24c02),其实是一个结果,用不同的方式实现功能。
原理
我们来简单看一下内部的构造图:
从内部简明图可以看出,主机smart和从机slave都有一个串行移位寄存器,主机通过向他的SPI串行移位寄存器写入数据进行发起数据传输,此寄存器通过MOSI将数据(1BYTE)传输给从机,此时,从机通过自己的移位寄存器将其中的内容(1BYTE)移出来发送给主机,这样就实现了,两个移位寄存器中内容的交换。
所以外设的写操作和读操作是也就是这样被完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
SPI 接口一般使用 4 条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK 时钟信号,由主设备产生。
CS 从设备片选信号,由主设备控制。
通信时钟极性与相位
SPI 总线四线工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性应该一致。
对比
和IIC对比,两者都是相似的配置方式
不同点:
IIC:需要模拟真实的硬件时序,数据接收,数据发送的每一个时序需要自己从底层函数构造起来
SPI:不需要构造最基础的单个字节数据发送与接收函数,可以直接调用库函数
相同点:
初始化结束,构造出能够发送与接受(单个字节)函数,作为调用的接口,在构造能够写入指定的地址,数据,长度等等,都是通过发送和接收函数完成的。
从IIC读写24c02(eeprom)到SPI读写falsh(w25q128),都是先构造使用此方式实现器件的读写基础函数,适用于大多数使用此原理进行通信的外部硬件,再根据具体的外部硬件属性,构造出来真正可以与外部器件通信的函数,如果读者没有发现,就需要大家自己要对自己的代码进行总结,为何要分模块编写我们的器件?
硬件连接
PB12控制flash的片选,PB13控制时钟频率,MISO控制数据接收,MOSI控制数据的输出。所以13,114,15可以配置为推挽输出,且是复用的,12设置成一般的推挽输出就可以。
基础函数
1.使用SPI方式操作外部器件的初始化
1).GPIO初始化函数:
void spi_init(void)
{
GPIO_InitTypeDef GPIO_Initstructure;
SPI_InitTypeDef SPI_Initstructure;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
GPIO_Initstructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Initstructure.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_Initstructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_Initstructure);
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
SPI_Initstructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;
SPI_Initstructure.SPI_CPHA=SPI_CPHA_2Edge;
SPI_Initstructure.SPI_CPOL=SPI_CPOL_High;
SPI_Initstructure.SPI_CRCPolynomial=
SPI_Initstructure.SPI_DataSize=7;
SPI_Initstructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
SPI_Initstructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_Initstructure.SPI_Mode=SPI_Mode_Master;
SPI_Initstructure.SPI_NSS=SPI_NSS_Soft;
SPI_Init(SPI2, &SPI_Initstructure);
SPI_Cmd(SPI2, ENABLE);
write_read(0xff);
}
观察仔细的可能已经发现,上面GPIO初始化少了PB12,为什么这里初始化上呢?因为这个IO口控制两个外设,我们的 F_CS 是连接在 PB12 上面的,W25Q128 和 NRF24L01 共 用 SPI2,所以这两个器件在使用的时候,必须分时复用(通过片选控制)才行。我们的这个IO口的初始化就放在具体操作的外设初始化内。
2).对flash的读写1BYTE函数
uint8_t write_read(uint8_t TXDATA)
{
uint8_t i;
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
i++;
if(i>200)return 0;
}
SPI_I2S_SendData(SPI2, TXDATA);
i=0;
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
{
i++;
if(i>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2) ;
}
根据寄存器标志位判断移位寄存器的状态,参数是要写入的值,return是读到的值,SPI_I2S_SendData(SPI2, TXDATA);和SPI_I2S_ReceiveData(SPI2) ;都是库函数可以调用的,不需要像IIC那样构造时许函数!
3).SPI速度设置
根据预分频值,通过你位操作实现对SPI速率的设置
void SPI2_SetSpeed(uint8_t SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
SPI2->CR1&=0XFFC7;
SPI2->CR1|=SPI_BaudRatePrescaler;
SPI_Cmd(SPI2,ENABLE);
}
2.基于SPI基础函数,搭建属于W25Q128器件的基础通信函数
1)GPIO初始化函数
void W25Q128_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure)<span style="box-sizing: border-