一、简介
SPI是串行外设接口(Serial Peripheral lnterface)的缩写。SPI是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,如NRF24L01、VS1053、SD卡等。
(1)速度:串口的通信一般也就是115200bps,但是SPI的通信速度可以达到10Mbps,接近快了一百倍,所以在配置的时候需要注意,一般不超过10Mbps。
(2)同步:采用同步方式(Synchronous)传输数据,主设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样,来保证数据在两个设备之间是同步传输。—简单地说,发送数据的时候,必须同时接收到数据,由时钟控制。
(3)全双工:发送数据的时候能够接收数据,接收数据的时候能够发送数据,即可以同时双向通信。
二、 spi四种模式
SPI的相位(CPHA)和极性(CPOL)都可以为0或1,对应的4种组合构成了SPI的4种模式
Mode 0: CPOL=0, CPHA=0
Mode 1 :CPOL=0, CPHA=1
Mode 2 :CPOL=1, CPHA=0
Mode 3 :CPOL=1, CPHA=1
时钟极性CPOL: 即SPI空闲时,时钟信号SCLK的电平(1:空闲时高电平; 0:空闲时低电平)
时钟相位CPHA:即SPI在SCLK第几个边沿开始采样(0:第一个边沿开始; 1:第二个边沿开始)
极性:polarity
相位:phase
常用的是mode 0 和mode 3,这两种模式的相同的地方是都在时钟上升沿采样传输数据,区别这两种方式的简单方法就是看空闲时,时钟的电平状态,低电平为mode 0 ,高电平为mode 3。
三、spi四线
SPI FLASH 一般用于存储LCD显示屏所要显示的图片,视频数据。
四根线:SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
或者说是MOSI、MISO、SCLK、CS四根,CS和SS是一样的,只是表示方法不同。
SCLK:串行时钟线,用于数据的同步。
MOSI:主机输出数据,从机接收数据。
MISO:主机接收数据,从机输出数据。
SS/CS:控制从机是否工作,往往是低电平有效,低电平选中从设备。
就比如STM32和LCD屏,使用SPI进行通信,单片机就是主机,LCD就是从机,单片机主机发送数据给从机LCD。
其中的SCLK、MOSI、MISO三根线是可以复用连接的,唯独片选信号线CS是要连接到器件N,这也代表了多个从设备接在一起 的时候,被片选的设备才可以进行工作,只能多选一,当其中一个设备被选中CS引脚为低电平的时候,其他引脚一定要设置为高电平,避免传输数据的时候发生紊乱。
四、编程
SPI读取W25Q128
(1)读取设备ID
1.根据设备数据手册读时序图,根据固件库参考手册编写函数
(1)GPIO初始化、SPI初始化
(2)SPI write初始化(主机发送数据的时候,从机会返回数据给单片机)
(3)读取ID,读取id的目的是验证自己的写数据函数是否正确。
如果出现可上可下的梯形时序图,那么表示可以是任意数据,下图简单示例一下,学会看图。
根据你所采用的SPI模式进行对比,例如SPI模式0,传输数据开始:
(1)片选信号从高变低,选中从设备。
(2)空闲时时钟的电平状态为低电平,时钟第一个边沿触发,从下图读出的数据就是0000 0100,即0x04,一字节数据发送完毕。
(3)片选信号从高变低,不选中从设备。
读从设备的ID,Read Manufacturer / Device lD (90h),读取厂商的ID,90h是命令,具体得看从器件的数据手册,一般你Ctrl+F搜索ID就可以找得到,通信开始拉低片选信号,结束时候拉高。
大概意思:
读取设备lD指令,该指令是通过驱动CS引脚拉低,并移动指令90h后跟一个0x00的24位地址来启动的。最高有效位(MSB)优先。
初始化、发送字节数据、读取ID的部分代码
static GPIO_InitTypeDef GPIO_InitStructure;
static SPI_InitTypeDef SPI_InitStructure;
#define W25QXX_SS PBout(14)
void w25qxx_init(void)
{
/*!< Enable the SPI clock,使能SPI1硬件时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/*!< Enable GPIO clocks,使能GPIOB硬件时钟 */
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOB, ENABLE);
//SPI1端口配置 PB3 PB4 PB5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStructure);
/*!< Connect SPI1 pins to AF3 AF4 AF5 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);
//初始化片选引脚 PB14
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
//输出功能
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
//速度50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//推挽复用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
//上拉
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//由于M4芯片还没有真正配置好,先不让外部SPI设备工作
W25QXX_SS = 1;
/*!< SPI configuration ,SPI的配置*/
//设置SPI为双线双向全双工通信
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
//配置M4工作在主机模式
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
//SPI的发送和接收都是8位数据位
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
//串口时钟线(SCLK)空闲的时候为高电平,这里电平的设置要根据通信的外围设备有关系的
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
//串行时钟的第二跳变沿进行数据采样,即采用了模式3
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
//很多时候基于多设备通信,片选引脚都设置为软件控制
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //SPI通信时钟 = 84MHz/16=5.25MHz
//最高有效位优先,根据通信的外围设备有关系的
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStructure);
/*!< Enable the sFLASH_SPI ,使能SPI1硬件*/
SPI_Cmd(SPI1, ENABLE);
}
//发送一个字节数据
uint8_t SPI1_SendByte(uint8_t byte)
{
/*!< Loop while DR register in not emplty */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/*!< Send byte through the SPI1 peripheral */
SPI_I2S_SendData(SPI1, byte);
/*!< Wait to receive a byte */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/*!< Return the byte read from the SPI bus */
return SPI_I2S_ReceiveData(SPI1);
}
//读取厂家ID数据
uint16_t w25qxx_read_id(void)
{
uint16_t id=0;
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x90指令
SPI1_SendByte(0x90);
//发送24bit地址,全都是0
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
//读取厂商ID,填写任意参数,十六位数据,先将获取的放在高八位
id = SPI1_SendByte(0xFF)<<8;
//读取设备ID,填写任意参数,读取第八位数据
id |= SPI1_SendByte(0xFF);
//片选引脚为高电平
W25QXX_SS = 1;
//打印出ID
return id;
}
(2)读取指定地址的数据
英文大概意思:
(1)读取数据指令允许从内存中顺序地读取一个或多个数据字节。
(2)该指令是通过驱动/CS引脚低,然后移动指令代码03h ,以及后面的24位地址到Dl引脚发的。
(3)代码和地址位被锁在时钟引脚的上升边缘上。地址被接收后,寻址存储器位置的数据字节将被移出在以最有效位(MSB)首先的CLK的下降边缘的DO引脚上。在每个字节的数据被移出后,这个地址会自动增加到下一个更高的地址,从而允许一个连续的数据流。这意味着,只要时钟继续,就可以用一条指令访问整个内存。本指令由driving /CS high完成。
(4)如果在erase、Program或Write cycle (BuSY=1)进程中发出读数据指令,该指令将被忽略,不会对当前周期产生任何影响。读取数据指令允许时钟速率从直流到最大fR(参阅交流电气特性)。
参数:addr是24位地址,*pbuf需要用户在外部定义数组来存储返回的数据,len是数组的长度
void w25qxx_read_data(uint32_t addr,uint8_t *pbuf,uint32_t len)
{
W25QXX_SS=0;//拉低片选信号
SPI1_SendByte(0x03);//发送读数据指令
SPI1_SendByte((addr>>16)&0xFF);//发送读取数据的地址,24位,一次一个字节
SPI1_SendByte((addr>>8)&0xFF);//总共三次
SPI1_SendByte((addr>>0)&0xFF);
while(len--)
{
*pbuf++ = SPI1_SendByte(0XFF);//返回读取的数据
}
W25QXX_SS=1;//拉高片选信号
}
调用:
int main(void)
{
uint16_t id=0;
uint8_t buf[64];
uint32_t i=0;
//系统定时器初始化,时钟源来自HCLK,且进行8分频,
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//串口1,波特率115200bps,开启接收中断
USART1_Init(115200);
//w25qxx初始化
w25qxx_init();
//读取ID
id = w25qxx_read_id();
printf("w25qxx id=%X rn",id);
//读取数据,从0地址开始读取,连续读取64字节
w25qxx_read_data(0,buf,64);
printf("w25qxx read addr from 0 data is:rn");
for(i=0; i<64; i++)
{
printf("%02X ",buf
);
}
printf("rn");//打印完回车换行
while(1)
}
(3)扇区擦除(这部分较为繁琐)
英文的大概意思:
(1)扇区擦除都是以4KB为单位,擦除后,所有数据都变为0xFF。
(2)进行扇区擦除之前,先执行写使能指令,解除写保护。
(3)去检查是否已经擦除完成,必须得执行读取状态寄存器指令,如果读取到BUSY位为1,代表说还没有完成;如果BUSY为0,表示已经擦除完成。
(4)执行完擦除扇区指令后,开启写保护
写使能:
//解除写保护
void w25qxx_write_enable(void)
{
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x06指令
SPI1_SendByte(0x06);
//片选引脚为高电平
W25QXX_SS = 1;
}
写失能
//开启写保护
void w25qxx_write_disable(void)
{
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x04指令
SPI1_SendByte(0x04);
//片选引脚为高电平
W25QXX_SS = 1;
}
寄存器
//读状态寄存器1
uint8_t w25qxx_read_status1(void)
{
uint8_t status;
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x05指令
SPI1_SendByte(0x05);
//读取状态寄存器1的值
status = SPI1_SendByte(0xFF);
//片选引脚为高电平
W25QXX_SS = 1;
return status;
}
将上述结合起来,进行扇区擦除
void w25qxx_erase_sector(uint32_t addr)
{
uint8_t status;
//解除写保护
w25qxx_write_enable();
//延时1ms,让W25Q128能够识别到CS引脚电平的变化
delay_ms(1);
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x20指令
SPI1_SendByte(0x20);
//发送24bit地址
SPI1_SendByte((addr>>16)&0xFF);
SPI1_SendByte((addr>>8)&0xFF);
SPI1_SendByte( addr&0xFF);
//片选引脚为高电平
W25QXX_SS = 1;
while(1)
{
//读取状态寄存器1
status= w25qxx_read_status1();
//若BUSY位为0,则跳出循环
if((status & 0x01) == 0)
break;
}
//开启写保护
w25qxx_write_disable();
}
调用
int main(void)
{
uint16_t id=0;
uint8_t buf[64];
uint32_t i=0;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//串口1,波特率115200bps,开启接收中断
USART1_Init(115200);
//w25qxx初始化
w25qxx_init();
//读取ID
id = w25qxx_read_id();
printf("w25qxx id=%X rn",id);
//进行扇区擦除
printf("w25qxx erase from 0 rn");
w25qxx_erase_sector(0);
//读取数据,从0地址开始读取,连续读取64字节
w25qxx_read_data(0,buf,64);
printf("w25qxx read addr from 0 data is:rn");
for(i=0; i<64; i++)
{
printf("%02X ",buf);
}
printf("rn");
while(1)
}
(4)页写
和扇区擦除过程有点像
void w25qxx_write_data(uint32_t addr,uint8_t *pbuf,uint32_t len)
{
uint8_t status;
//解除写保护
w25qxx_write_enable();
//延时1ms,让W25Q128能够识别到CS引脚电平的变化
delay_ms(1);
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x02指令
SPI1_SendByte(0x02);
//发送24bit地址
SPI1_SendByte((addr>>16)&0xFF);
SPI1_SendByte((addr>>8)&0xFF);
SPI1_SendByte( addr&0xFF);
//写入数据
while(len--)
SPI1_SendByte(*pbuf++);
//片选引脚为高电平
W25QXX_SS = 1;
while(1)
{
//读取状态寄存器1
status= w25qxx_read_status1();
//若BUSY位为0,则跳出循环
if((status & 0x01) == 0)
break;
}
//开启写保护
w25qxx_write_disable();
}
调用的时候需要添加头文件,string.h,在主函数中进行调用。
uint8_t SPI1_SendByte(uint8_t byte)
(5)下一篇文章写使用普通的IO口来模拟SPI时序,替代发送一个字节数据函数:
uint8_t SPI1_SendByte(uint8_t byte)
一、简介
SPI是串行外设接口(Serial Peripheral lnterface)的缩写。SPI是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,如NRF24L01、VS1053、SD卡等。
(1)速度:串口的通信一般也就是115200bps,但是SPI的通信速度可以达到10Mbps,接近快了一百倍,所以在配置的时候需要注意,一般不超过10Mbps。
(2)同步:采用同步方式(Synchronous)传输数据,主设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样,来保证数据在两个设备之间是同步传输。—简单地说,发送数据的时候,必须同时接收到数据,由时钟控制。
(3)全双工:发送数据的时候能够接收数据,接收数据的时候能够发送数据,即可以同时双向通信。
二、 spi四种模式
SPI的相位(CPHA)和极性(CPOL)都可以为0或1,对应的4种组合构成了SPI的4种模式
Mode 0: CPOL=0, CPHA=0
Mode 1 :CPOL=0, CPHA=1
Mode 2 :CPOL=1, CPHA=0
Mode 3 :CPOL=1, CPHA=1
时钟极性CPOL: 即SPI空闲时,时钟信号SCLK的电平(1:空闲时高电平; 0:空闲时低电平)
时钟相位CPHA:即SPI在SCLK第几个边沿开始采样(0:第一个边沿开始; 1:第二个边沿开始)
极性:polarity
相位:phase
常用的是mode 0 和mode 3,这两种模式的相同的地方是都在时钟上升沿采样传输数据,区别这两种方式的简单方法就是看空闲时,时钟的电平状态,低电平为mode 0 ,高电平为mode 3。
三、spi四线
SPI FLASH 一般用于存储LCD显示屏所要显示的图片,视频数据。
四根线:SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
或者说是MOSI、MISO、SCLK、CS四根,CS和SS是一样的,只是表示方法不同。
SCLK:串行时钟线,用于数据的同步。
MOSI:主机输出数据,从机接收数据。
MISO:主机接收数据,从机输出数据。
SS/CS:控制从机是否工作,往往是低电平有效,低电平选中从设备。
就比如STM32和LCD屏,使用SPI进行通信,单片机就是主机,LCD就是从机,单片机主机发送数据给从机LCD。
其中的SCLK、MOSI、MISO三根线是可以复用连接的,唯独片选信号线CS是要连接到器件N,这也代表了多个从设备接在一起 的时候,被片选的设备才可以进行工作,只能多选一,当其中一个设备被选中CS引脚为低电平的时候,其他引脚一定要设置为高电平,避免传输数据的时候发生紊乱。
四、编程
SPI读取W25Q128
(1)读取设备ID
1.根据设备数据手册读时序图,根据固件库参考手册编写函数
(1)GPIO初始化、SPI初始化
(2)SPI write初始化(主机发送数据的时候,从机会返回数据给单片机)
(3)读取ID,读取id的目的是验证自己的写数据函数是否正确。
如果出现可上可下的梯形时序图,那么表示可以是任意数据,下图简单示例一下,学会看图。
根据你所采用的SPI模式进行对比,例如SPI模式0,传输数据开始:
(1)片选信号从高变低,选中从设备。
(2)空闲时时钟的电平状态为低电平,时钟第一个边沿触发,从下图读出的数据就是0000 0100,即0x04,一字节数据发送完毕。
(3)片选信号从高变低,不选中从设备。
读从设备的ID,Read Manufacturer / Device lD (90h),读取厂商的ID,90h是命令,具体得看从器件的数据手册,一般你Ctrl+F搜索ID就可以找得到,通信开始拉低片选信号,结束时候拉高。
大概意思:
读取设备lD指令,该指令是通过驱动CS引脚拉低,并移动指令90h后跟一个0x00的24位地址来启动的。最高有效位(MSB)优先。
初始化、发送字节数据、读取ID的部分代码
static GPIO_InitTypeDef GPIO_InitStructure;
static SPI_InitTypeDef SPI_InitStructure;
#define W25QXX_SS PBout(14)
void w25qxx_init(void)
{
/*!< Enable the SPI clock,使能SPI1硬件时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/*!< Enable GPIO clocks,使能GPIOB硬件时钟 */
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOB, ENABLE);
//SPI1端口配置 PB3 PB4 PB5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStructure);
/*!< Connect SPI1 pins to AF3 AF4 AF5 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);
//初始化片选引脚 PB14
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
//输出功能
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
//速度50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//推挽复用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
//上拉
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//由于M4芯片还没有真正配置好,先不让外部SPI设备工作
W25QXX_SS = 1;
/*!< SPI configuration ,SPI的配置*/
//设置SPI为双线双向全双工通信
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
//配置M4工作在主机模式
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
//SPI的发送和接收都是8位数据位
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
//串口时钟线(SCLK)空闲的时候为高电平,这里电平的设置要根据通信的外围设备有关系的
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
//串行时钟的第二跳变沿进行数据采样,即采用了模式3
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
//很多时候基于多设备通信,片选引脚都设置为软件控制
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //SPI通信时钟 = 84MHz/16=5.25MHz
//最高有效位优先,根据通信的外围设备有关系的
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStructure);
/*!< Enable the sFLASH_SPI ,使能SPI1硬件*/
SPI_Cmd(SPI1, ENABLE);
}
//发送一个字节数据
uint8_t SPI1_SendByte(uint8_t byte)
{
/*!< Loop while DR register in not emplty */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/*!< Send byte through the SPI1 peripheral */
SPI_I2S_SendData(SPI1, byte);
/*!< Wait to receive a byte */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/*!< Return the byte read from the SPI bus */
return SPI_I2S_ReceiveData(SPI1);
}
//读取厂家ID数据
uint16_t w25qxx_read_id(void)
{
uint16_t id=0;
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x90指令
SPI1_SendByte(0x90);
//发送24bit地址,全都是0
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
SPI1_SendByte(0x00);
//读取厂商ID,填写任意参数,十六位数据,先将获取的放在高八位
id = SPI1_SendByte(0xFF)<<8;
//读取设备ID,填写任意参数,读取第八位数据
id |= SPI1_SendByte(0xFF);
//片选引脚为高电平
W25QXX_SS = 1;
//打印出ID
return id;
}
(2)读取指定地址的数据
英文大概意思:
(1)读取数据指令允许从内存中顺序地读取一个或多个数据字节。
(2)该指令是通过驱动/CS引脚低,然后移动指令代码03h ,以及后面的24位地址到Dl引脚发的。
(3)代码和地址位被锁在时钟引脚的上升边缘上。地址被接收后,寻址存储器位置的数据字节将被移出在以最有效位(MSB)首先的CLK的下降边缘的DO引脚上。在每个字节的数据被移出后,这个地址会自动增加到下一个更高的地址,从而允许一个连续的数据流。这意味着,只要时钟继续,就可以用一条指令访问整个内存。本指令由driving /CS high完成。
(4)如果在erase、Program或Write cycle (BuSY=1)进程中发出读数据指令,该指令将被忽略,不会对当前周期产生任何影响。读取数据指令允许时钟速率从直流到最大fR(参阅交流电气特性)。
参数:addr是24位地址,*pbuf需要用户在外部定义数组来存储返回的数据,len是数组的长度
void w25qxx_read_data(uint32_t addr,uint8_t *pbuf,uint32_t len)
{
W25QXX_SS=0;//拉低片选信号
SPI1_SendByte(0x03);//发送读数据指令
SPI1_SendByte((addr>>16)&0xFF);//发送读取数据的地址,24位,一次一个字节
SPI1_SendByte((addr>>8)&0xFF);//总共三次
SPI1_SendByte((addr>>0)&0xFF);
while(len--)
{
*pbuf++ = SPI1_SendByte(0XFF);//返回读取的数据
}
W25QXX_SS=1;//拉高片选信号
}
调用:
int main(void)
{
uint16_t id=0;
uint8_t buf[64];
uint32_t i=0;
//系统定时器初始化,时钟源来自HCLK,且进行8分频,
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//串口1,波特率115200bps,开启接收中断
USART1_Init(115200);
//w25qxx初始化
w25qxx_init();
//读取ID
id = w25qxx_read_id();
printf("w25qxx id=%X rn",id);
//读取数据,从0地址开始读取,连续读取64字节
w25qxx_read_data(0,buf,64);
printf("w25qxx read addr from 0 data is:rn");
for(i=0; i<64; i++)
{
printf("%02X ",buf
);
}
printf("rn");//打印完回车换行
while(1)
}
(3)扇区擦除(这部分较为繁琐)
英文的大概意思:
(1)扇区擦除都是以4KB为单位,擦除后,所有数据都变为0xFF。
(2)进行扇区擦除之前,先执行写使能指令,解除写保护。
(3)去检查是否已经擦除完成,必须得执行读取状态寄存器指令,如果读取到BUSY位为1,代表说还没有完成;如果BUSY为0,表示已经擦除完成。
(4)执行完擦除扇区指令后,开启写保护
写使能:
//解除写保护
void w25qxx_write_enable(void)
{
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x06指令
SPI1_SendByte(0x06);
//片选引脚为高电平
W25QXX_SS = 1;
}
写失能
//开启写保护
void w25qxx_write_disable(void)
{
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x04指令
SPI1_SendByte(0x04);
//片选引脚为高电平
W25QXX_SS = 1;
}
寄存器
//读状态寄存器1
uint8_t w25qxx_read_status1(void)
{
uint8_t status;
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x05指令
SPI1_SendByte(0x05);
//读取状态寄存器1的值
status = SPI1_SendByte(0xFF);
//片选引脚为高电平
W25QXX_SS = 1;
return status;
}
将上述结合起来,进行扇区擦除
void w25qxx_erase_sector(uint32_t addr)
{
uint8_t status;
//解除写保护
w25qxx_write_enable();
//延时1ms,让W25Q128能够识别到CS引脚电平的变化
delay_ms(1);
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x20指令
SPI1_SendByte(0x20);
//发送24bit地址
SPI1_SendByte((addr>>16)&0xFF);
SPI1_SendByte((addr>>8)&0xFF);
SPI1_SendByte( addr&0xFF);
//片选引脚为高电平
W25QXX_SS = 1;
while(1)
{
//读取状态寄存器1
status= w25qxx_read_status1();
//若BUSY位为0,则跳出循环
if((status & 0x01) == 0)
break;
}
//开启写保护
w25qxx_write_disable();
}
调用
int main(void)
{
uint16_t id=0;
uint8_t buf[64];
uint32_t i=0;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//串口1,波特率115200bps,开启接收中断
USART1_Init(115200);
//w25qxx初始化
w25qxx_init();
//读取ID
id = w25qxx_read_id();
printf("w25qxx id=%X rn",id);
//进行扇区擦除
printf("w25qxx erase from 0 rn");
w25qxx_erase_sector(0);
//读取数据,从0地址开始读取,连续读取64字节
w25qxx_read_data(0,buf,64);
printf("w25qxx read addr from 0 data is:rn");
for(i=0; i<64; i++)
{
printf("%02X ",buf);
}
printf("rn");
while(1)
}
(4)页写
和扇区擦除过程有点像
void w25qxx_write_data(uint32_t addr,uint8_t *pbuf,uint32_t len)
{
uint8_t status;
//解除写保护
w25qxx_write_enable();
//延时1ms,让W25Q128能够识别到CS引脚电平的变化
delay_ms(1);
//片选引脚为低电平
W25QXX_SS = 0;
//发送0x02指令
SPI1_SendByte(0x02);
//发送24bit地址
SPI1_SendByte((addr>>16)&0xFF);
SPI1_SendByte((addr>>8)&0xFF);
SPI1_SendByte( addr&0xFF);
//写入数据
while(len--)
SPI1_SendByte(*pbuf++);
//片选引脚为高电平
W25QXX_SS = 1;
while(1)
{
//读取状态寄存器1
status= w25qxx_read_status1();
//若BUSY位为0,则跳出循环
if((status & 0x01) == 0)
break;
}
//开启写保护
w25qxx_write_disable();
}
调用的时候需要添加头文件,string.h,在主函数中进行调用。
uint8_t SPI1_SendByte(uint8_t byte)
(5)下一篇文章写使用普通的IO口来模拟SPI时序,替代发送一个字节数据函数:
uint8_t SPI1_SendByte(uint8_t byte)
举报