stm32-SPI SPI : serial peripheral interface主机发送了多少数据从机就会返回多少数据极性 时钟极性 cpol clock polarity 他决定了spi空闲时时钟线SCK默认是低电平还是高电平有些芯片喜欢时钟默认低有些芯片喜欢时钟默认高spi为了兼容各种设别于是允许配置时间空闲时高还是低CPHA clock phase 时钟相位 spi在第几个时钟边沿“采样数据”数据变化和数据读取不能同时发生否则数据还没稳定就被读取可能会错所以需要一个边沿改变数据另外一个边沿读取数据第一边沿第二边沿SPI的四种模式传输顺序MSB : most significant bitLSB : least significant bit一个字节里面 bit怎么排数据宽度: spi总线每次传输bit位的个数 8bit和16bitdo 是 miso master input slave output 接PA6单片机作为主机txe transmit buffer empty : 发送缓冲区空表示可以写新的数据了rxne receive buffer not empty 接收缓冲区非空表示收到数据了ovr overrun 溢出2线全双工 两根数据线同时发送和接受2线只读 虽然有两根线但是只接收常用于stm32作为spi从机只负责上传数据或者读取flash1线发送 只使用一根数据线只发送 比如驱动oled只需要显示1线接收 1根数据线只接收主机1线接收从机1线发送一个接受一个发送根据从机设置SPI的属性SPI内部是 移位寄存器 shift registerspi会放入移位寄存器每来一个时钟整个寄存器移一位一个时钟周期spi传一个bit作为从机的时候nss就是一个片选信号多主机模式 spi总线有多个主机主机1通过普通IO向 主机2 的NSS输入低电压 主机2就变成了从机多主机模式nss被拉低时主机身份变成从机nss必须是高电压否则主机身份就会丢失主机每发送一个比特位必然会从从机收到一个比特位#includestm32f10x.h#includedelay.h#includeled.h#includekey.h#includebuzzer.h#includelightSensor.h#includestdio.h#includeoled.h#includecounterSensor.h#includeencoder.h#includetimer.h#includepwm.h#includeinputCapture.h#includead.h// direct memory access#includemydma.huint16_tad_value[4];voidapp_onBoardLed_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitTypeDef GPIO_initStruct{0};// 操控引脚GPIO_initStruct.GPIO_PinGPIO_Pin_13;GPIO_initStruct.GPIO_ModeGPIO_Mode_Out_OD;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOC,GPIO_initStruct);}// general purpose input outputvoidapp_GPIOA_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_initStruct{0};// 操控引脚GPIO_initStruct.GPIO_PinGPIO_Pin_5|GPIO_Pin_7;GPIO_initStruct.GPIO_ModeGPIO_Mode_AF_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOA,GPIO_initStruct);GPIO_initStruct.GPIO_PinGPIO_Pin_6;GPIO_initStruct.GPIO_ModeGPIO_Mode_IPU;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOA,GPIO_initStruct);GPIO_initStruct.GPIO_PinGPIO_Pin_4;GPIO_initStruct.GPIO_ModeGPIO_Mode_Out_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOA,GPIO_initStruct);}voidapp_spi1_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);SPI_InitTypeDef spi_initStruct;// 是master 还是 从机spi_initStruct.SPI_ModeSPI_Mode_Master;spi_initStruct.SPI_DirectionSPI_Direction_2Lines_FullDuplex;spi_initStruct.SPI_DataSizeSPI_DataSize_8b;spi_initStruct.SPI_CPOLSPI_CPOL_High;spi_initStruct.SPI_CPHASPI_CPHA_2Edge;spi_initStruct.SPI_FirstBitSPI_FirstBit_MSB;spi_initStruct.SPI_BaudRatePrescalerSPI_BaudRatePrescaler_64;// 软件控制nssnss必须为1才是主机否则是从机spi_initStruct.SPI_NSSSPI_NSS_Soft;SPI_Init(SPI1,spi_initStruct);// nss设置为1SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);}voidapp_spi_masterTransmitReceive(SPI_TypeDef*spix,constuint8_t*pDataTx,uint8_t*pDataRx,uint16_tsize){// 闭合总开关SPI_Cmd(spix,ENABLE);SPI_I2S_SendData(spix,pDataTx[0]);for(uint16_ti0;isize-1;i){while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_TXE)RESET);SPI_I2S_SendData(spix,pDataTx[i1]);// 读取数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)RESET);pDataRx[i]SPI_I2S_ReceiveData(spix);}// 读取最后一个字节的数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)RESET);pDataRx[size-1]SPI_I2S_ReceiveData(spix);// 断开总开关SPI_Cmd(spix,DISABLE);}// 常规序列 单通道intmain(void){OLED_Init();while(1){}}64Mbit ⇒ 8M byte ,一个扇区一个扇区划分多个 页 一个页256byteflash不能直接改 0 —》 1. 而擦除操作通常是大范围的所以必须划分页扇区块w25Q64: 属于 SPI NOR flash 外部nor flash页 page : 最小编程单位写入单位不可能一次性写入全部数据写一段就会停一下写一段会停一下这里的一段就是一个页page写的最小单位扇区 4kb 最常用的擦除单位 擦除速度还行浪费空间小flash来说安全性最重要flash中有一把锁将锁打开扇区擦除 和 页编程 都需要时间主机发 0x05, 回收到 状态寄存器1 8个bit位随后一位就是busy是否不空闲spi协议只规定时钟怎么走bit怎么转什么时候采样sckmosimisocpolcphamsb一拍传1bitspi只是规定 双方同时交换bit不是双方数据必须相同spi协议 底层w25Q64通信协议 是上层w25Q64规定收到什么之后后面怎么解释是返回寄存器值还是清除扇区#includestm32f10x.h#includedelay.h#includeled.h#includekey.h#includebuzzer.h#includelightSensor.h#includestdio.h#includeoled.h#includecounterSensor.h#includeencoder.h#includetimer.h#includepwm.h#includeinputCapture.h#includead.h// direct memory access#includemydma.huint16_tad_value[4];voidapp_onBoardLed_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitTypeDef GPIO_initStruct{0};// 操控引脚GPIO_initStruct.GPIO_PinGPIO_Pin_13;GPIO_initStruct.GPIO_ModeGPIO_Mode_Out_OD;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOC,GPIO_initStruct);}// general purpose input outputvoidapp_GPIOA_init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_initStruct{0};// 操控引脚GPIO_initStruct.GPIO_PinGPIO_Pin_5|GPIO_Pin_7;GPIO_initStruct.GPIO_ModeGPIO_Mode_AF_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOA,GPIO_initStruct);GPIO_initStruct.GPIO_PinGPIO_Pin_6;GPIO_initStruct.GPIO_ModeGPIO_Mode_IPU;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOA,GPIO_initStruct);// 对片选引脚写一个高电压 后面变成低电压了才是读取从机GPIO_initStruct.GPIO_PinGPIO_Pin_4;GPIO_initStruct.GPIO_ModeGPIO_Mode_Out_PP;// 输入模式不用写最大输出速度GPIO_initStruct.GPIO_SpeedGPIO_Speed_50MHz;// 初始化IO引脚GPIO_Init(GPIOA,GPIO_initStruct);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);}voidapp_spi1_init(void){app_GPIOA_init();RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);SPI_InitTypeDef spi_initStruct;// 是master 还是 从机spi_initStruct.SPI_ModeSPI_Mode_Master;spi_initStruct.SPI_DirectionSPI_Direction_2Lines_FullDuplex;spi_initStruct.SPI_DataSizeSPI_DataSize_8b;spi_initStruct.SPI_CPOLSPI_CPOL_Low;spi_initStruct.SPI_CPHASPI_CPHA_1Edge;spi_initStruct.SPI_FirstBitSPI_FirstBit_MSB;spi_initStruct.SPI_BaudRatePrescalerSPI_BaudRatePrescaler_64;// 软件控制nssnss必须为1才是主机否则是从机spi_initStruct.SPI_NSSSPI_NSS_Soft;SPI_Init(SPI1,spi_initStruct);// nss设置为1SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);SPI_Cmd(SPI1,ENABLE);}voidapp_spi_masterTransmitReceive(SPI_TypeDef*spix,constuint8_t*pDataTx,uint8_t*pDataRx,uint16_tsize){// // 闭合总开关// SPI_Cmd(spix,ENABLE);SPI_I2S_SendData(spix,pDataTx[0]);for(uint16_ti0;isize-1;i){while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_TXE)RESET);SPI_I2S_SendData(spix,pDataTx[i1]);// 读取数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)RESET);pDataRx[i]SPI_I2S_ReceiveData(spix);}// 读取最后一个字节的数据while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE)RESET);pDataRx[size-1]SPI_I2S_ReceiveData(spix);// // 断开总开关// SPI_Cmd(spix,DISABLE);}voidapp_w25Q64_saveByte(uint8_tbyte){uint8_tbuffer[10];buffer[0]0x06;// 写使能 spi发送 0x06 》 write enable // 一旦真正执行写或者擦除后会自动关闭写使能,,,后面还需要重新打开GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 扇区擦除 0x20 24位地址buffer[0]0x20;buffer[1]0x00;buffer[2]0x00;buffer[3]0x00;GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);app_spi_masterTransmitReceive(SPI1,buffer,buffer,4);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 等待空闲while(1){buffer[0]0x05;// 激活从机GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);// 发送0x05获取寄存器数据 不同命令后面跟的数据格式不同是协议app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);// 获取寄存器的值buffer[0]0xff;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 不忙 》 清除扇区结束了if((buffer[0]0x01)0){break;}}// 写使能 》 防止误写每次写操作前必须明确授权一次GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);buffer[0]0x06;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 页编程 0x02 写数据 24位地址 》 数据要写到哪个位置GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);buffer[0]0x02;buffer[1]0x00;buffer[2]0x00;buffer[3]0x00;buffer[4]byte;app_spi_masterTransmitReceive(SPI1,buffer,buffer,5);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 等待空闲while(1){buffer[0]0x05;// 激活从机GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);// 发送0x05获取寄存器数据 不同命令后面跟的数据格式不同是协议app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);// 获取寄存器的值buffer[0]0xff;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);// 不忙 》 清除扇区结束了if((buffer[0]0x01)0){break;}}}uint8_tapp_w25Q64_loadByte(void){uint8_tbuffer[10];buffer[0]0x03;buffer[1]0x00;buffer[2]0x00;buffer[3]0x00;// 0x03读取数据GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);app_spi_masterTransmitReceive(SPI1,buffer,buffer,4);// 接受一个字节buffer[0]0xff;app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);returnbuffer[0];}voidtest_w25q64_id(void){uint8_ttx[4];uint8_trx[4];tx[0]0x9F;tx[1]0xFF;tx[2]0xFF;tx[3]0xFF;GPIO_ResetBits(GPIOA,GPIO_Pin_4);app_spi_masterTransmitReceive(SPI1,tx,rx,4);GPIO_SetBits(GPIOA,GPIO_Pin_4);OLED_ShowHexNum(1,1,rx[1],2);OLED_ShowHexNum(2,1,rx[2],2);OLED_ShowHexNum(3,1,rx[3],2);}uint8_ta0;// 常规序列 单通道intmain(void){OLED_Init();OLED_ShowString(1,1,hehe:);app_spi1_init();// test_w25q64_id();app_w25Q64_saveByte(0x12);aapp_w25Q64_loadByte();OLED_ShowHexNum(2,1,(uint32_t)a,4);while(1){}}