STM32教程----SPI驱动
W25Q128是Winbond公司推出的128Mb(16MB)容量NOR Flash存储器芯片,支持SPI/QSPI接口,广泛应用于嵌入式系统和数据存储领域。XXX代表芯片的容量为xxxMb。
·
一、SPI通信协议介绍
(一)SPI基本概念
SPI(Serial Peripheral Interface),即串行外围设备接口,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议。其工作模式有两种:主模式和从模式。SPI是一种允许一个主设备启动一个从设备的同步通讯的协议,从而完成数据的交换。也就是SPI是一种规定好的通讯方式。这种通信方式的优点是占用端口较少,一般4根就够基本通讯了(不算电源线)。同时传输速度也很高。一般来说要求主设备要有SPI控制器(也可用模拟方式),就可以与基于SPI的芯片通讯。
(二)SPI通讯模型

箭头表示数据的流向。

(三)SPI特性
1、SPI主机通过4根线和从机进行通信:
- MISO: 主机发送从机接收。
- MOSI:从机发送主机接收。
- SCK(SCLK):时钟信号,由主机产生。
- NSS:片选信号,由主机发送,选择与那个从机通信,一般为低电平位有效信号。
2、时钟极性(CPOL):
- CPOL = 0 :时钟空闲时为低电平 0
- CPOL = 1 :时钟空闲时为高电平 1
3、时钟相位(CPHA):
- CPHA = 0 :在时钟SCK的第一个跳变沿采样
- CPHA = 1 :在时钟SCK的第二个跳变沿采样
4、SPI通讯模式

| SPI MODE | CPOL | CPHA |
| MODE0 :00 | 0 | 0 |
| MODE1 :01 | 0 | 1 |
| MODE2 :10 | 1 | 0 |
| MODE3 :11 | 1 | 1 |
(四)SPI的优缺点
1、优点
- 全双工串行通信
- 传输速度快
- 极其灵活的数据传输,不限于8位,它可以是任意大小的字
- 结构简单,实现简单
2、缺点
- 传输距离有限
- 使用四根信号线(I2C和UART使用两根信号线)
- 无法确认是否已成功接收数据(I2C拥有此功能)
- 没有任何形式的错误检查,如UART中的奇偶校验位
- 只允许一个主设备
- 没有定义硬件级别的错误检查协议
二、STM32的SPI
(一)SPI框图

