spi是摩托罗拉公司设计的用于板间的串行通信方式,应用场景类似于IIC,速度快于IIC。
spi属于高速,全双工,同步的通信总线;和设备连接占用4根线,比较占用引脚,速度最快达到40Mbps。

主设备控制,用于选择当前通信的从设备 , 一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access),所以, Master 设备必须首先通过 CS对 Slave 设备进行片选, 把想要访问的 Slave 设备选上.
产生时钟信号,由主设备控制
主设备输入,从设备输出
主设备输出,从设备输入(MISO和MOSI实现全双工)
spi每一位数据的发送和接收是在时钟信号的边沿完成,根据选择的上升沿/下降沿和时钟信号的高低电平顺序,一共有4种情况。
CPOL表示高低电平顺序,叫做极性;若CPOL = 0,串行同步时钟的空闲状态为低电平;
若CPOL = 1,串行同步时钟的空闲状态为高电平;
CPHA表示第一个/第二个边沿,叫做相位。
SPI输出串行同步时钟极性和相位可以根据外设工作要求进行配置。 SPI 设备间的数据传输又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)". 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.
分别是以下四种情况:
MODE0:CPOL= 0,CPHA=0。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的上升沿采样,下降沿输出;
MODE1:CPOL= 0,CPHA=1。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的下降沿采样,上升沿输出;
MODE2:CPOL= 1,CPHA=0。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的下降沿采样,上升沿输出;
MODE3:CPOL= 1,CPHA=1。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的上升沿采样,下降沿输出;


SPI使用MOSI以及MISO信号来传输数据,使用SCK信号线进行数据同步。
MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出时同时进行的。
SPI每次传输数据可以8位或16位为单位,每次传输的单位数不受限制
原理图:

spi接口的CS连接到了PB14,SCLK,MISO,MOSI分别连接到了PB3 PB4 PB5,这三个IO口具有SPI的复用功能。
(1)使用GPIO接口模拟SPI的时序,只要是IO口即可以使用。
(2)使用SPI控制器,只需要配置好SPI的通信参数后,直接进行传输,接口必须有SPI复用功能。
添加spi的库函数源码:

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
GPIO_Init(...); GPIO_AFPinConfig(...);
(3)初始化SPI
- void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
- 参数:
- SPIx - 哪个SPI
- SPI_InitStruct - SPI初始化结构
- typedef struct
- {
- uint16_t SPI_Direction; /*!< 传输方向 单向/双向 @ref SPI_data_direction */
- uint16_t SPI_Mode; /*!< 主/从设备选择 @ref SPI_mode */
- uint16_t SPI_DataSize; /*!< 数据位长度 @ref SPI_data_size */
- uint16_t SPI_CPOL; /*!< 极性 @ref SPI_Clock_Polarity */
- uint16_t SPI_CPHA; /*!< 相位 @ref SPI_Clock_Phase */
- uint16_t SPI_NSS; /*!< 片选信号选择 软件/硬件 @ref SPI_Slave_Select_management */
- uint16_t SPI_BaudRatePrescaler; /*!< 参考时钟的预分频系数 10M左右*/
- uint16_t SPI_FirstBit; /*!< 传输位顺序 高位/低位 @ref SPI_MSB_LSB_transmission */
- uint16_t SPI_CRCPolynomial; /*!< CRC校验,无需配置 */
- }SPI_InitTypeDef;
(4)使能SPI
SPI_Cmd(...);
(5)使用SPI传输数据
//发送和接收同时进行 void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
(6)查询SPI传输状态
- FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
- 参数:
- * @arg SPI_I2S_FLAG_TXE: Transmit buffer empty flag.
- * @arg SPI_I2S_FLAG_RXNE: Receive buffer not empty flag.
- * @arg SPI_I2S_FLAG_BSY: Busy flag.
- * @arg SPI_I2S_FLAG_OVR: Overrun flag.
- * @arg SPI_FLAG_MODF: Mode Fault flag.
- * @arg SPI_FLAG_CRCERR: CRC Error flag.
- * @arg SPI_I2S_FLAG_TIFRFE: Format Error.
- * @arg I2S_FLAG_UDR: Underrun Error flag.
- * @arg I2S_FLAG_CHSIDE: Channel Side flag.
CS WP HOLD低电平有效,WP和HOLD接VCC,功能关闭,片选是低电平选中。
SPI接口支持CPOL和CPHA为0,0和1,1的模式


