首页 > 其他 > 详细

5、SPI通信协议

时间:2020-09-13 21:36:46      阅读:59      评论:0      收藏:0      [点我收藏+]

1.定义

SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI是一种高速的全双工同步的通信总线,并且在芯片的引脚上只占用了四根线,节省了芯片的引脚,同时为PCB的布局上节省空间提供方便,正式出于这种简单易用的特性,如今越来越多的芯片继承了这种通信协议,例如NRF24L01VS053SD卡等。

注意:

1M4高达37.5Mbps,不过很多外围设备往往只支持高达10Mbps,极少部分的设备能超过10Mbps,例如W250128.

2)高速的:10Mbps

3)全双工,同步:发送数据的时候同时接收到数据(串口是全双工异步通信)

2.单机与多机通信

2.1 单机通信

 技术分享图片

1SCLK

穿行时钟线,用于数据同步主机输出数据,从机输入数据(Master Input Slave Output

2MISO

主机输入数据,从机输出数据(Master Output Slave Input

3 /SS/CS

(芯)片选引脚,引脚低电平,从机工作有效;引脚高电平从机工作无效

2.2 多机通信

通信的时候,虽然有3根引脚是共用的,唯独片选引脚不能共用,因为通信的时候,至于徐主机跟其中一个从机尽行通信。

技术分享图片

3.工作模式

3.1 常用工作模式

SPI总线有4种工作方式,其中使用最为广泛的是模式0模式3

3.2 CPOL(Clock Polarity)

时钟极性选择,为0时SPI总线空闲时钟低电平;为1时SPI总线空闲时的时钟高电平

3.3 CPHA(Clock phase)

时钟相位选择,为0时在SCLK第一个跳变沿主机对MISO引脚电平进行采样;为1时在SCLK第二个跳变沿主机对MISO引脚电平进行采样

3.4 四种工作模式时序图

 1、模式0

 

技术分享图片

 2、模式1

 

技术分享图片

 

 3、模式2

 

技术分享图片

 4、模式3

 

技术分享图片

4.硬件原理

4.1 W25Q128端引脚连接

技术分享图片

4.2 mcu端引脚连接

 技术分享图片

5.代码实现

5.1 硬件管脚及spi相关参数的初始化

5.1.1 硬件实现

 1 void w25qxx_init(void)
 2 {
 3     // 1.PB硬件(GPIO)时钟使能
 4     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
 5     // 2.SPI1硬件时钟使能
 6     RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI1, ENABLE);
 7 
 8     // 配置PB3-PB5为复用功能模式
 9     GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);     // PB3复用为SPI1
10     GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);     // PB4复用为SPI1
11     GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);     // PB5复用为SPI1
12 
13     // 3.PB3-PB5连接到SPI1硬件
14     /*!< SPI SCK MOSI MISO pin configuration */
15     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin_4; 
16     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        // 复用模式
17     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
18     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      // 推挽输出
19     GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;     
20     GPIO_Init(GPIOB, &GPIO_InitStructure);
21 
22     // 4.PB14配置输出模式
23     /*!< Configure sFLASH Card CS pin in output pushpull mode ********************/
24     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
25     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
26     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
27     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
28     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
29     GPIO_Init(GPIOB, &GPIO_InitStructure);
30 
31     // 5.看时序图,PB14会有一个初始电平
32     /*!< Deselect the FLASH: Chip Select high */
33     GPIO_SetBits(GPIOB, GPIO_Pin_14);
34 
35     // 6.配置SPI1的参数
36     /*!< SPI configuration */
37     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 全双工
38     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;       // 主机模式
39     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   // 看数据手册,8为数据位
40     SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;         // 看数据手册,spi flash可以配置为高电平
41     SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;        // 看数据手册,miso引脚的在时钟线第二边沿采样数据
42     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;           // 片选引脚由代码控制
43     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;// 看数据手册,spi1的硬件时钟源为APB2,SPI1_CLK=84MHz/8=10.5MHz
44     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  // 看数据手册,最高有效位优先先传输
45     SPI_InitStructure.SPI_CRCPolynomial = 7;            // crc值计算的多项式
46     SPI_Init(SPI1, &SPI_InitStructure);
47
48     // 7.使能SPI1硬件工作 49     /*!< Enable the sFLASH_SPI  */ 50     SPI_Cmd(SPI1, ENABLE);

 

