注意:资料查询请查看本人资源(免费)
25.1 SPI 协议简介
1.物理层
SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线
(1) ( Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、CS,以下用 NSS 表示。
(2) SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,
不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 fpclk/2,两个设备之间
通讯时,通讯速率受限于低速设备。
(3) MOSI (Master Output,Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输
出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
(4) MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数
据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
2.协议层
与 I2C 的类似,SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步
(1)SPI 基本通讯过程
(2)通讯的起始和停止信号
(3)数据有效性
(4)CPOL/CPHA 及通讯模式
25.2 STM32 的 SPI 特性及架构
1.STM32 的 SPI 外设简介
2.STM32 的 SPI 架构剖析
(1)通讯引脚
(2)时钟控制逻辑
(3)数据控制逻辑
(4)整体控制逻辑
3.通讯过程
(1) 控制 NSS 信号线,产生起始信号 (图中没有画出);
(2) 把要发送的数据写入到“数据寄存器 DR”中,该数据会被存储到发送缓冲区;
(3) 通讯开始,SCK 时钟开始运行。MOSI 把发送缓冲区中的数据一位一位地传输出去;MISO 则
把数据一位一位地存储进接收缓冲区中;
(4) 当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表示传输完一
帧,发送缓冲区已空;
(5) 等待到“TXE 标志位”为 1 时,若还要继续发送数据,则再次往“数据寄存器 DR”写入数据
即可;
假如我们使能了 TXE 或 RXNE 中断,TXE 或 RXNE 置 1 时会产生 SPI 中断信号,进入同一个中
断服务函数,到 SPI 中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处
理。也可以使用 DMA 方式来收发“数据寄存器 DR”中的数据。
25.3 SPI 初始化结构体详解
(1) SPI_Direction
(2) SPI_Mode
(3) SPI_DataSize
(4) SPI_CPOL 和 SPI_CPHA
(5) SPI_NSS
(6) SPI_BaudRatePrescaler
(7) SPI_FirstBit
(8) SPI_CRCPolynomial
25.4 SPI—读写串行 FLASH 实验
1. 硬件设计
2.软件设计
2.1 编程要点
(1) 初始化通讯使用的目标引脚及端口时钟;
(2) 使能 SPI 外设的时钟;
(3) 配置 SPI 外设的模式、地址、速率等参数并使能 SPI 外设;
(4) 编写基本 SPI 按字节收发的函数;
(5) 编写对 FLASH 擦除及读写操作的的函数;
(6) 编写测试程序,对读写数据进行校验。
2.2 代码分析
(1)SPI 硬件相关宏定义
(2)初始化 SPI 的 GPIO
1… 使用 GPIO_InitTypeDef 定义 GPIO 初始化结构体变量,以便下面用于存储 GPIO 配置;
2… 调用库函数 RCC_APB2PeriphClockCmd 来使能 SPI 引脚使用的 GPIO 端口时钟。
3… 向 GPIO 初始化结构体赋值,把 SCK/MOSI/MISO 引脚初始化成复用推挽模式。而 CS(NSS) 引脚由于使用软件控制,我们把它配置为普通的推挽输出模式。
4… 使用以上初始化结构体的配置,调用 GPIO_Init 函数向寄存器写入参数,完成 GPIO 的初始化。
(3)配置 SPI 的模式
(4)使用 SPI 发送和接收一个字节的数据
1… 本函数中不包含 SPI 起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好
起始和停止信号的操作;
2… 对 SPITimeout 变量赋值为宏 SPIT_FLAG_TIMEOUT。这个 SPITimeout 变量在下面的 while 循
环中每次循环减 1,该循环通过调用库函数 SPI_I2S_GetFlagStatus 检测事件,若检测到事件,则
进入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测 SPIT_FLAG_TIMEOUT
次都还没等待到事件则认为通讯失败,调用的 SPI_TIMEOUT_UserCallback 输出调试信息,并退
出通讯;
3… 通过检测 TXE 标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个
数据已经发送完毕;
4… 等待至发送缓冲区为空后,调用库函数 SPI_I2S_SendData 把要发送的数据“byte”写入到 SPI
的数据寄存器 DR,写入 SPI 数据寄存器的数据会存储到发送缓冲区,由 SPI 外设发送出去;
5… 写入完毕后等待 RXNE 事件,即接收缓冲区非空事件。由于 SPI 双线全双工模式下 MOSI 与
MISO 数据传输是同步的 (请对比“SPI 通讯过程”阅读),当接收缓冲区非空时,表示上面的数据
发送完毕,且接收缓冲区也收到新的数据;
6… 等待至接收缓冲区非空时,通过调用库函数 SPI_I2S_ReceiveData 读取 SPI 的数据寄存器
DR,就可以获取接收缓冲区中的新数据了。代码中使用关键字“return”把接收到的这个数据
作为 SPI_FLASH_SendByte 函数的返回值,所以我们可以看到在下面定义的 SPI 接收数据函数
SPI_FLASH_ReadByte,它只是简单地调用了 SPI_FLASH_SendByte 函数发送数据“Dummy_Byte”,
然后获取其返回值 (因为不关注发送的数据,所以此时的输入参数“Dummy_Byte”可以为任意
值)。可以这样做的原因是 SPI 的接收过程和发送过程实质是一样的,收发同步进行,关键在于
我们的上层应用中,关注的是发送还是接收的数据。
(5)控制 FLASH 的指令
(6)定义 FLASH 指令编码表
(7)读取 FLASH 芯片 ID
(8)FLASH 写使能以及读取当前状态
(9)FLASH 扇区擦除
(10)FLASH 的页写入
(11)不定量数据写入
(12)从 FLASH 读取数据
bsp_DMA_SPI.h
#include "stm32f10x.h"
#define FLASH_SPIx SPI1
#define FLASH_SPI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CLK RCC_APB2Periph_SPI1
#define FLASH_SPI_CS_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_CS_CLK RCC_APB2Periph_GPIOC
#define FLASH_SPI_CS_PORT GPIOC
#define FLASH_SPI_CS_PIN GPIO_Pin_0
#define FLASH_SPI_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_SCK_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_SCK_PORT GPIOA
#define FLASH_SPI_SCK_PIN GPIO_Pin_5
#define FLASH_SPI_MISO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_MISO_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_MISO_PORT GPIOA
#define FLASH_SPI_MISO_PIN GPIO_Pin_6
#define FLASH_SPI_MOSI_APBxClock_FUN RCC_APB2PeriphClockCmd
#define FLASH_SPI_MOSI_CLK RCC_APB2Periph_GPIOA
#define FLASH_SPI_MOSI_PORT GPIOA
#define FLASH_SPI_MOSI_PIN GPIO_Pin_7
#define FLASH_SPI_CS_LOW() GPIO_ResetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define FLASH_SPI_CS_HIGH() GPIO_SetBits( FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN )
#define WIP_Flag 0x01
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
#define FLASH_WriteAddress 0x00
#define FLASH_ReadAddress 0x00
#define BufferSize 20
#define BUFFER_SIZE 32
#define SPI_FLASH_PerWritePageSize 256
#define SPI_FLASH_PageSize 256
#define FLASH_SectorToErase 0x00
#define PASSED 0x01
#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
#define sFLASH_ID 0XEF4017
#define Dummy_Byte 0xFF
void SPI_FLASH_Init(void);
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
void FLASH_ERROR(char * errorCode);
u8 SPI_FLASH_SendByte(u8 byte);
u8 SPI_FLASH_ReadByte(void);
u32 SPI_FLASH_ReadID(void);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);
void SPI_FLASH_SectorErase(u32 SectorAddr);
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead);
uint8_t Buffercmp(uint8_t* pBuffer,uint8_t* pBuffer1, uint16_t BufferLength);
extern uint32_t SPITimeout;
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
bsp_DMA_SPI.c
#include "bsp_DMA_SPI.h"
uint32_t SPITimeout;
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]=
{
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80
};
void SPI_FLASH_Init(void) {
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );
FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|
FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE );
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);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);
FLASH_SPI_CS_HIGH();
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_Cmd(FLASH_SPIx, ENABLE);
}
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
printf("I2C 等待超时!errorCode = %d",errorCode);
return 0;
}
void FLASH_ERROR(char * errorCode){
printf("I2C error !errorCode=%s",errorCode);
}
u8 SPI_FLASH_SendByte(u8 byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
while (SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_TXE) == RESET)
{
if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
}
SPI_I2S_SendData(FLASH_SPIx, byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
while (SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_RXNE) == RESET)
{
if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
}
return SPI_I2S_ReceiveData(FLASH_SPIx);
}
u8 SPI_FLASH_ReadByte(void)
{
return (SPI_FLASH_SendByte(Dummy_Byte));
}
u32 SPI_FLASH_ReadID(void) {
u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
FLASH_SPI_CS_LOW();
SPI_FLASH_SendByte(W25X_JedecDeviceID);
Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
FLASH_SPI_CS_HIGH();
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}
void SPI_FLASH_WriteEnable(void) {
FLASH_SPI_CS_LOW();
SPI_FLASH_SendByte(W25X_WriteEnable);
FLASH_SPI_CS_HIGH();
}
void SPI_FLASH_WaitForWriteEnd(void)
{
u8 FLASH_Status = 0;
FLASH_SPI_CS_LOW();
SPI_FLASH_SendByte(W25X_ReadStatusReg);
do
{
FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);
}
while ((FLASH_Status & WIP_Flag) == SET);
FLASH_SPI_CS_HIGH();
}
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
SPI_FLASH_WriteEnable();
SPI_FLASH_WaitForWriteEnd();
FLASH_SPI_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_SPI_CS_HIGH();
SPI_FLASH_WaitForWriteEnd();
}
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
SPI_FLASH_WriteEnable();
FLASH_SPI_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 small!");
}
while (NumByteToWrite--)
{
SPI_FLASH_SendByte(*pBuffer);
pBuffer++;
}
FLASH_SPI_CS_HIGH();
SPI_FLASH_WaitForWriteEnd();
}
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % SPI_FLASH_PageSize;
count = SPI_FLASH_PageSize - Addr;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
if (Addr == 0)
{
if (NumOfPage == 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr,
NumByteToWrite);
}
else
{
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr,
SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
SPI_FLASH_PageWrite(pBuffer, WriteAddr,
NumOfSingle);
}
}
else
{
if (NumOfPage == 0)
{
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
}
else
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr,
NumByteToWrite);
}
}
else
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
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);
}
}
}
}
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
FLASH_SPI_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--)
{
*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
pBuffer++;
}
FLASH_SPI_CS_HIGH();
}
uint8_t Buffercmp(uint8_t* pBuffer,uint8_t* pBuffer1, uint16_t BufferLength)
{
while (BufferLength--) {
if (*pBuffer != *pBuffer1) {
return 0;
}
pBuffer++;
pBuffer1++;
}
return 1;
}
(13)main 函数
#include "bsp_led.h"
#include "./SPI/bsp_DMA_SPI.h"
#include "bsp_usart.h"
#include "bsp_led.h"
void Delay(__IO u32 nCount);
uint8_t SPI_Buf_Write[255]={"王祖豪是个好人么"};
uint8_t SPI_Buf_Read[255];
u32 DeviceID;
u32 FlashID;
uint8_t TransferStatus1;
int main(void) {
LED_GPIO_Config();
LED_BLUE;
Delay(1000);
USART_Config();
printf("\r\n 这是一个 8Mbyte 串行 flash(W25Q64) 实验 \r\n");
SPI_FLASH_Init();
DeviceID = SPI_FLASH_ReadID();
Delay( 200 );
FlashID = SPI_FLASH_ReadID();
printf("\r\n FlashID is 0x%X,\
Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);
if (FlashID == sFLASH_ID)
{
printf("\r\n 检测到串行 flash W25Q64 !\r\n");
SPI_FLASH_SectorErase(FLASH_SectorToErase);
SPI_FLASH_BufferWrite(SPI_Buf_Write, FLASH_WriteAddress, BufferSize);
printf("\r\n 写入的数据为:%s \r\t", SPI_Buf_Write);
SPI_FLASH_BufferRead(SPI_Buf_Read, FLASH_ReadAddress, BufferSize);
printf("\r\n 读出的数据为:%s \r\n", SPI_Buf_Read);
TransferStatus1 = Buffercmp(SPI_Buf_Write, SPI_Buf_Read, 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");
}
}
else
{
LED_RED;
printf("\r\n 获取不到 W25Q64 ID!\n\r");
}
while (1);
}
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
|