SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的引脚上只占用了四根线,节省了芯片的引脚,同时为PCB的布局上节省空间提供方便,正式出于这种简单易用的特性,如今越来越多的芯片继承了这种通信协议,例如NRF24L01、VS053、SD卡等。
注意:
(1)M4高达37.5Mbps,不过很多外围设备往往只支持高达10Mbps,极少部分的设备能超过10Mbps,例如W250128.
(2)高速的:10Mbps
(3)全双工,同步:发送数据的时候同时接收到数据(串口是全双工异步通信)
1、SCLK
穿行时钟线,用于数据同步主机输出数据,从机输入数据(Master Input Slave Output)
2、MISO
主机输入数据,从机输出数据(Master Output Slave Input)
3、 /SS(/CS)
(芯)片选引脚,引脚低电平,从机工作有效;引脚高电平从机工作无效
通信的时候,虽然有3根引脚是共用的,唯独片选引脚不能共用,因为通信的时候,至于徐主机跟其中一个从机尽行通信。
SPI总线有4种工作方式,其中使用最为广泛的是模式0和模式3
时钟极性选择,为0时SPI总线空闲时的时钟为低电平;为1时SPI总线空闲时的时钟为高电平
时钟相位选择,为0时在SCLK第一个跳变沿主机对MISO引脚电平进行采样;为1时在SCLK第二个跳变沿主机对MISO引脚电平进行采样;
1、模式0
2、模式1
3、模式2
4、模式3
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.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.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.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.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 }
原文:https://www.cnblogs.com/sbtblogs/p/13662651.html