5.1.2 gpio引脚模拟实现

 1 void w25qxx_init_spi_simu(void)
 2 {
 3     int32_t i;
 4 
 5     // 1.PB硬件(GPIO)时钟使能
 6    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
 7 
 8     // 2.配置PB3,PB5,PB14为输出模式
 9     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin14; 
10     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;       // 输出模式模式
11     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  // 高速相应
12     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      // 推挽输出,增加电流输出能力
13     GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;     // 没有使能上下拉电阻
14     GPIO_Init(GPIOB, &GPIO_InitStructure);
15 
16     // 3.PB4配置输入模式
17     /*!< Configure sFLASH Card CS pin in input pushpull mode ********************/
18     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
19     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;        // 输入模式
20     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
21     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  // 高速响应
22     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;    // 没有使能上下拉电阻
23     GPIO_Init(GPIOB, &GPIO_InitStructure);
24 
25     // 5.看时序图,PB14会有一个初始电平
26     /*!< Deselect the FLASH: Chip Select high */
27     PBout(14) = 1;                  // 片选初始为高
28     PBout(3) = 1;                   // 时钟初始为高
29 }

5.2 发送&接收1byte数据

5.2.1 硬件实现

 1 uint8_t sFLASH_SendByte(uint8_t byte)
 2 {
 3   /*!< Loop while DR register in not emplty */
 4   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
 5 
 6   /*!< Send byte through the SPI1 peripheral */
 7   SPI_I2S_SendData(SPI1, byte);
 8   /*!< Wait to receive a byte */
 9   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
10   /*!< Return the byte read from the SPI bus */
11   return SPI_I2S_ReceiveData(SPI1);
12 }

 

5.2.2 gpio引脚模拟实现

1、时序图

技术分享图片

2、代码实现

 1 uint8_t w25qxx_txrx_spi_simu_for_mode03(uint8_t tx_byte)
 2 {
 3     int32_t i;
 4     uint8_t rx_byte=0x00;
 5 
 6     for (i=7; i<=0; i--)
 7     {
 8         if (tx_byte & (1<<i))       // 准备要发送的数据
 9         {
10             PBout(5) = 1;
11         }
12         else
13         {
14             PBout(5) = 0;   
15         }
16 
17         PBout(3) = 0;               // 模式3,第一个跳变沿发送1bit的数据
18         delay_us(1);                // 延时一会,mosi引脚已经发送的对方
19 
20         PBout(3) = 1;               // 时钟线输出高电平
21         delay_us(1);
22 
23         //读取MISO引脚电平
24         if (PBin(4))
25         {
26             rx_byte |= 1<<i;        // 读取1bit数据
27         }
28     }
29     return rx_byte;
30 }

5.3 写数据到flash

5.3.1 写使能

1、时序图

技术分享图片

2、代码实现

 1 void w25qxx_write_enable(void)
 2 {
 3     // 1.片选拉低
 4     PBout(14) = 0;
 5 
 6     // 2.写入06h指令
 7 sFLASH_SendByte(0x06);
 8 
 9     //3.片选拉高
10     PBout(14) = 0;
11 }

 

5.3.2 等待写入完成

 1 void w25qxx_wait_for_write_end(void)
 2 {
 3     uint8_t flashstatus = 0;
 4 
 5     /*!< Select the FLASH: Chip Select low */
 6     // 1.片选拉低
 7     PBout(14) = 0;
 8     /*!< Send "Read Status Register" instruction */
 9     sFLASH_SendByte(0x05);
10     /*!< Loop as long as the memory is busy with a write cycle */
11     /**
12      * 读取W25Qxx的状态寄存器
13      * BIT7  BIT6  BIT5  BIT4  BIT3  BIT2 BIT1  BIT0
14      * SPR    RV    TB   BP2   BP1   BP0  WEL   BUSY
15      * SPR:默认0,状态寄存器保护位,配合WP使用
16      * TB,BP2,BP1,BP0:FLASH区域写保护设置
17      * WEL:写使能锁定
18      * BUSY:忙标记位(1,忙;0,空闲)
19      **/
20     do
21     {
22       /*!< Send a dummy byte to generate the clock needed by the FLASH
23       and put the value of the status register in FLASH_Status variable */
24       flashstatus = sFLASH_SendByte(0xff);
25     }
26     while ((flashstatus & 0x01) == SET); /* Write in progress */
27 
28     /*!< Deselect the FLASH: Chip Select high */
29     // 片选拉高
30     PBout(14) = 1;
31 }

 