(二)STM32的SPI配置流程
- SPI工作参数配置: HAL_SPI_Init()
- 使能SPI时钟和初始化相关引脚: HAL_SPI_MspInit()
- 使能SPI: __HAL_SPI_ENABLE()
- SPI传输数据
(三)初始化相关重要结构体
//1、SPI handle Structure definition
typedef struct __SPI_HandleTypeDef{
SPI_TypeDef *Instance; //基地址
SPI_InitTypeDef Init; //初始化结构体
uint8_t *pTxBuffPtr; //发送缓存
uint16_t TxXferSize;/发送数据大小
uint16_t TxXferCount;//还剩余多少个数据要发送
uint8_t *pRxBuffPtr;//接收缓存
uint16_t RxXferSize;//接收数据大小
uint16_t RxXferCount;//还剩余多少个数据要接收
DMA_HandleTypeDef *hdmatx;//DMA 发送句柄
DMA_HandleTypeDef *hdmarx;//DMA 接收句柄
void (*RxISR)(struct __SPI_HandleTypeDef * hspi);
void (*TxISR)(struct __SPI_HandleTypeDef * hspi);
HAL_LockTypeDef Lock;
__IO HAL_SPI_StateTypeDef State;
__IO uint32_t ErrorCode;
}SPI_HandleTypeDef;
//1、SPI Configuration Structure definition
typedef struct {
uint32_t Mode; // 模式:主(SPI_MODE_MASTER),从(SPI_MODE_SLAVE)
uint32_t Direction;//方式:只接受模式,单线双向通信数据模式,全双工
uint32_t DataSize; //8 位还是 16 位帧格式选择项
uint32_t CLKPolarity; //时钟极性
uint32_t CLKPhase; //时钟相位
uint32_t NSS; //SS 信号由硬件(NSS 管脚)还是软件控制
uint32_t BaudRatePrescaler;//SS 信号由硬件(NSS 管脚)还是软件控制
uint32_t FirstBit; //SS 信号由硬件(NSS 管脚)还是软件控制
uint32_t TIMode;//SS 信号由硬件(NSS 管脚)还是软件控制
uint32_t CRCCalculation; //SS 信号由硬件(NSS 管脚)还是软件控制
uint32_t CRCPolynomial;//CRC 多项式
} SPI_InitTypeDef;
(四)初始化配置代码
SPI_HandleTypeDef hspi1;
void SPI_Init(void){
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; //主机模式
hspi1.Init.Direction = SPI_DIRECTION_2LINES; //双线:全双工
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; //8位
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; //空闲时为高电平
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; //(第二个)偶数个跳变沿采样数据
hspi1.Init.NSS = SPI_NSS_SOFT; //片选CS由软件控制
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; //不使用TI模式
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; //数据MSB,高位优先
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; //定义波特率预分频的值:波特率预分频值为2,最高速度42Mhz
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; //关闭硬件CRC校验
hspi1.Init.CRCPolynomial = 7;
if(HAL_SPI_Init(&hspi1) != HAL_OK){
printf("HAL_SPI_Init error \r\n");
Error_Handler();
}
__HAL_SPI_ENABLE(&hspi1); //使能SPI
}
//SPI初始化回调函数
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi){
if (hspi->Instance == SPI1){
GPIO_InitTypeDef GPIO_InitStruct = {0};
//初始化SPI1
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIO时钟
//初始化SCK、MISO、MOSI引脚
GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
//初始化CS引脚
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
}
(五)STM32的SPI读写函数(基于HAL库)
//1、SPI发送
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi,
const uint8_t *pData, uint16_t Size,
uint32_t Timeout);
//2、SPI读数据
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,
uint16_t Size, uint32_t Timeout);
//3、SPI读写数据,同时读和写
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi,
const uint8_t *pTxData,
uint8_t *pRxData, uint16_t Size,
uint32_t Timeout);
/*
HAL_XXX_Transmit_IT : 类似这种以_IT结尾的就是以中断的方式发送或接收
HAL_XXX_Transmit_DMA : 类似这种以_DMA结尾的是以DMA方式发送或者接收
*/
三、SPI使用---读取W25Q128芯片的芯片ID
(一)W25Qxxx芯片介绍
W25Q128是Winbond公司推出的128Mb(16MB)容量NOR Flash存储器芯片,支持SPI/QSPI接口,广泛应用于嵌入式系统和数据存储领域。
XXX代表芯片的容量为xxxMb。
(二)SPI时序
W25Q128支持spi MODE0(空闲时时钟为低电平,奇数个跳变沿采样)和MODE3(空闲时时钟为高电平,偶数个跳变沿采样)两种模式。
读写命令表:


查看芯片手册:读取芯片ID的时序为:发送0x90,紧接着两个无效字符,一个0x00,在接收一个字节的厂商ID,一个字节的芯片ID。
(三)代码实现读取芯片ID
uint16_t Flash_ReadID(void){
uint16_t ID = 0;
uint8_t byteData=0;
uint8_t CMD=0x90;
//CS = 0 : 开始通信
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &CMD, 1, 200); //发送0x90
CMD=0x00;
HAL_SPI_Transmit(&hspi1, &CMD, 1, 200); //第一个无效字节
HAL_SPI_Transmit(&hspi1, &CMD, 1, 200); //第二个无效字节
HAL_SPI_Transmit(&hspi1, &CMD, 1, 200); //发送0x00
HAL_SPI_Receive(&hspi1, &byteData, 1, 200); //接收厂商ID
ID=byteData;
ID=ID<<8;//Manufacturer ID
HAL_SPI_Receive(&hspi1, &byteData, 1, 200); //接收芯片ID
ID|=byteData; //Device ID
//CS = 1: 结束通信
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
return ID;
}更多推荐



所有评论(0)