赞
踩
目录
本章所运用的知识点都是博主从各个网站搜集来的(侵删@小麦大叔@野火),也附带一点自己的看法。本章所用到的开发板是野火的霸道F103系列开发板,需要完整可运行代码的同学也可以找@我拿。
总所周知,学习单片机离不开协议,上章我们讲述了I2C的作用、时序、以及基本代码。相信大家或多或少也了解完了,那么现在跟着我一起来学习同样重要且应用广泛的协议——SPI。
SPI,是英语 Serial Peripheral Interface 的缩写,顾名思义就是串行外围设备接口。SPI,是一种高速的,全双工,同步的通信总线,
如上图所示:
SPI协议传输是主机MCU与从机(1、2、3......)进行数据传输,一个主机可以与多个从机通信(但不能同时通信,会出现数据报错),通信要求则是SS线的电平拉低。下面我们来介绍一下主机上的四根线
SS: 从设备选择信号线,常称为 片选信号线 ,也称为NSS 、 CS 。SCK (Serial Clock): 时钟信号线 ,用于通讯数据同步。MOSI (Master Output, Slave Input) : 主设备输出/从设备输入引脚 。MISO(Master Input,,Slave Output) : 主设备输入/从设备输出引脚 。SS:每个从设备都有独立的这一条SS信号线, 本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。 I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议 中没有设备地址,它使用SS 信号线来寻址,当主机要选择从设备时,把该从设备的 SS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从 设备进行SPI 通讯。所以 SPI 通讯以 SS 线置低电平为开始信号,以 SS 线被拉高作为 结束信号。SCK (Serial Clock):它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如STM32的SPI时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。MOSI (Master Output, Slave Input) :主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。MISO(Master Input,,Slave Output) :主机从这条信号线读入数据,从机的数 据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
2. 通讯的起始和停止信号
标号① 处, NSS 信号线由高变低,是 SPI 通讯的起始信号。 NSS 是每个从机各 自独占的信号线,当从机检在自己的NSS 线检测到起始信号后,就知道自己 被主机选中了,开始准备与主机通讯。 在图中的标号 ⑥ 处, NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通 讯结束,从机的选中状态被取消 。
SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。 MOSI及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出 是同时进行的。
4.CPOL/CPHA及通讯模式
时钟极性CPOL: 是指 SPI 通讯设备处于空闲状态时, SCK 信号线的电平信号 (即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态 ) 。 CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。 时钟相位CPHA: 是指数据的采样的时刻,当 CPHA=0 时, MOSI 或 MISO 数 据线上的信号将会在SCK 时钟线的“奇数边沿”被采样。当 CPHA=1 时, 数据线在SCK 的“偶数边沿”采样。
5.CPOL/CPHA及通讯模式
CK 信号线在空闲状态为低电平时, CPOL=0 ;空闲状态为高电平时, CPOL=1 。 CPHA=0 , MOSI 和 MISO 数据线的有效信号在 SCK 的奇数边沿保持不变,数据信 号将在SCK 奇数边沿时被采样,在非采样时刻, MOSI 和 MISO 的有效信号才发生 切换。
6.CPOL/CPHA及通讯模式
- 控制NSS信号线,产生起始信号(图中没有画出);
- 把要发送的数据写入到“数据寄存器DR”中,该数据会被存储到发 送缓冲区;
- 通讯开始,SCK时钟开始运行。MOSI把发送缓冲区中的数据一位 一位地传输出去;MISO则把数据一位一位地存储进接收缓冲区中;
- 当发送完一帧数据的时候,“状态寄存器SR”中的“TXE标志位” 会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完 一帧数据的时候,“RXNE标志位”会被置1,表示传输完一帧,接 收缓冲区非空;
- 等待到“TXE标志位”为1时,若还要继续发送数据,则再次往 “数据寄存器DR”写入数据即可;等待到“RXNE标志位”为1时, 通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。
假如使能了TXE 或RXNE中断,TXE或RXNE置1时会产生SPI中断信号, 进入同一个中断服务函数,到SPI中断服务程序后,可通过检查寄存器 位来了解是哪一个事件,再分别进行处理。也可以使用DMA方式来收 发“数据寄存器DR”中的数据。
SPI通讯的优势
使SPI作为串行通信接口脱颖而出的原因很多;
- 全双工串行通信;
- 高速数据传输速率。
- 简单的软件配置;
- 极其灵活的数据传输,不限于8位,它可以是任意大小的字;
- 非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)。
SPI的缺点
- 没有硬件从机应答信号(主机可能在不知情的情况下无处发送);
- 通常仅支持一个主设备;
- 需要更多的引脚(与I2C不同);
- 没有定义硬件级别的错误检查协议;
- 与RS-232和CAN总线相比,只能支持非常短的距离;
部分关键代码:
初始化SPI
- /**
- * @brief SPI_FLASH初始化
- * @param 无
- * @retval 无
- */
- void SPI_FLASH_Init(void)
- {
- SPI_InitTypeDef SPI_InitStructure;
- GPIO_InitTypeDef GPIO_InitStructure;
-
- /* 使能SPI时钟 */
- FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );
-
- /* 使能SPI引脚相关的时钟 */
- FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
- FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE );
-
- /* 配置SPI的 CS引脚,普通IO即可 */
- GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);
-
- /* 配置SPI的 SCK引脚*/
- GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
-
- /* 配置SPI的 MISO引脚*/
- GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
- GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
-
- /* 配置SPI的 MOSI引脚*/
- GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
- GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
-
- /* 停止信号 FLASH: CS引脚高电平*/
- SPI_FLASH_CS_HIGH();
-
- /* SPI 模式配置 */
- // FLASH芯片 支持SPI模式0及模式3,据此设置CPOL CPHA
- SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
- SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
- SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
- SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
- SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
- SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
- SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
- SPI_InitStructure.SPI_CRCPolynomial = 7;
- SPI_Init(FLASH_SPIx , &SPI_InitStructure);
-
- /* 使能 SPI */
- SPI_Cmd(FLASH_SPIx , ENABLE);
-
- }
Flash扇区擦除
- /**
- * @brief 擦除FLASH扇区
- * @param SectorAddr:要擦除的扇区地址
- * @retval 无
- */
- void SPI_FLASH_SectorErase(u32 SectorAddr)
- {
- /* 发送FLASH写使能命令 */
- SPI_FLASH_WriteEnable();
- SPI_FLASH_WaitForWriteEnd();
- /* 擦除扇区 */
- /* 选择FLASH: CS低电平 */
- SPI_FLASH_CS_LOW();
- /* 发送扇区擦除指令*/
- SPI_FLASH_SendByte(W25X_SectorErase);
- /*发送擦除扇区地址的高位*/
- SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
- /* 发送擦除扇区地址的中位 */
- SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
- /* 发送擦除扇区地址的低位 */
- SPI_FLASH_SendByte(SectorAddr & 0xFF);
- /* 停止信号 FLASH: CS 高电平 */
- SPI_FLASH_CS_HIGH();
- /* 等待擦除完毕*/
- SPI_FLASH_WaitForWriteEnd();
- }
-
- /**
- * @brief 擦除FLASH扇区,整片擦除
- * @param 无
- * @retval 无
- */
- void SPI_FLASH_BulkErase(void)
- {
- /* 发送FLASH写使能命令 */
- SPI_FLASH_WriteEnable();
-
- /* 整块 Erase */
- /* 选择FLASH: CS低电平 */
- SPI_FLASH_CS_LOW();
- /* 发送整块擦除指令*/
- SPI_FLASH_SendByte(W25X_ChipErase);
- /* 停止信号 FLASH: CS 高电平 */
- SPI_FLASH_CS_HIGH();
-
- /* 等待擦除完毕*/
- SPI_FLASH_WaitForWriteEnd();
- }
对Flash进行写入(包括页写入)
- /**
- * @brief 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
- * @param pBuffer,要写入数据的指针
- * @param WriteAddr,写入地址
- * @param NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize
- * @retval 无
- */
- void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
- {
- /* 发送FLASH写使能命令 */
- SPI_FLASH_WriteEnable();
-
- /* 选择FLASH: CS低电平 */
- SPI_FLASH_CS_LOW();
- /* 写页写指令*/
- SPI_FLASH_SendByte(W25X_PageProgram);
- /*发送写地址的高位*/
- SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
- /*发送写地址的中位*/
- SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
- /*发送写地址的低位*/
- SPI_FLASH_SendByte(WriteAddr & 0xFF);
-
- if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
- {
- NumByteToWrite = SPI_FLASH_PerWritePageSize;
- FLASH_ERROR("SPI_FLASH_PageWrite too large!");
- }
-
- /* 写入数据*/
- while (NumByteToWrite--)
- {
- /* 发送当前要写入的字节数据 */
- SPI_FLASH_SendByte(*pBuffer);
- /* 指向下一字节数据 */
- pBuffer++;
- }
-
- /* 停止信号 FLASH: CS 高电平 */
- SPI_FLASH_CS_HIGH();
-
- /* 等待写入完毕*/
- SPI_FLASH_WaitForWriteEnd();
- }
-
- /**
- * @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
- * @param pBuffer,要写入数据的指针
- * @param WriteAddr,写入地址
- * @param NumByteToWrite,写入数据长度
- * @retval 无
- */
- void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
- {
- u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
-
- /*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
- Addr = WriteAddr % SPI_FLASH_PageSize;
-
- /*差count个数据值,刚好可以对齐到页地址*/
- count = SPI_FLASH_PageSize - Addr;
- /*计算出要写多少整数页*/
- NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
- /*mod运算求余,计算出剩余不满一页的字节数*/
- NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
-
- /* Addr=0,则WriteAddr 刚好按页对齐 aligned */
- if (Addr == 0)
- {
- /* NumByteToWrite < SPI_FLASH_PageSize */
- if (NumOfPage == 0)
- {
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
- }
- else /* NumByteToWrite > SPI_FLASH_PageSize */
- {
- /*先把整数页都写了*/
- while (NumOfPage--)
- {
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
- WriteAddr += SPI_FLASH_PageSize;
- pBuffer += SPI_FLASH_PageSize;
- }
- /*若有多余的不满一页的数据,把它写完*/
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
- }
- }
- /* 若地址与 SPI_FLASH_PageSize 不对齐 */
- else
- {
- /* NumByteToWrite < SPI_FLASH_PageSize */
- if (NumOfPage == 0)
- {
- /*当前页剩余的count个位置比NumOfSingle小,一页写不完*/
- if (NumOfSingle > count)
- {
- temp = NumOfSingle - count;
- /*先写满当前页*/
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
-
- WriteAddr += count;
- pBuffer += count;
- /*再写剩余的数据*/
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
- }
- else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
- {
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
- }
- }
- else /* NumByteToWrite > SPI_FLASH_PageSize */
- {
- /*地址不对齐多出的count分开处理,不加入这个运算*/
- NumByteToWrite -= count;
- NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
- NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
-
- /* 先写完count个数据,为的是让下一次要写的地址对齐 */
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
-
- /* 接下来就重复地址对齐的情况 */
- WriteAddr += count;
- pBuffer += count;
- /*把整数页都写了*/
- while (NumOfPage--)
- {
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
- WriteAddr += SPI_FLASH_PageSize;
- pBuffer += SPI_FLASH_PageSize;
- }
- /*若有多余的不满一页的数据,把它写完*/
- if (NumOfSingle != 0)
- {
- SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
- }
- }
- }
- }
读Flash
- /**
- * @brief 读取FLASH数据
- * @param pBuffer,存储读出数据的指针
- * @param ReadAddr,读取地址
- * @param NumByteToRead,读取数据长度
- * @retval 无
- */
- void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
- {
- /* 选择FLASH: CS低电平 */
- SPI_FLASH_CS_LOW();
-
- /* 发送 读 指令 */
- SPI_FLASH_SendByte(W25X_ReadData);
-
- /* 发送 读 地址高位 */
- SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
- /* 发送 读 地址中位 */
- SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
- /* 发送 读 地址低位 */
- SPI_FLASH_SendByte(ReadAddr & 0xFF);
-
- /* 读取数据 */
- while (NumByteToRead--) /* while there is data to be read */
- {
- /* 读取一个字节*/
- *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
- /* 指向下一个字节缓冲区 */
- pBuffer++;
- }
-
- /* 停止信号 FLASH: CS 高电平 */
- SPI_FLASH_CS_HIGH();
- }
读取Flash ID以及FLASH Device ID
- /**
- * @brief 读取FLASH ID
- * @param 无
- * @retval FLASH ID
- */
- u32 SPI_FLASH_ReadID(void)
- {
- u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
-
- /* 开始通讯:CS低电平 */
- SPI_FLASH_CS_LOW();
-
- /* 发送JEDEC指令,读取ID */
- SPI_FLASH_SendByte(W25X_JedecDeviceID);
-
- /* 读取一个字节数据 */
- Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
-
- /* 读取一个字节数据 */
- Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
-
- /* 读取一个字节数据 */
- Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
-
- /* 停止通讯:CS高电平 */
- SPI_FLASH_CS_HIGH();
-
- /*把数据组合起来,作为函数的返回值*/
- Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
-
- return Temp;
- }
- /**
- * @brief 读取FLASH Device ID
- * @param 无
- * @retval FLASH Device ID
- */
- u32 SPI_FLASH_ReadDeviceID(void)
- {
- u32 Temp = 0;
-
- /* Select the FLASH: Chip Select low */
- SPI_FLASH_CS_LOW();
-
- /* Send "RDID " instruction */
- SPI_FLASH_SendByte(W25X_DeviceID);
- SPI_FLASH_SendByte(Dummy_Byte);
- SPI_FLASH_SendByte(Dummy_Byte);
- SPI_FLASH_SendByte(Dummy_Byte);
-
- /* Read a byte from the FLASH */
- Temp = SPI_FLASH_SendByte(Dummy_Byte);
-
- /* Deselect the FLASH: Chip Select high */
- SPI_FLASH_CS_HIGH();
-
- return Temp;
- }
主函数MAIN
- typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;
-
- /* 获取缓冲区的长度 */
- #define TxBufferSize1 (countof(TxBuffer1) - 1)
- #define RxBufferSize1 (countof(TxBuffer1) - 1)
- #define countof(a) (sizeof(a) / sizeof(*(a)))
- #define BufferSize (countof(Tx_Buffer)-1)
-
- #define FLASH_WriteAddress 0x00000
- #define FLASH_ReadAddress FLASH_WriteAddress
- #define FLASH_SectorToErase FLASH_WriteAddress
-
-
-
- /* 发送缓冲区初始化 */
- uint8_t Tx_Buffer[] = "感谢\r\n";
- uint8_t Rx_Buffer[BufferSize];
-
- __IO uint32_t DeviceID = 0;
- __IO uint32_t FlashID = 0;
- __IO TestStatus TransferStatus1 = FAILED;
-
- // 函数原型声明
- void Delay(__IO uint32_t nCount);
- TestStatus Buffercmp(uint8_t* pBuffer1,uint8_t* pBuffer2, uint16_t BufferLength);
-
- /*
- * 函数名:main
- * 描述 :主函数
- * 输入 :无
- * 输出 :无
- */
- int main(void)
- {
- LED_GPIO_Config();
- LED_BLUE;
-
- /* 配置串口为:115200 8-N-1 */
- USART_Config();
- printf("\r\n 这是一个8Mbyte串行flash(W25Q64)实验 \r\n");
-
- /* 8M串行flash W25Q64初始化 */
- SPI_FLASH_Init();
-
- /* 获取 Flash Device ID */
- DeviceID = SPI_FLASH_ReadDeviceID();
- Delay( 200 );
-
- /* 获取 SPI Flash ID */
- FlashID = SPI_FLASH_ReadID();
- printf("\r\n FlashID is 0x%X,\
- Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);
-
- /* 检验 SPI Flash ID */
- if (FlashID == sFLASH_ID)
- {
- printf("\r\n 检测到串行flash W25Q64 !\r\n");
-
- /* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */
- // 这里擦除4K,即一个扇区,擦除的最小单位是扇区
- SPI_FLASH_SectorErase(FLASH_SectorToErase);
-
- /* 将发送缓冲区的数据写到flash中 */
- // 这里写一页,一页的大小为256个字节
- SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);
- printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);
-
- /* 将刚刚写入的数据读出来放到接收缓冲区中 */
- SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
- printf("\r\n 读出的数据为:%s \r\n", Rx_Buffer);
-
- /* 检查写入的数据与读出的数据是否相等 */
- TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
-
- if( PASSED == TransferStatus1 )
- {
- LED_GREEN;
- printf("\r\n 8M串行flash(W25Q64)测试成功!\n\r");
- }
- else
- {
- LED_RED;
- printf("\r\n 8M串行flash(W25Q64)测试失败!\n\r");
- }
- }// if (FlashID == sFLASH_ID)
- else// if (FlashID == sFLASH_ID)
- {
- LED_RED;
- printf("\r\n 获取不到 W25Q64 ID!\n\r");
- }
-
- while(1);
- }
-
- /*
- * 函数名:Buffercmp
- * 描述 :比较两个缓冲区中的数据是否相等
- * 输入 :-pBuffer1 src缓冲区指针
- * -pBuffer2 dst缓冲区指针
- * -BufferLength 缓冲区长度
- * 输出 :无
- * 返回 :-PASSED pBuffer1 等于 pBuffer2
- * -FAILED pBuffer1 不同于 pBuffer2
- */
- TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
- {
- while(BufferLength--)
- {
- if(*pBuffer1 != *pBuffer2)
- {
- return FAILED;
- }
-
- pBuffer1++;
- pBuffer2++;
- }
- return PASSED;
- }
-
- void Delay(__IO uint32_t nCount)
- {
- for(; nCount != 0; nCount--);
- }
以上就是SPI的全部内容,本文简单介绍了SPI的使用,希望对大家有帮助!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。