5.3.3 页编程

1、时序图

 

 技术分享图片

2、代码实现

 1 int16_t w25qxx_page_program(uint8_t* pdata;uint32_t WriteAddr,uint16_t len)
 2 {
 3     uint16_t i;
 4 
 5     // 1.写使能
 6     w25qxx_write_enable();
 7 
 8     // 2.片选拉低
 9     PBout(14) = 0;
10 
11     // 3.写入02h指令(页编程指令)
12     sFLASH_SendByte(0x02);
13 
14     // 4.写入要编成的要编程的地址值
15     sFLASH_SendByte((WriteAddr & 0xff0000) >> 16);
16     sFLASH_SendByte((WriteAddr & 0xff00) >> 8);
17     sFLASH_SendByte(WriteAddr & 0xff);
18 /*  
19     if (len>256)
20     {
21         printf("lenth of page program data is too long!\r\n");
22     }
23     else
24     {
25         for (i=0; i<len; i++)
26         {
27             sFLASH_SendByte(pdata[i]);
28         }
29     }
30 */
31     if (len>256)
32     {
33         printf("lenth of page program data is too long!\r\n");
34         return -1;
35     }
36 
37     // 4.写一页入数据
38     while (len--)
39     {
40         sFLASH_SendByte(*pdata);
41         pdata++;
42     }
43 
44     // 5.片选拉高
45     PBout(14) = 1;
46 
47     // 6.等待数据写入完成
48     w25qxx_wait_for_write_end();
49
50    return (int16_t)len; 51 }

 

5.3.4 任意写数据到flash

/**
  * @brief  Writes block of data to the FLASH. In this function, the number of
  *         WRITE cycles are reduced, using Page WRITE sequence.
  * @param  pBuffer: pointer to the buffer  containing the data to be written
  *         to the FLASH.
  * @param  WriteAddr: FLASH‘s internal address to write to.
  * @param  NumByteToWrite: number of bytes to write to the FLASH.
  * @retval None
  */
#define sFLASH_SPI_PAGESIZE 0x100
#define sFLASH_WritePage w25qxx_page_program

