目录
1、SPI概述
SPI是串行外设接口 (Serial Peripheral Interface) 的缩写。是 Motorola
首先在其 MC68HCXX 系列处理器上定义的,是一种高速的,全双工,同步的通信总线。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,如ADS8341,还有数字信号处理器和数字信号解码器之间,在芯片的管脚上只占用四根线(也可只用三根线,片选信号由硬件直接拉低),节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。但是没有指定的流控制,没有应答机制确认是否接收到数据。
2、SPI接口
SPI 接口一般使用 4 条线通信:
a)、MISO 主设备数据输入,从设备数据输出。
b)、MOSI 主设备数据输出,从设备数据输入。
c)、SCLK 时钟信号,由主设备产生。
d)、CS 从设备片选信号,由主设备控制。(若只有一个主设备和一个从设备可以省略,直接拉低)
SPI 内部结构简图
当有多个从机时,每个从机单独接一个片选信号,即由片选信号决定与哪个从设备通讯。
3、SPI 总线四种工作方式
STM32中的SPI_CR寄存器的CPOL和CPHA位,能够组合成四种可能的时序关系。
- CPOL=0:SCLK=0时处于空闲态;CPOL=1:SCLK=1时处于空闲态;
- CPHA=0:数据采样在第1个边沿;CPHA=1:数据采样在第2个边沿。
一般情況下,我们选择哪种方式是根据从设备来确定的,可以查看从设备的时序图或芯片手册等,保持主从设备的工作方式一致即可。
上图是TI公司的ADS8341,16位、4通道、SPI接口的AD采样芯片的时序图,可以看到,空闲时时钟时低电平,数据在第一个边沿进行传输,故CPOL=0,CPHA=0;从时序图中我们也可看出,在向芯片写数据的同时,芯片也在输出数据,只是输出的数据为0,同时我们读取设备的时候也可向数据中写入0;
4、SPI接口初始化举例
4.1单片机的SPI使用
以STM32f103控制的一个从设备的初始化过程来说明:使用SPI2,其中时钟引脚为PB13,MISO为PB14,MOSI为PB15;
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB 时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE );//①SPI2 时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //①初始化 GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15 上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置 SPI 全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置 SPI 工作模式:设置为主 SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//选择了串行时钟的稳态:时钟悬空高
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由硬件管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //②根据指定的参数初始化外设 SPIx 寄存器
SPI_Cmd(SPI2, ENABLE); //③使能 SPI 外设
SPI2_ReadWriteByte(0xff); //④启动传输
}
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //等待发送区空
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设 SPIx 发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //等待接收
//完一个 byte
{ retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过 SPIx 最近接收的数据
}
4.2 带Linux/Android系统的SPI使用
一般带Linux/Android系统的控制板,系统中都自带SPI的驱动,我们只需要在板级文件或设备树中开放驱动和对引脚进行相关配置即可,在系统中一般有SPI的使用文档,可自行查看;
void SPI_Init(void)
{
int ret = 0;
ret = ioctl(m_fd, SPI_IOC_WR_MODE, &mode);
if (ret == -1)
pabort("can't set spi mode");
ret = ioctl(m_fd, SPI_IOC_RD_MODE, &mode);
if (ret == -1)
pabort("can't get spi mode");
/*
* bits per word
*/
ret = ioctl(m_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't set bits per word");
ret = ioctl(m_fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't get bits per word");
/*
* max speed hz
*/
ret = ioctl(m_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't set max speed hz");
ret = ioctl(m_fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't get max speed hz");
/*
* SPI_IOC_WR_LSB_FIRST
*/
ret =ioctl(m_fd, SPI_IOC_WR_LSB_FIRST, &num);
if (ret == -1)
pabort("can't set SPI_IOC_WR_LSB_FIRST");
ret =ioctl(m_fd, SPI_IOC_RD_LSB_FIRST, &num);
if (ret == -1)
pabort("can't set SPI_IOC_RD_LSB_FIRST");
}
int transfer(int fd, unsigned char cmd) {
int ret;
unsigned char tx[] = {cmd };
unsigned char rx[ARRAY_SIZE(tx)] = {0,};
struct spi_ioc_transfer tr ;
tr.tx_buf = (unsigned long)tx;
tr.rx_buf = (unsigned long)rx;
tr.len = ARRAY_SIZE(tx);
tr.delay_usecs = delay;
tr.speed_hz = speed;
tr.bits_per_word = bits;
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1)
return -1;
return 0;
}
5、SPI的调试
SPI程序写好,下载到开发板中后,相关配置不一定正确,也许无法得到需要的结果,要进行调试,个人认为主要的工具有万用表和示波器。
1)首先检测硬件电路是否通畅,在通讯时,相关的SPI片选(CS)是否被拉低,可用万用表进行测量,或者先使用硬件直接拉低,进行调试,正确后在使用普通IO进行控制;
2)使用示波器查看是否有时钟输出,没有时钟输出,就需检查相关配置了,如stm32的SPI时钟是否使能、NSS信号是否配置等。
3)使用示波器同时检测时钟和MOSI两个引脚,主要目的是检测我们的输入是否正确,是否是我们想要给从设备的输入值,可以直接在示波器中手动读数,如果示波器有SPI读数功能配置后自动读数。
4)若对从设备来说输入正确,但是输出有问题,使用示波器同时检测时钟和MOSI两个引脚,查看是否是我们想的输出,若在示波器中是正确的,但是我们在软件中读出的结果却是有问题的,这一定是我们软件中某些配置有问题,可查看芯片手册看结果是否需要移位,是需要输出是8位、16位还是其他等。如,若是使用SPI进行AD转换,我们可以查看参考电压是否正确,模拟电压是多少,可以计算出应该输出的值为多少。