3、 多从机只需要增加SS片选信号线
4、速率高,最高频率可达到fplck/2,受限于低速设备(例如STM32F407的APB2总线最高可达42MHz)



通过切换时钟极性(CPOL)和时钟相位(CPHA)可以更改SPI的采样模式
CPOL = 0 :SCK起始信号为低电平
CPOL = 1 :SCK起始信号为高电平
CPHA = 0 :对奇数边缘采样
CPHA = 1 :对偶数边缘采样

一般常用的是模式0和模式3,例如flash的W25Q128只支持模式0和模式3
模式3

对flash发送数据需要等待TXE发送寄存器为reset,接收则需要等待RXNE接收非空寄存器reset。
下面是对flash写入和接收1字节数据的函数代码
/* ------------------SPI对flash写入1字节数据----------------------- */
// data :要写入flash的数据
uint32_t SPI_WriteByte(uint8_t data)
{//等待事件响应TimeOut_count = SPI_time_out;while (SPI_I2S_GetFlagStatus(SPI_FLASH,SPI_I2S_FLAG_TXE) == RESET){if ((TimeOut_count--) == 0) return SPI_timeout_callback(0);}//发送要写入的数据SPI_I2S_SendData(SPI_FLASH, data);//等待事件响应TimeOut_count = SPI_time_out;while (SPI_I2S_GetFlagStatus(SPI_FLASH,SPI_I2S_FLAG_RXNE) == RESET){if ((TimeOut_count--) == 0) return SPI_timeout_callback(1);}//接受返回的数据return SPI_I2S_ReceiveData(SPI_FLASH);
}
发送和接收都是这个函数,因为SPI是全双工的,在发送1个字节的同时就会返回1个字节的数据
对SPI在总线上查找



查找spi1对应引脚,对应开发板硬件原理图,我的开发板是STMF407

根据开发板原理图
cs片选引脚:PG6
SCK:PB3
MISO:PB4
MOSI:PB5
根据引脚可以编写对应的SPI头文件宏
/*SPI引脚参数定义*/
#define SPI_FLASH SPI1
#define SPI_FLASH_CLK RCC_APB2Periph_SPI1
#define SPI_FLASH_INIT RCC_APB2PeriphClockCmd
/*SCK引脚*/
#define SPI_FLASH_SCK_PIN GPIO_Pin_3
#define SPI_FLASH_SCK_GPIO_PORT GPIOB
#define SPI_FLASH_SCK_GPIO_CLK RCC_AHB1Periph_GPIOB
#define SPI_FLASH_SCK_SOURCE GPIO_PinSource3
#define SPI_FLASH_SCK_AF GPIO_AF_SPI1
/*MISO引脚*/
#define SPI_FLASH_MISO_PIN GPIO_Pin_4
#define SPI_FLASH_MISO_GPIO_PORT GPIOB
#define SPI_FLASH_MISO_GPIO_CLK RCC_AHB1Periph_GPIOB
#define SPI_FLASH_MISO_SOURCE GPIO_PinSource4
#define SPI_FLASH_MISO_AF GPIO_AF_SPI1
/*MOSI引脚*/
#define SPI_FLASH_MOSI_PIN GPIO_Pin_5
#define SPI_FLASH_MOSI_GPIO_PORT GPIOB
#define SPI_FLASH_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOB
#define SPI_FLASH_MOSI_SOURCE GPIO_PinSource5
#define SPI_FLASH_MOSI_AF GPIO_AF_SPI1
/*CS引脚*/
#define SPI_FLASH_CS_PIN GPIO_Pin_6
#define SPI_FLASH_CS_GPIO_PORT GPIOG
#define SPI_FLASH_CS_GPIO_CLK RCC_AHB1Periph_GPIOG
/*拉高拉低CS引脚*/
#define SPI_FLASH_CS_LOW() GPIO_ResetBits(SPI_FLASH_CS_GPIO_PORT,SPI_FLASH_CS_PIN)
#define SPI_FLASH_CS_HIGH() GPIO_SetBits(SPI_FLASH_CS_GPIO_PORT,SPI_FLASH_CS_PIN)
对应flash W25Q128数据手册,编写对应的功能函数