(2)操作时序
在进行任何操作前开启片选,操作完成后关闭片选。
1)读设备ID
发送90H ===> 发送24位0地址 ===> 收到厂家ID(0xef)和设备ID(0x17)
*************************************************************************************************************
//代码实现W25Q128芯片的SPI通信
//功能函数
- #include
- #include
- #include
- #include
-
- void spi1_init(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- SPI_InitTypeDef SPI_InitStruct;
-
- //1.开始时钟
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
-
- //2.配置GPIO为SPI功能
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//输出模式
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;//PB14
- GPIO_Init(GPIOB,&GPIO_InitStruct);
-
- //片选关闭
- W25Q128_CS = 1;
-
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用模式
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3 PB4 PB5
- GPIO_Init(GPIOB,&GPIO_InitStruct);
-
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);
-
-
- //3.SPI初始化
- SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双向全双工
- SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主设备
- SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据长度8位
- SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;//极性 mode 0
- SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;//相位 mode 0
- SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//软件片选
- SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//APB2总线时钟 84M/16分频 = 5.25M
- SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位先出
- SPI_Init(SPI1,&SPI_InitStruct);
-
- //4.使能SPI1
- SPI_Cmd(SPI1,ENABLE);
- }
-
- //发送接收数据 ----- 同时进行
- //data是要发送的数据,返回接收的数据
- u8 spi1_send_recv_byte(u8 data)
- {
- //等待发送缓冲区为空
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)!=SET);
- //发送数据
- SPI_I2S_SendData(SPI1,data);
- //等待接收缓冲区非空
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)!=SET);
- //接收数据
- return SPI_I2S_ReceiveData(SPI1);
- }
-
- //读取W25Q128的ID
- u16 w25q128_read_id(void)
- {
- u16 id = 0;
-
- //片选选中
- W25Q128_CS = 0;
-
- //发送90H
- spi1_send_recv_byte(0x90);
- //发送24位0地址
- spi1_send_recv_byte(0x00);
- spi1_send_recv_byte(0x00);
- spi1_send_recv_byte(0x00);
-
- //接收厂家ID放在高8位
- id |= spi1_send_recv_byte(0xff)<<8;
- //接收设备ID放在低8位
- id |= spi1_send_recv_byte(0xff);
-
- //片选关闭
- W25Q128_CS = 1;
-
- return id;
- }
-
- //读取W25Q128的数据
- void w25q128_read_data(u32 addr,u8 *data,u8 len)
- {
- //片选选中
- W25Q128_CS = 0;
-
- //发送03H
- spi1_send_recv_byte(0x03);
- //发送24位读的地址,高位先出
- spi1_send_recv_byte((addr>>16)&0xff);//16~23位
- spi1_send_recv_byte((addr>>8)&0xff);//8~15位
- spi1_send_recv_byte((addr>>0)&0xff);//0~7位
-
- //读取数据
- while(len--){
- *data++ = spi1_send_recv_byte(0xff);
- }
-
- //片选关闭
- W25Q128_CS = 1;
- }
-
- //开启/关闭写使能 enable = 0 开启写使能 enable = 1 关闭写使能
- void w25q128_write_enable_disable(u8 enable)
- {
- //片选选中
- W25Q128_CS = 0;
-
- if(enable){
- spi1_send_recv_byte(0x04);
- }
- else{
- spi1_send_recv_byte(0x06);
- }
-
- //片选关闭
- W25Q128_CS = 1;
- }
-
- //读状态寄存器1
- u8 w25q128_read_status(void)
- {
- u8 status = 0;
-
- //片选选中
- W25Q128_CS = 0;
-
- //发送05H
- spi1_send_recv_byte(0x05);
-
- status = spi1_send_recv_byte(0xff);
-
- //片选关闭
- W25Q128_CS = 1;
-
- return status;
- }
-
- //扇区擦除
- void w25q128_sector_erase(u32 addr)
- {
- //开启写使能
- w25q128_write_enable_disable(0);
-
- //延时,让片选信号保持一段时间,使W25Q128感受到
- delay_us(50);
-
- //片选选中
- W25Q128_CS = 0;
-
- //发送20H
- spi1_send_recv_byte(0x20);
-
- //发送24位擦除地址,高位先出
- spi1_send_recv_byte((addr>>16)&0xff);//16~23位
- spi1_send_recv_byte((addr>>8)&0xff);//8~15位
- spi1_send_recv_byte((addr>>0)&0xff);//0~7位
-
- //片选关闭
- W25Q128_CS = 1;
-
- delay_us(50);
-
- //等待擦除完成
- while(w25q128_read_status()&0x1){
- //1ms查询一次
- delay_ms(1);
- }
-
- delay_us(50);
-
- //关闭写使能
- w25q128_write_enable_disable(1);
- }
-
- //写数据(按页)
- void w25q128_write_page(u32 addr,u8 *data,u8 len)
- {
- //开启写使能
- w25q128_write_enable_disable(0);
-
- //延时,让片选信号保持一段时间,使W25Q128感受到
- delay_us(50);
-
- //片选选中
- W25Q128_CS = 0;
-
-
- //发送02H
- spi1_send_recv_byte(0x02);
-
- //发送24位写地址,高位先出
- spi1_send_recv_byte((addr>>16)&0xff);//16~23位
- spi1_send_recv_byte((addr>>8)&0xff);//8~15位
- spi1_send_recv_byte((addr>>0)&0xff);//0~7位
-
- //发送写的数据
- while(len--){
- spi1_send_recv_byte(*data++);
- }
-
- //片选关闭
- W25Q128_CS = 1;
-
- delay_us(50);
-
- //等待写完成
- while(w25q128_read_status()&0x1){
- //1ms查询一次
- delay_ms(1);
- }
-
- delay_us(50);
-
- //关闭写使能
- w25q128_write_enable_disable(1);
- }
-
- void flash_write_temp(u8 temp)
- {
- u8 buf[16] = {0},i,data = 0;
- u32 addr = 0;
-
- //找到未被写过的块
- while(1){
- w25q128_read_data(addr,buf,16);
- if(buf[0]!=0x55)
- break;
-
- addr += 16;
- }
-
- data = 0x55;
- //写入0x55
- w25q128_write_page(addr,&data,1);
- //写入temp
- w25q128_write_page(addr+1,&temp,1);
-
- data = 0;
- //求校验和
- w25q128_read_data(addr,buf,16);
- for(i=0;i<15;i++){
- data = (data + buf[i])&0xff;
- }
-
- //写入校验和
- w25q128_write_page(addr+15,&data,1);
- }
-
- void flash_read_temp(void)
- {
- u8 buf[16] = {0},i,data;
- u32 addr = 0;
-
- //找出所有写入温度的块
- while(1){
- data = 0;
- w25q128_read_data(addr,buf,16);
- if(buf[0]!=0x55)
- break;
-
- //求校验和
- for(i=0;i<15;i++){
- data = (data + buf[i])&0xff;
- }
-
- if(data!=buf[15]){
- printf("checksum error!data = %#x,buf[15] = %#x\r\n",data,buf[15]);
- break;
- }
-
- printf("0x%x temp = %d\r\n",addr,buf[1]);
-
- addr += 16;
- }
- }
//头文件声明
- #ifndef _SPI_FLASH_H_
- #define _SPI_FLASH_H_
-
- #include
-
- #define W25Q128_CS PBout(14)
-
- void spi1_init(void);
- u16 w25q128_read_id(void);
- void w25q128_read_data(u32 addr,u8 *data,u8 len);
- void w25q128_sector_erase(u32 addr);
- void w25q128_write_page(u32 addr,u8 *data,u8 len);
-
- void flash_write_temp(u8 temp);
- void flash_read_temp(void);
-
- #endif