void w25qxx_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
  uint8_t NumOfPage 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

  Addr = WriteAddr % sFLASH_SPI_PAGESIZE;                        // 写数据的地址与页对齐地址的偏移量
  count = sFLASH_SPI_PAGESIZE - Addr;                            // 一整页剩余的长度
  NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;             // 数据的页数
  NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;            // 不满1页的数据长度

  if (Addr == 0/*!< WriteAddr is sFLASH_PAGESIZE aligned  */   // 所写的地址刚好页对齐
  {
    if (NumOfPage == 0/*!< NumByteToWrite < sFLASH_PAGESIZE */ // 写入的数据长度小于1页
    {
      sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */                // 写入数据的长度大于1页
    {
      while (NumOfPage--)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else /*!< WriteAddr is not sFLASH_PAGESIZE aligned  */        // 所写的地址不能页对齐
  {
    if (NumOfPage == 0/*!< NumByteToWrite < sFLASH_PAGESIZE */// 写入的数据长度小于1页
    {
      if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */
      {
        temp = NumOfSingle - count;

        sFLASH_WritePage(pBuffer, WriteAddr, count);            // 分两次写,一部分写上一页剩余空间
        WriteAddr +=  count;
        pBuffer += count;

        sFLASH_WritePage(pBuffer, WriteAddr, temp);             // 另一部分写下一页
      }
      else
      {
        sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /*!< NumByteToWrite > sFLASH_PAGESIZE */               // 写入数据的长度大于1页
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
      NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

      sFLASH_WritePage(pBuffer, WriteAddr, count);              // 先写未满1页
      WriteAddr +=  count;
      pBuffer += count;

      while (NumOfPage--)                                       // 再写整页
      {
        sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
        WriteAddr +=  sFLASH_SPI_PAGESIZE;
        pBuffer += sFLASH_SPI_PAGESIZE;
      }

      if (NumOfSingle != 0)
      {
        sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);     // 最后再写不满一整页的数据
      }
    }
  }
}

5.4 读数据

5.4.1 读厂商和设备id

1、时序图

技术分享图片

2、代码实现

 1 void w25qxx_read_id(uint8_t *man_id, uint8_t *dev_id)
 2 {
 3     // 1.片选引脚输出低电平
 4     PBout(14) = 0;
 5 
 6     // 2.写入读厂上id命令(90h)
 7     sFLASH_SendByte(0x90);
 8 
 9     // 3.写入三个字节的地址(000000h)
10     sFLASH_SendByte(0x00);
11     sFLASH_SendByte(0x00);
12     sFLASH_SendByte(0x00);
13 
14     // 4.读厂商id(对于W25Q128FV的厂商id为0xef)
15     man_id* = sFLASH_SendByte(0xff);    
16 
17     // 5.读设备id(对于W25Q128FV的设备id为0x17)
18     dev_id* = sFLASH_SendByte(0xff);        
19 
20     // 5.片选引脚输出高电平
21     PBout(14) = 1;
22 }

5.5 擦除

5.5.1 扇区擦除

1、时序图

 技术分享图片

2、代码实现

 1 /**
 2  *  The Sector Erase instruction sets all memory within a specified sector (4K-bytes) to the erased state of 
 3  *  all 1s (FFh). A Write Enable instruction must be executed before the device will accept the Sector Erase 
 4  *  Instruction (Status Register bit WEL must equal 1). The instruction is initiated by driving the /CS pin low 
 5  *  and shifting the instruction code “20h” followed a 24-bit sector address (A23-A0).
 6  *  The /CS pin must be driven high after the eighth bit of the last byte has been latched. If this is not done 
 7  *  the Sector Erase instruction will not be executed. After /CS is driven high, the self-timed Sector Erase 
 8  *  instruction will commence for a time duration of tSE (See AC Characteristics). While the Sector Erase 
 9  *  cycle is in progress, the Read Status Register instruction may still be accessed for checking the status 
10  *  of the BUSY bit. The BUSY bit is a 1 during the Sector Erase cycle and becomes a 0 when the cycle is 
11  *  finished and the device is ready to accept other instructions again. After the Sector Erase cycle has 
12  *  finished the Write Enable Latch (WEL) bit in the Status Register is cleared to 0. The Sector Erase 
13  *  instruction will not be executed if the addressed page is protected by the Block Protect (CMP, SEC, TB, 
14  *  BP2, BP1, and BP0) bits or the Individual Block/Sector Locks.
15  */
16 void w25qxx_sector_erase(uint16_t EraseAddr)
17 {
18     // 1.写使能
19     w25qxx_write_enable();
20 
21     // 2.片选拉低
22     PBout(14) = 0;
23 
24     // 3.写入20h指令(扇区擦除指令)
25     sFLASH_SendByte(0x20);
26 
27     // 4.写入要擦除4KByte的首地址
28     sFLASH_SendByte((EraseAddr & 0xff0000) >> 16);
29     sFLASH_SendByte((EraseAddr & 0xff00) >> 8);
30     sFLASH_SendByte(EraseAddr & 0xff);
31 
32     // 5.片选拉高
33     PBout(14) = 1;
34 
35     // 6.等待数据写入完成
36     w25qxx_wait_for_write_end();    
37 }

 

5.5.2 整芯片擦除

1、时序图

技术分享图片

2、代码实现

 1 /**
 2   * @brief  Erases the entire FLASH.
 3   * @param  None
 4   * @retval None
 5   */
 6 void sFLASH_EraseBulk(void)
 7 {
 8   /*!< Send write enable instruction */
 9   w25qxx_write_enable();
10 
11   /*!< Bulk Erase */
12   /*!< Select the FLASH: Chip Select low */
13   PBout(14) = 0;
14   /*!< Send Bulk Erase instruction  */
15   sFLASH_SendByte(0xC7);
16   /*!< Deselect the FLASH: Chip Select high */
17   PBout(14) = 1;
18 
19   /*!< Wait the end of Flash writing */
20   w25qxx_wait_for_write_end();
21 }

 

5、SPI通信协议

原文:https://www.cnblogs.com/sbtblogs/p/13662651.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!