I2C通信优点节省硬件资源。I2C通信缺点弱上拉导致上升沿慢限制通信最大频率。I2C通信最大频率一般认为400kHz。SPI通信**SPISerial Peripheral Interface**是由Motorola公司开发的一种通用数据总线。四根通信线SCKSerial Clock、MOSIMaster Output Slave Input、MISOMaster Input Slave Output、SSSlave Select同步、全双工。支持总线挂载多设备一主多从。SPI通信优点传输频率高速度快。设计简单。SPI通信缺点硬件开销大。SPI硬件电路所有SPI设备的SCK、MOSI、MISO分别连在一起。主机另外引出多条SS控制线分别接到各从机的SS引脚。同一时刻主机只能与一个从机进行通信。输出引脚配置为推挽输出输入引脚配置为浮空或上拉输入。推挽输出高低电平都有很强的驱动能力避免了I2C中上升沿缓慢的问题。当从机SS为高电平即从机未被选中时其MISO引脚必须切换为高阻态从而防止一条线上由多个输出导致的电平冲突问题。移位示意图SPI高位先行因此主机和从机的移位寄存器每个时钟都进行左移操作。移位寄存器的时钟源由主机中的波特率发生器提供它提供的时钟控制主机移位寄存器移位同时通过SCK线输入到从机控制从机移位寄存器的频率实现同步通信。时钟上升沿主机和从机移位寄存器分别左移将最高位放置到MOSI和MISO引脚时钟下降沿主机和从机分别从MISO和MOSI引脚读取数据并放置在移位寄存器最低位。如此循环8次可以实现主机从机全双工通信交换一个字节数据。SPI通信的基础是交换一个字节实现交换一个字节后可以方便地实现发送一个字节、接收一个字节。SPI时序基本单元起始条件SS从高电平切换到低电平主机拉低对应从机的SS信号。终止条件SS从低电平切换到高电平主机拉高对应从机的SS信号。交换一个字节模式0CPOLClock Polarity 时钟极性 0空闲状态时SCK为低电平。CPHAClock Phase时钟相位 0SCK第一个边沿移入数据第二个边沿移出数据。SS下降沿时立刻触发移位输出将最高位输出到各自引脚等待SCK第一个上升沿读取。交换一个字节模式1CPOLClock Polarity 时钟极性 0空闲状态时SCK为低电平。CPHAClock Phase时钟相位 1SCK第一个边沿移出数据第二个边沿移入数据。SS高电平时从机的输出口MISO应该配置为高阻态所以此时MISO电平在低电平与高电平之间。CPHA设置为1在时钟上升沿主机从机同时移出数据并将最高位通过MOSI和MISO输出在下降沿采样各自的输入引脚放置到移位寄存器最低位。交换一个字节模式2与模式0相比SCK反向其余相同CPOLClock Polarity 时钟极性 1空闲状态时SCK为高电平。CPHAClock Phase时钟相位 0SCK第一个边沿移入数据第二个边沿移出数据。交换一个字节模式3与模式1相比SCK反向其余相同CPOLClock Polarity 时钟极性 1空闲状态时SCK为高电平。CPHAClock Phase时钟相位 1SCK第一个边沿移出数据第二个边沿移入数据。SPI时序SPI通常采用指令码读写数据。SPI开始后第一个交换发送给从机的数据为指令码从机中有对应的指令集发送不同的指令可以控制从机完成不同的功能。发送指令向SS指定的设备发送指令0x06指定地址写向SS指定的设备发送写指令0x02随后在指定地址Address[23:0]下写入指定数据Data指定地址读向SS指定的设备发送读指令0x03随后在指定地址Address[23:0]下读取指定数据DataW25Q64简介W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器常应用于数据存储、字库存储、固件程序存储等场景。存储介质Nor Flash闪存时钟频率80MHz/160MHzDual SPI/320MHzQuad SPI存储容量24位地址W25Q6464Mbit/8MByte硬件电路WP写保护低电平有效低电平时不能向芯片写入。W25Q64框图8MB空间划分Block每块64KB共128块 - Sector每个扇区4KB每块共16个扇区 - Page256Byte每个扇区共16页状态寄存器Status Register可以判断芯片是否处于忙状态、是否写保护等256字节的页缓冲器负责在SPI通信时缓存数据时序结束后复制到Flash此时芯片处于忙状态Busy发送该信号到状态寄存器因此写入的一个时序连续写入量不能超过256字节。Flash操作注意事项写入操作时写入操作前必须先进行写使能每个数据位只能由1改写为0不能由0改写为1写入数据前必须先擦除擦除后所有数据变为1擦除必须按最小擦除单元进行连续写入多字节时最多写入一页的数据超过页尾位置的数据会回到页首覆盖写入写入操作结束后芯片会进入忙状态不响应新的读写操作读取操作时直接调用读取时序无需使能无需额外操作没有页的限制读取后不会进入忙状态但不能在忙状态读取常用指令软件SPI读写W25Q64整体框架SPI通信层 - W25Q64硬件驱动层 - main.c调用SPI通信层新建MySPI模块实现通信引脚封装初始化SPI通信起始、中止、交换一个字节W25Q64硬件驱动层调用MySPI实现各种指令和功能的完整时序比如写使能、擦除、页编程、读数据等代码// MySPI.c#includestm32f10x.h// Device header// 封装对SS的操作voidMySPI_W_SS(uint8_tBitVal){GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitVal);// SPI通信非常快所以不用加延时}// 封装对SCK的操作voidMySPI_W_SCK(uint8_tBitVal){GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitVal);}// 封装对MOSI的操作voidMySPI_W_MOSI(uint8_tBitVal){GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitVal);}// 封装对MISO的操作uint8_tMySPI_R_MISO(void){returnGPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);}voidMySPI_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 配置GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;// 输出引脚配置推挽输出模式GPIO_InitStructure.GPIO_PinGPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU;// 输入引脚配置上拉输入模式GPIO_InitStructure.GPIO_PinGPIO_Pin_6;GPIO_Init(GPIOA,GPIO_InitStructure);// 设置默认电平MySPI_W_SS(1);// SS默认高电平MySPI_W_SCK(0);// 模式0下SCK默认低电平}// 产生起始条件voidMySPI_Start(void){MySPI_W_SS(0);}// 产生终止条件voidMySPI_Stop(void){MySPI_W_SS(1);}// 交换一个字节uint8_tMySPI_SwapByte(uint8_tByteSend){uint8_ti,ByteReceive0x00;for(i0;i8;i){// 开始条件产生后主机左移并把最高位写到MOSIMySPI_W_MOSI(ByteSend(0x80i));// SCK上升沿到来后主机读取MISOMySPI_W_SCK(1);if(MySPI_R_MISO()1){ByteReceive|(0x80i);}// 产生下降沿MySPI_W_SCK(0);}returnByteReceive;}// W25Q64.c#includestm32f10x.h// Device header#includeMySPI.h#includeW25Q64_Instructions.hvoidW25Q64_Init(void){MySPI_Init();}// 获取ID指令voidW25Q64_ReadID(uint8_t*MID,uint16_t*DID){MySPI_Start();// 主机向W25Q64发送读指令 抛玉引砖MySPI_SwapByte(W25Q64_JEDEC_ID);// 主机接收W25Q64的返回 抛砖引玉*MIDMySPI_SwapByte(W25Q64_DUMMY_BYTE);*DIDMySPI_SwapByte(W25Q64_DUMMY_BYTE);// DID低8位*DID8;*DID|MySPI_SwapByte(W25Q64_DUMMY_BYTE);// DID高8位MySPI_Stop();}// 写使能指令voidW25Q64_WriteEnable(void){MySPI_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);MySPI_Stop();}// 等待忙调用后检查当前是否Busy系统不忙时结束voidW25Q64_WaitBusy(void){uint32_tTimeout100000;MySPI_Start();// 发送指令MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);// 接收状态寄存器的值取出最低为检查是否Busy如果忙就等待while((MySPI_SwapByte(W25Q64_DUMMY_BYTE)0x01)1){Timeout--;if(Timeout0){break;}}MySPI_Stop();}// 页编程指令往指定地址写入Count个字节数据Count最大为256voidW25Q64_PageProgram(uint32_tAddress,uint8_t*DataArray,uint16_tCount){// 写入指令前必须先写使能W25Q64_WriteEnable();MySPI_Start();// 发送页编程指令MySPI_SwapByte(W25Q64_PAGE_PROGRAM);// 发送地址MySPI_SwapByte(Address16);MySPI_SwapByte(Address8);MySPI_SwapByte(Address);// 转成uint8_t类型参数高位会被舍弃// 发送写入的数据uint16_ti;for(i0;iCount;i){MySPI_SwapByte(DataArray[i]);}MySPI_Stop();// 事后等待W25Q64_WaitBusy();}// 实现扇区擦除指令voidW25Q64_SectorErase(uint32_tAddress){// 写入指令前必须先写使能W25Q64_WriteEnable();MySPI_Start();// 发送扇区擦除指令MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);// 发送地址MySPI_SwapByte(Address16);MySPI_SwapByte(Address8);MySPI_SwapByte(Address);// 转成uint8_t类型参数高位会被舍弃MySPI_Stop();// 事后等待W25Q64_WaitBusy();}// 读取数据指令Count值无限制voidW25Q64_ReadData(uint32_tAddress,uint8_t*DataArray,uint32_tCount){MySPI_Start();// 发送读取指令MySPI_SwapByte(W25Q64_READ_DATA);// 发送地址MySPI_SwapByte(Address16);MySPI_SwapByte(Address8);MySPI_SwapByte(Address);// 转成uint8_t类型参数高位会被舍弃// 接收读取数据uint32_ti;for(i0;iCount;i){DataArray[i]MySPI_SwapByte(W25Q64_DUMMY_BYTE);}MySPI_Stop();}// main.c#includestm32f10x.h// Device header#includeOLED_Software.h#includeW25Q64.huint8_tMID;uint16_tDID;uint8_tArrayWrite[]{0x01,0x02,0x03,0x04};uint8_tArrayReceive[4];intmain(void){OLED_Init();W25Q64_Init();OLED_ShowString(1,1,MID: DID:);OLED_ShowString(2,1,W:);OLED_ShowString(3,1,R:);W25Q64_ReadID(MID,DID);OLED_ShowHexNum(1,5,MID,2);OLED_ShowHexNum(1,12,DID,4);W25Q64_SectorErase(0x000000);W25Q64_PageProgram(0x000000,ArrayWrite,4);W25Q64_ReadData(0x000000,ArrayReceive,4);OLED_ShowHexNum(2,3,ArrayWrite[0],2);OLED_ShowHexNum(2,6,ArrayWrite[1],2);OLED_ShowHexNum(2,9,ArrayWrite[2],2);OLED_ShowHexNum(2,12,ArrayWrite[3],2);OLED_ShowHexNum(3,3,ArrayReceive[0],2);OLED_ShowHexNum(3,6,ArrayReceive[1],2);OLED_ShowHexNum(3,9,ArrayReceive[2],2);OLED_ShowHexNum(3,12,ArrayReceive[3],2);while(1){}}
[STM32]Day11-软件实现SPI读写W25Q64
I2C通信优点节省硬件资源。I2C通信缺点弱上拉导致上升沿慢限制通信最大频率。I2C通信最大频率一般认为400kHz。SPI通信**SPISerial Peripheral Interface**是由Motorola公司开发的一种通用数据总线。四根通信线SCKSerial Clock、MOSIMaster Output Slave Input、MISOMaster Input Slave Output、SSSlave Select同步、全双工。支持总线挂载多设备一主多从。SPI通信优点传输频率高速度快。设计简单。SPI通信缺点硬件开销大。SPI硬件电路所有SPI设备的SCK、MOSI、MISO分别连在一起。主机另外引出多条SS控制线分别接到各从机的SS引脚。同一时刻主机只能与一个从机进行通信。输出引脚配置为推挽输出输入引脚配置为浮空或上拉输入。推挽输出高低电平都有很强的驱动能力避免了I2C中上升沿缓慢的问题。当从机SS为高电平即从机未被选中时其MISO引脚必须切换为高阻态从而防止一条线上由多个输出导致的电平冲突问题。移位示意图SPI高位先行因此主机和从机的移位寄存器每个时钟都进行左移操作。移位寄存器的时钟源由主机中的波特率发生器提供它提供的时钟控制主机移位寄存器移位同时通过SCK线输入到从机控制从机移位寄存器的频率实现同步通信。时钟上升沿主机和从机移位寄存器分别左移将最高位放置到MOSI和MISO引脚时钟下降沿主机和从机分别从MISO和MOSI引脚读取数据并放置在移位寄存器最低位。如此循环8次可以实现主机从机全双工通信交换一个字节数据。SPI通信的基础是交换一个字节实现交换一个字节后可以方便地实现发送一个字节、接收一个字节。SPI时序基本单元起始条件SS从高电平切换到低电平主机拉低对应从机的SS信号。终止条件SS从低电平切换到高电平主机拉高对应从机的SS信号。交换一个字节模式0CPOLClock Polarity 时钟极性 0空闲状态时SCK为低电平。CPHAClock Phase时钟相位 0SCK第一个边沿移入数据第二个边沿移出数据。SS下降沿时立刻触发移位输出将最高位输出到各自引脚等待SCK第一个上升沿读取。交换一个字节模式1CPOLClock Polarity 时钟极性 0空闲状态时SCK为低电平。CPHAClock Phase时钟相位 1SCK第一个边沿移出数据第二个边沿移入数据。SS高电平时从机的输出口MISO应该配置为高阻态所以此时MISO电平在低电平与高电平之间。CPHA设置为1在时钟上升沿主机从机同时移出数据并将最高位通过MOSI和MISO输出在下降沿采样各自的输入引脚放置到移位寄存器最低位。交换一个字节模式2与模式0相比SCK反向其余相同CPOLClock Polarity 时钟极性 1空闲状态时SCK为高电平。CPHAClock Phase时钟相位 0SCK第一个边沿移入数据第二个边沿移出数据。交换一个字节模式3与模式1相比SCK反向其余相同CPOLClock Polarity 时钟极性 1空闲状态时SCK为高电平。CPHAClock Phase时钟相位 1SCK第一个边沿移出数据第二个边沿移入数据。SPI时序SPI通常采用指令码读写数据。SPI开始后第一个交换发送给从机的数据为指令码从机中有对应的指令集发送不同的指令可以控制从机完成不同的功能。发送指令向SS指定的设备发送指令0x06指定地址写向SS指定的设备发送写指令0x02随后在指定地址Address[23:0]下写入指定数据Data指定地址读向SS指定的设备发送读指令0x03随后在指定地址Address[23:0]下读取指定数据DataW25Q64简介W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器常应用于数据存储、字库存储、固件程序存储等场景。存储介质Nor Flash闪存时钟频率80MHz/160MHzDual SPI/320MHzQuad SPI存储容量24位地址W25Q6464Mbit/8MByte硬件电路WP写保护低电平有效低电平时不能向芯片写入。W25Q64框图8MB空间划分Block每块64KB共128块 - Sector每个扇区4KB每块共16个扇区 - Page256Byte每个扇区共16页状态寄存器Status Register可以判断芯片是否处于忙状态、是否写保护等256字节的页缓冲器负责在SPI通信时缓存数据时序结束后复制到Flash此时芯片处于忙状态Busy发送该信号到状态寄存器因此写入的一个时序连续写入量不能超过256字节。Flash操作注意事项写入操作时写入操作前必须先进行写使能每个数据位只能由1改写为0不能由0改写为1写入数据前必须先擦除擦除后所有数据变为1擦除必须按最小擦除单元进行连续写入多字节时最多写入一页的数据超过页尾位置的数据会回到页首覆盖写入写入操作结束后芯片会进入忙状态不响应新的读写操作读取操作时直接调用读取时序无需使能无需额外操作没有页的限制读取后不会进入忙状态但不能在忙状态读取常用指令软件SPI读写W25Q64整体框架SPI通信层 - W25Q64硬件驱动层 - main.c调用SPI通信层新建MySPI模块实现通信引脚封装初始化SPI通信起始、中止、交换一个字节W25Q64硬件驱动层调用MySPI实现各种指令和功能的完整时序比如写使能、擦除、页编程、读数据等代码// MySPI.c#includestm32f10x.h// Device header// 封装对SS的操作voidMySPI_W_SS(uint8_tBitVal){GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitVal);// SPI通信非常快所以不用加延时}// 封装对SCK的操作voidMySPI_W_SCK(uint8_tBitVal){GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitVal);}// 封装对MOSI的操作voidMySPI_W_MOSI(uint8_tBitVal){GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitVal);}// 封装对MISO的操作uint8_tMySPI_R_MISO(void){returnGPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);}voidMySPI_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 配置GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;// 输出引脚配置推挽输出模式GPIO_InitStructure.GPIO_PinGPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU;// 输入引脚配置上拉输入模式GPIO_InitStructure.GPIO_PinGPIO_Pin_6;GPIO_Init(GPIOA,GPIO_InitStructure);// 设置默认电平MySPI_W_SS(1);// SS默认高电平MySPI_W_SCK(0);// 模式0下SCK默认低电平}// 产生起始条件voidMySPI_Start(void){MySPI_W_SS(0);}// 产生终止条件voidMySPI_Stop(void){MySPI_W_SS(1);}// 交换一个字节uint8_tMySPI_SwapByte(uint8_tByteSend){uint8_ti,ByteReceive0x00;for(i0;i8;i){// 开始条件产生后主机左移并把最高位写到MOSIMySPI_W_MOSI(ByteSend(0x80i));// SCK上升沿到来后主机读取MISOMySPI_W_SCK(1);if(MySPI_R_MISO()1){ByteReceive|(0x80i);}// 产生下降沿MySPI_W_SCK(0);}returnByteReceive;}// W25Q64.c#includestm32f10x.h// Device header#includeMySPI.h#includeW25Q64_Instructions.hvoidW25Q64_Init(void){MySPI_Init();}// 获取ID指令voidW25Q64_ReadID(uint8_t*MID,uint16_t*DID){MySPI_Start();// 主机向W25Q64发送读指令 抛玉引砖MySPI_SwapByte(W25Q64_JEDEC_ID);// 主机接收W25Q64的返回 抛砖引玉*MIDMySPI_SwapByte(W25Q64_DUMMY_BYTE);*DIDMySPI_SwapByte(W25Q64_DUMMY_BYTE);// DID低8位*DID8;*DID|MySPI_SwapByte(W25Q64_DUMMY_BYTE);// DID高8位MySPI_Stop();}// 写使能指令voidW25Q64_WriteEnable(void){MySPI_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);MySPI_Stop();}// 等待忙调用后检查当前是否Busy系统不忙时结束voidW25Q64_WaitBusy(void){uint32_tTimeout100000;MySPI_Start();// 发送指令MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);// 接收状态寄存器的值取出最低为检查是否Busy如果忙就等待while((MySPI_SwapByte(W25Q64_DUMMY_BYTE)0x01)1){Timeout--;if(Timeout0){break;}}MySPI_Stop();}// 页编程指令往指定地址写入Count个字节数据Count最大为256voidW25Q64_PageProgram(uint32_tAddress,uint8_t*DataArray,uint16_tCount){// 写入指令前必须先写使能W25Q64_WriteEnable();MySPI_Start();// 发送页编程指令MySPI_SwapByte(W25Q64_PAGE_PROGRAM);// 发送地址MySPI_SwapByte(Address16);MySPI_SwapByte(Address8);MySPI_SwapByte(Address);// 转成uint8_t类型参数高位会被舍弃// 发送写入的数据uint16_ti;for(i0;iCount;i){MySPI_SwapByte(DataArray[i]);}MySPI_Stop();// 事后等待W25Q64_WaitBusy();}// 实现扇区擦除指令voidW25Q64_SectorErase(uint32_tAddress){// 写入指令前必须先写使能W25Q64_WriteEnable();MySPI_Start();// 发送扇区擦除指令MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);// 发送地址MySPI_SwapByte(Address16);MySPI_SwapByte(Address8);MySPI_SwapByte(Address);// 转成uint8_t类型参数高位会被舍弃MySPI_Stop();// 事后等待W25Q64_WaitBusy();}// 读取数据指令Count值无限制voidW25Q64_ReadData(uint32_tAddress,uint8_t*DataArray,uint32_tCount){MySPI_Start();// 发送读取指令MySPI_SwapByte(W25Q64_READ_DATA);// 发送地址MySPI_SwapByte(Address16);MySPI_SwapByte(Address8);MySPI_SwapByte(Address);// 转成uint8_t类型参数高位会被舍弃// 接收读取数据uint32_ti;for(i0;iCount;i){DataArray[i]MySPI_SwapByte(W25Q64_DUMMY_BYTE);}MySPI_Stop();}// main.c#includestm32f10x.h// Device header#includeOLED_Software.h#includeW25Q64.huint8_tMID;uint16_tDID;uint8_tArrayWrite[]{0x01,0x02,0x03,0x04};uint8_tArrayReceive[4];intmain(void){OLED_Init();W25Q64_Init();OLED_ShowString(1,1,MID: DID:);OLED_ShowString(2,1,W:);OLED_ShowString(3,1,R:);W25Q64_ReadID(MID,DID);OLED_ShowHexNum(1,5,MID,2);OLED_ShowHexNum(1,12,DID,4);W25Q64_SectorErase(0x000000);W25Q64_PageProgram(0x000000,ArrayWrite,4);W25Q64_ReadData(0x000000,ArrayReceive,4);OLED_ShowHexNum(2,3,ArrayWrite[0],2);OLED_ShowHexNum(2,6,ArrayWrite[1],2);OLED_ShowHexNum(2,9,ArrayWrite[2],2);OLED_ShowHexNum(2,12,ArrayWrite[3],2);OLED_ShowHexNum(3,3,ArrayReceive[0],2);OLED_ShowHexNum(3,6,ArrayReceive[1],2);OLED_ShowHexNum(3,9,ArrayReceive[2],2);OLED_ShowHexNum(3,12,ArrayReceive[3],2);while(1){}}