SPI通信协议
片选线:NSS、CS,主机选择从机时,把该信号线置低电平开始,置高电平结束通信
MOSI:主设备输出/从设备输入引脚,该线上的数据方向为主机到从机
SPI通信时序
只有MISO信号是从机产生,其余都是主机产生,MOSI与MISO数据的输入输出是同时进行的,使用SCK进行同步
一般采用图中MSB先行(高位先行)的模式 每次传输单位数不限制,8位16位均可
②传输信号:在时钟信号线上升沿时,主机与从机将数据放在信号线上,置0或1
CPOL/CPHA及通讯模式
SPI共有四种通讯模式,区别在于总线空闲时SCK的时钟状态及数据采样时刻
CPOL:时钟极性,指SPI开始通信前,NSS线为高电平时SCK的状态,CPOL=0时,SCK为低电平,CPOL=1时,SCK为高电平
CPHA:时钟相位:指数据采样的时刻,CPHA=0时,数据的信号在SCK的奇数边沿被采样,CPHA=1时,在SCK的偶数边沿被采样
下图为奇数边沿采样的时序图,CPOL=0 的时候,时钟的奇数边沿是上升沿,而 CPOL=1 的时候,时钟的奇数边沿是下降沿
下图为偶数边沿采样的时序图
主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是模式0和3,都是SCK上升沿时被采样
SPI架构
SPI1位于APB2上,最高速率36M,其余在APB1上,最高18M
SCK时钟分频(CR寄存器中的BR位)fpclk指APB1/2总线的频率
通过写 SPI 的“数据寄存器 DR”把数 据填充到发送缓冲区中,通讯读“数据寄存器 DR”,可以获取接收缓冲区中的内容。
其中数据帧 长度可以通过“控制寄存器 CR1”的“DFF 位”配置成 8 位及 16 位模式;
配置“LSBFIRST 位” 可选择 MSB 先行还是 LSB 先行。
我们一般不使用SPI 外设的标准 NSS 信号线,而是使用普通的 GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号
主机端收发数据时序:
若使能了 TXE 或 RXNE 中断,TXE 或 RXNE 置 1 时会产生 SPI 中断信号,在中断中进行判断和处理,也可以使用DMA方式来收发
SPI初始化结构体:
W25Qxx.c
//PA4----SPI1_NSS //PA5----SPI1_SCK //PA6----SPI1_MISO //PA7----SPI1_MOSI /************************************************************ *函数功能:初始化配置引脚及SPI功能结构体 *参数: *返回值: **************************************************************/ void My_SPI2_Init() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); SPI_CS_High(); SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;//1 这里是模式3 SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;//1 SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;//2分频 SPI_InitStructure.SPI_CRCPolynomial=7; SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//数据帧8位 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(SPI1,&SPI_InitStructure); SPI_Cmd(SPI1,ENABLE); } /************************************************************ *函数功能:发送一个字节 *参数:要发送的字节 *返回值:接收到的字节 **************************************************************/ uint8_t SPI_SendByte(uint8_t Byte) { uint32_t i=1000; while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET)//发送空标志位置1 { if((i--)==0){ Serial_Printf("\r\n send error\r\n"); return 0; } } SPI_I2S_SendData(SPI1,Byte); i=1000; while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET)//接收缓冲区非空置1 { if((i--)==0){ Serial_Printf("\r\n receive error\r\n"); return 0; } } SPI_I2S_ReceiveData(SPI1); } /************************************************************ *函数功能:接收数据 *参数: *返回值:接收到的数据 **************************************************************/ uint8_t SPI_ReadByte() { return (SPI_SendByte(DafutVal)); } /************************************************************ *函数功能:读取flash id *参数: *返回值: **************************************************************/ uint32_t SPI_ReadFlashID() { uint32_t temp1=0,temp2=0,temp3=0,ID=0; SPI_CS_Low(); SPI_SendByte(W25X_JedecDeviceID);//发送读取ID指令 temp1=SPI_ReadByte(); temp2=SPI_ReadByte(); temp3=SPI_ReadByte(); SPI_CS_High(); ID=(temp1<<16)|(temp2<<8)|temp3; return ID; } /************************************************************ *函数功能:芯片写使能 *参数: *返回值: **************************************************************/ void SPI_W25QXX_WriteEnable() { SPI_CS_Low(); SPI_SendByte(W25X_WriteEnable); SPI_CS_High(); } /************************************************************ *函数功能:等待上一次写操作完成 *参数: *返回值: **************************************************************/ void SPI_W25Q64XX_WaitWriteEnd() { uint8_t Status=0; SPI_CS_Low(); SPI_SendByte(W25X_ReadStatusReg); Status=SPI_ReadByte(); while((Status&0x01)==SET)//最低位判断忙碌位 { Status=SPI_ReadByte(); } SPI_CS_High(); } /************************************************************ *函数功能:擦除FLSH指定扇区 *参数:扇区地址 *返回值: **************************************************************/ void SPI_Erase_Sector(uint32_t addr) { SPI_W25QXX_WriteEnable(); SPI_W25Q64XX_WaitWriteEnd(); SPI_CS_Low(); SPI_SendByte(W25X_SectorErase);//发送擦除扇区指令 SPI_SendByte((addr>>16)&0xFF);//高位地址先发送 SPI_SendByte((addr>>8)&0xFF); SPI_SendByte(addr&0xFF); SPI_CS_High(); SPI_W25Q64XX_WaitWriteEnd(); } /************************************************************ *函数功能:读取FLSH内容 *参数:地址,缓存数组,读取个数 *返回值: **************************************************************/ void SPI_Read_Data(uint32_t addr,uint8_t *receiveBuff,uint32_t length) { SPI_CS_Low(); SPI_SendByte(W25X_ReadData);//发送读取指令 SPI_SendByte((addr>>16)&0xFF);//发送读取地址 SPI_SendByte((addr>>8)&0xFF); SPI_SendByte(addr&0xFF); while(length--) { *receiveBuff=SPI_ReadByte(); receiveBuff++; } SPI_CS_High(); } /************************************************************ *函数功能:写入FLSH内容 *参数:地址,写入数组,写入个数(最多支持写入256个字节) *返回值: **************************************************************/ void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t length) { SPI_W25QXX_WriteEnable(); SPI_CS_Low(); SPI_SendByte(W25X_PageProgram);//发送读取指令 SPI_SendByte((addr>>16)&0xFF);//发送读取地址 SPI_SendByte((addr>>8)&0xFF); SPI_SendByte(addr&0xFF); while(length--) { SPI_SendByte(*writeBuff); writeBuff++; } SPI_CS_High(); SPI_W25Q64XX_WaitWriteEnd(); } /************************************************************ *函数功能:读取Flash 的Deveice ID *参数: *返回值: **************************************************************/ uint32_t SPI_ReadDeviceID() { uint32_t temp=0; SPI_CS_Low(); SPI_SendByte(W25X_DeviceID); SPI_ReadByte(); SPI_ReadByte(); SPI_ReadByte(); temp=SPI_ReadByte(); SPI_CS_High(); return temp; } /************************************************************ *函数功能:进入掉电模式 *参数: *返回值: **************************************************************/ void SPI_W25QXX_PowerDown() { SPI_CS_Low(); SPI_SendByte(W25X_PowerDown); SPI_CS_High(); } /************************************************************ *函数功能:唤醒 *参数: *返回值: **************************************************************/ void SPI_W25QXX_WakeUP() { SPI_CS_Low(); SPI_SendByte(W25X_ReleasePowerDown); SPI_CS_High(); }
W25Qxx.h
#ifndef __W25QXX_H_ #define __W25QXX_H_ #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 #define DafutVal0x00 #define SPI_CS_High()GPIO_SetBits(GPIOA,GPIO_Pin_4) #define SPI_CS_Low()GPIO_ResetBits(GPIOA,GPIO_Pin_4) void My_SPI2_Init(void); uint8_t SPI_SendByte(uint8_t Byte); uint8_t SPI_ReadByte(void); void SPI_W25QXX_PowerDown(void); void SPI_W25QXX_WakeUP(void); uint32_t SPI_ReadFlashID(void); uint32_t SPI_ReadDeviceID(void); void SPI_Erase_Sector(uint32_t addr); void SPI_W25Q64XX_WaitWriteEnd(void); void SPI_Read_Data(uint32_t addr,uint8_t *receiveBuff,uint32_t length); void SPI_Write_Data(uint32_t addr,uint8_t *writeBuff,uint32_t length); #endif
main.c
uint8_t ReadBuf[4096];//扇区擦除是擦除4096个字节 uint8_t WriteBuf[256]; int main(void) { uint32_t ID; uint16_t i; OLED_Init(); Serial_Init(); My_SPI2_Init(); Serial_Printf("\r\n SPI_Flash测试\r\n"); //读取ID ID=SPI_ReadFlashID(); Serial_Printf("\r\n FLASH ID:0x%x",ID); OLED_ShowHexNum(1,1,ID,6); //从0地址开始扇区擦除 SPI_Erase_Sector(0); //从0地址写入测试 for(i=0;i<25;i++) { WriteBuf[i]=i; } SPI_Write_Data(0,WriteBuf,25); //从0地址读取测试 SPI_Read_Data(0,ReadBuf,4096);//读取擦除后的扇区 for(i=0;i<4096;i++) { Serial_Printf("0x%x ",ReadBuf[i]); if(i%10==0) { Serial_Printf("\r\n"); } } while(1) { } }