根据对应的功能写出对应的宏增加代码可读性
上图的DUMMY是无效数据就用0xFF
#define DUMMY 0xFF/*命令定义-开头*******************************/
#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
下面是函数介绍
/*写使能函数
*/
void SPI_FLASH_WriteEnable(void)
{SPI_FLASH_CS_LOW();SPI_WriteByte(W25X_WriteEnable);SPI_FLASH_CS_HIGH();
}
/*等待BUSY位为0,即等待Flash内部数据写入完毕
*/
void SPI_FLASH_WaitForWriteEnd(void)
{uint8_t flash_status = 0;SPI_FLASH_CS_LOW();SPI_WriteByte(W25X_ReadStatusReg);TimeOut_count = SPI_time_out;do{flash_status = SPI_WriteByte(DUMMY);if((TimeOut_count--) == 0){SPI_timeout_callback(2);break;}} while((flash_status & 0x01) == SET);SPI_FLASH_CS_HIGH();
}
/*扇区擦除函数addr:要擦除的扇区
*/
void Sector_Erase(uint32_t addr)
{SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();SPI_FLASH_CS_LOW();SPI_WriteByte(W25X_SectorErase);SPI_WriteByte((addr>>16) & 0xFF);SPI_WriteByte((addr>>8) & 0xFF);SPI_WriteByte(addr & 0xFF);SPI_FLASH_CS_HIGH();SPI_FLASH_WaitForWriteEnd();
}
/*写一页flash数据addr:要写入的地址起始buff:写入的的暂存缓冲区size:写的字节数 page一定要在256以内
*/
void Page_write(uint32_t addr,uint8_t *buff,uint32_t size)
{SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();SPI_FLASH_CS_LOW();SPI_WriteByte(W25X_PageProgram);SPI_WriteByte((addr>>16) & 0xFF);SPI_WriteByte((addr>>8) & 0xFF);SPI_WriteByte(addr & 0xFF);while(size--){SPI_WriteByte(*buff);buff++;}SPI_FLASH_CS_HIGH();SPI_FLASH_WaitForWriteEnd();
}
/*发送0xAB读取flashID
*/
uint8_t Read_flash_ID(void)
{uint8_t id;//拉低CS片选引脚SPI_FLASH_CS_LOW();//写指令SPI_WriteByte(W25X_ReleasePowerDown);SPI_WriteByte(DUMMY);SPI_WriteByte(DUMMY);SPI_WriteByte(DUMMY);//读指令id = SPI_WriteByte(DUMMY);//拉高CS片选引脚 传输结束SPI_FLASH_CS_HIGH();return id;
}
基于Page_write函数做了逻辑处理
/*写flash数据addr:要写入的地址起始buff:写入的的暂存缓冲区size:写的字节数
*/
void Buffer_write(uint32_t addr,uint8_t *buff,uint32_t size)
{u8 num_signgle , num_page , count ,temp;num_signgle = addr % 256; //求出首地址是否对齐count = 256 - num_signgle; //首页剩余要写的字节num_page = size / 256; //若对齐的页数temp = size % 256; //如果对齐的话最后一页剩余要补的字节if(num_signgle != 0) //首页没对齐的情况{num_page = (size - count)/256; //重新算出没对齐后的页数if(num_page == 0) {if(size > count) //虽然是0页但也可能存在尾部跨页数的存在{Page_write(addr , buff , count);addr += count;buff += count;Page_write(addr , buff , (size-count));}else //没跨页数{Page_write(addr , buff , size);}}else //没对齐且不止一页{Page_write(addr , buff , count); //补齐首页addr += count;buff += count;while(num_page--) //写中间完整页{Page_write(addr , buff , 256);addr += 256;buff += 256;}temp = (size-count)%256; //若有剩余补尾页页if(temp != 0){Page_write(addr , buff , temp);}}}else{if(num_page == 0) //对齐0页直接写{Page_write(addr , buff , size);}else{while(num_page--) //对齐直接完整页{Page_write(addr , buff , 256);addr += 256;buff += 256;}if(temp != 0) //若有剩余补尾页页{Page_write(addr , buff , temp);}}}
}