SPI即Serial Peripheral Interface的缩写,全名串行外设接口,是一种高速的支持全双工同步通讯的接口技术。通讯速率可达几M到几十M。
一般的常用的SPI是四根线:
SDO/MOSI:主设备数据输出,从设备数据输入,如主机读取命令;
SDI/MISO:主设备数据输入,从设备数据输,如从机返回数据;
SCLK: 时钟信号,由主设备产生,用于数据同步;
CS/SS: 从设备使能信号,由主设备控制来选择与哪一个从机进行通讯;
四线是一种全双工的
ST的MCU配置四线SPI:
spi.c
#include "spi.h"
#include "main.h"
SPI_HandleTypeDef hspi1;
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(spiHandle->Instance==SPI1)
{
/* SPI1 clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**SPI1 GPIO Configuration
PA4 ------> SPI1_NSS
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{
if(spiHandle->Instance==SPI1)
{
/* Peripheral clock disable */
__HAL_RCC_SPI1_CLK_DISABLE();
/**SPI1 GPIO Configuration
PA4 ------> SPI1_NSS
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
}
}
unsigned char spiTxBuf[256],spiRxBuf[256];
unsigned char SPI_readNBytes ( unsigned char devAddr,
unsigned char regAddr,
unsigned char readLen,
unsigned char *readBuf)
{
HAL_GPIO_WritePin(SPI_CS_GPIO_Port,SPI_CS_Pin,GPIO_PIN_RESET);
spiTxBuf[0] = regAddr|0x80;
for (short k = 1;k< readLen;k++)
{
spiTxBuf[k] = 0xFF;
}
HAL_SPI_TransmitReceive( &hspi1, spiTxBuf,spiRxBuf,readLen+1,readLen+1);
HAL_GPIO_WritePin( SPI_CS_GPIO_Port,SPI_CS_Pin,GPIO_PIN_SET);
while (readLen > 0)
{
readBuf[readLen-1] = spiRxBuf[readLen];
readLen--;
}
return (0);
}
unsigned char SPI_writeNBytes( unsigned char devAddr,
unsigned char regAddr,
unsigned char writeLen,
unsigned char *writeBuf)
{ hbgnh
HAL_GPIO_WritePin(SPI_CS_GPIO_Port,SPI_CS_Pin,GPIO_PIN_RESET);
spiTxBuf[0] = regAddr & 0x7F;
for (int k = 0;k<writeLen;k++)
{
spiTxBuf[k+1] = writeBuf[k];
}
HAL_SPI_TransmitReceive( &hspi1, spiTxBuf,spiRxBuf,writeLen+1,writeLen+1);
HAL_GPIO_WritePin( SPI_CS_GPIO_Port,SPI_CS_Pin,GPIO_PIN_SET);
return (0);
}
总结:当我们程序设置 hspi1.Init.Direction = SPI_DIRECTION_2LINE.
HAL_SPI_TransmitReceive可以在发送的同时也接收。
但是还有一种SPI通讯为了减少线路和管脚,会采用3线制,网上很多人认为3线制是没有CS片选,这是不对的,真正的SPI三线制通讯模式是指SDO/MOSI与SDI/MISO共用一条总线的通讯方式,采用的是半双工通讯。这里又要说到什么是全双工什么是半双工了。全双工是允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工指可以同时进行信号的双向传输。
半双工是数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
ST的MCU配置三线SPI:
SPI.c
#include "spi.h"
#include "main.h"
SPI_HandleTypeDef hspi1;
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_1LINE; /* 半双工三线SPI */
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(spiHandle->Instance==SPI1)
{
/* SPI1 clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**SPI1 GPIO Configuration
PA4 ------> SPI1_NSS
PA5 ------> SPI1_SCK
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{
if(spiHandle->Instance==SPI1)
{
/* Peripheral clock disable */
__HAL_RCC_SPI1_CLK_DISABLE();
/**SPI1 GPIO Configuration
PA4 ------> SPI1_NSS
PA5 ------> SPI1_SCK
PA7 ------> SPI1_MOSI
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_7);
}
}
unsigned char spiTxBuf[256],spiRxBuf[256];
unsigned char SPI3_readNBytes ( unsigned char devAddr,
unsigned char regAddr,
unsigned short readLen,
unsigned char *readBuf)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET); /* 拉低片选 */
spiTxBuf[0] = regAddr | 0x80;
HAL_SPI_Transmit( &hspi1, spiTxBuf, 1, 1); /* 发送(只发送) */
HAL_SPI_Receive ( &hspi1, readBuf, readLen, readLen); /* 接收(只接收) */
HAL_GPIO_WritePin( GPIOA,GPIO_PIN_4,GPIO_PIN_SET); /* 拉高片选 */
return 0;
}
unsigned char SPI3_writeNBytes( unsigned char devAddr,
unsigned char regAddr,
unsigned short writeLen,
unsigned char *writeBuf)
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET); /* 拉低片选 */
spiTxBuf[0] = regAddr & 0x7F;
for (int k = 0;k<writeLen;k++) {
spiTxBuf[k+1] = writeBuf[k];
}
HAL_SPI_Transmit( &hspi1, spiTxBuf, writeLen+1, writeLen+1);
HAL_GPIO_WritePin( GPIOA,GPIO_PIN_4,GPIO_PIN_SET); /* 拉高片选 */
return 0;
}
总结:当我们程序设置 hspi1.Init.Direction = SPI_DIRECTION_1LINE当使用HAL_SPI_Transmit函数
底层会设置成只发送。当使用HAL_SPI_Receive函数底层会设置成只接收。
具体控制BIDIMODE位
因为SDO/MOSI与SDI/MISO共用了一条总线,所以并不能同时传输,也就是所谓的半双工通讯。而通讯过程中同样要用到CS/SS线进行片选,并不是没有CS/SS。而所谓的不需要CS/SS 线是指的如果SPI线上有一个主机和一个从机的情况下,是可以省略CS/SS片选线的,因为只有1个从机,所以该从机的CS/SS可以设置成常选状态,不能采用CS/SS拉高来作为结束,如果出现数据错误后果会很严重,后面会一直错误,这种3线确实也是3条线,但是跟半双工3线制模式的SPI通讯还是有区别的,往往很多IC所明确的只能采用3线制一般都是指半双工模式。
主要介绍SPI的几种模式及应用