SPI全称是串行外设接口(Serial Peripheral Interface),是由Motorola提出的一种全双工同步串行通信接口,通信波特率可以高达5Mbps,但具体速度大小取决于SPI硬件。SPI接口具有全双工操作,操作简单,数据传输速率较高的优点,但也存在没有指定的流控制,没有应答机制确认是否接收到数据的缺点。SPI总线只需四条线就可以完成MCU与各种外围器件的通讯:
SCLK:Serial Clock,(串行)时钟
MISO:Master In Slave Out,主设备输入,从设备输出
MOSI:Master Out Slave In,主设备输出,从设备输入
SS: Slave Select,选中从设备,片选,片选的其他别称[① CS(Chip Select)芯片选择、②CE(Chip Enable)芯片使能]
先简单说一下,关于SPI中一些常见的说法:
SPI的极性Polarity和相位Phase,最常见的写法是CPOL和CPHA,不过也有一些其他写法,简单总结如下:
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (时钟)相位
(3) SCK=SCLK=SPI的时钟
(4) Edge=边沿,即时钟电平变化的时刻,即上升沿(rising edge)或者下降沿(falling edge)
对于一个时钟周期内,有两个edge,分别称为:
Leading edge=前一个边沿=第一个边沿,对于开始电压是1,那么就是1变成0的时候,对于开始电压是0,那么就是0变成1的时候;
Trailing edge=后一个边沿=第二个边沿,对于开始电压是1,那么就是0变成1的时候(即在第一次1变成0之后,才可能有后面的0变成1),对于开始电压是0,那么就是1变成0的时候;
①、SPI是[单主设备(single-master )]通信协议,这意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写[从设备]时,它首先拉低[从设备]对应的SS线(SS是低电平有效),接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,[主设备]把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”
②、SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响,SPI主模块和与之通信的外设音时钟相位和极性应该一致。
如果CPOL=0,串行同步时钟的空闲状态为低电平;
如果CPOL=1,串行同步时钟的空闲状态为高电平;
时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。
如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;
如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样;
在一个SPI时钟周期内,会完成如下操作:
1) 主设备通过MOSI线发送1位数据,从设备通过该线读取这1位数据;
2) 从设备通过MISO线发送1位数据,主设备通过该线读取这1位数据。
这是通过移位寄存器来实现的。如图所示,主设备和从设备各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主设备寄存器和从机寄存器,并且依次移入从设备寄存器和主设备寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。
SSPSR 是 SPI 设备内部的移位寄存器(Shift Register). 它的主要作用是根据 SPI 时钟信号状态, 往 SSPBUF 里移入或者移出数据, 每次移动的数据大小由Bus-Width 以及 Channel-Width 所决定。
用排线短接树莓派的19脚和21脚,实现树莓派spi通信的自发自收
①、使能内核SPI驱动模块抽象出spi设备
②、利用open系统调用打开spi设备 "/dev/spidev0.0"
③、利用ioctl设置spi设备各项参数
④、进行读写操作
①、使能内核SPI驱动
pi@raspberrypi:~ $ sudo raspi-config 弹出系统配置对话框,使能spi,重新启动树莓派,内核加载成功
重启之后可以确认系统已自动加载SPI驱动
pi@raspberrypi:~ $ ls /dev/spidev0.*
/dev/spidev0.0 /dev/spidev0.1
这里抽象出两个spi接口设备,但树莓派只引出来一组spi(引脚19、21、23、24),对应的设备文件为/dev/spidev0.0
②树莓派自发自收代码
/*********************************************************************************
* Copyright: (C) 2018 wangtao
* All rights reserved.
*
* Filename: spi_own.c
* Description: This file
*
* Version: 1.0.0(11/07/2018)
* Author: WangTao
* ChangeLog: 1, Release initial version on "11/07/2018 17:15:56 PM"
*
********************************************************************************/
#include
#include
#include
#include "SPISet.h"
int initSPI()
{
int spiFd;
spiFd=SPISetup(0,500000); //初始化SPI通道0,并设置为最大速度500000hz
if(spiFd==-1)
{
printf("init spi failed!\n");
}
}
int main()
{
char tx_Data[10]={1,2,3,4,5,6,7,8,9,10}; //定义读写的数据
char rx_Data[10]={0,0,0,0,0,0,0,0,0,0};
int i=0;
initSPI(); //spi的初始化
while(1)
{
SPIDataRW(0,tx_Data,rx_Data,7); //向总线中写入7个数据
printf("read spi_rx_data is:\n"); //读出总线的数据,引脚19与21短接打印【1,2,3,4,5,6,7,0,0,0】
// 引脚19与21不短接打印【0,0,0,0,0,0,0,0,0,0】
for(i=0;i<10;i++)
{
printf("%d\n",rx_Data[i]);
}
printf("\n");
sleep(1);
}
return 0;
}
/*********************************************************************************
* Copyright: (C) 2018 wangtao
* All rights reserved.
*
* Filename: SPISet.c
* Description: This file
*
* Version: 1.0.0(11/07/2018)
* Author: WangTao
* ChangeLog: 1, Release initial version on "11/07/2018 17:15:56 PM"
*
********************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include "SPISet.h"
static const char *spiDev0 = "/dev/spidev0.0" ;
static const char *spiDev1 = "/dev/spidev0.1" ;
static uint8_t spiBPW = 8 ;
static uint16_t spiDelay = 0 ;
static uint32_t spiSpeeds [2] ;
static int spiFds [2] ;
/*
* SPIDataRW:
* Write and Read a block of data over the SPI bus.
* Note the data ia being read into the transmit buffer, so will
* overwrite it!
* This is also a full-duplex operation.
*********************************************************************************
*********************************************************************************/
int SPIDataRW (int channel, unsigned char *tx_data, unsigned char *rx_data, int len)
{
int i = 0;
struct spi_ioc_transfer spi ;
channel &= 1 ;
memset (&spi, 0, sizeof (spi)) ;
spi.tx_buf = (unsigned long)tx_data ;
spi.rx_buf = (unsigned long)rx_data ;
spi.len = len ;
spi.delay_usecs = spiDelay ;
spi.speed_hz = spiSpeeds [channel] ;
spi.bits_per_word = spiBPW ;
return ioctl (spiFds [channel], SPI_IOC_MESSAGE(1), &spi) ; //SPI_IOC_MESSAGE(1)的1表示spi_ioc_transfer的数量
}
/*
* SPISetupMode:
* Open the SPI device, and set it up, with the mode, etc.
*********************************************************************************
*********************************************************************************/
int SPISetupMode (int channel, int speed, int mode)
{
int fd ;
if ((fd = open (channel == 0 ? spiDev0 : spiDev1, O_RDWR)) < 0)
{
printf("Unable to open SPI device: %s\n", strerror (errno)) ;
return -1;
}
spiSpeeds [channel] = speed ;
spiFds [channel] = fd ;
/*
* 设置spi的读写模式:
* Mode 0: CPOL=0, CPHA=0
* Mode 1: CPOL=0, CPHA=1
* Mode 2: CPOL=1, CPHA=0
* Mode 3: CPOL=1, CPHA=1
* 这里我们默认设置为模式0
*********************************************************************************
*/
if (ioctl (fd, SPI_IOC_WR_MODE, &mode) < 0)
{
printf("Can't set spi mode: %s\n", strerror (errno)) ;
return -1;
}
if (ioctl (fd, SPI_IOC_RD_MODE, &mode) < 0)
{
printf("Can't get spi mode: %s\n", strerror (errno)) ;
return -1;
}
/*
* spi的读写bit/word设置可写
* 这里设置为8个位为一个字节
*********************************************************************************
*/
if (ioctl (fd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0)
{
printf("Can't set bits per word: %s\n", strerror (errno)) ;
return -1;
}
if (ioctl (fd, SPI_IOC_RD_BITS_PER_WORD, &spiBPW) < 0)
{
printf("Can't get bits per word: %s\n", strerror (errno)) ;
return -1;
}
/*
* 设置spi读写速率
*********************************************************************************
*/
if (ioctl (fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0)
{
printf("Can't set max speed hz: %s\n", strerror (errno));
return -1;
}
if (ioctl (fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0)
{
printf("Can't get max speed hz: %s\n", strerror (errno));
return -1;
}
return fd ;
}
/*
* SPISetup:
* Open the SPI device, and set it up, etc. in the default MODE 0
*********************************************************************************
*********************************************************************************/
int SPISetup (int channel, int speed)
{
return SPISetupMode (channel, speed, 0) ;
}
/*********************************************************************************
* Copyright: (C) 2018 wangtao
* All rights reserved.
*
* Filename: SPISet.h
* Description: This file
*
* Version: 1.0.0(11/07/2018)
* Author: WangTao
* ChangeLog: 1, Release initial version on "11/07/2018 17:15:56 PM"
*
********************************************************************************/
#ifdef __cplusplus
extern "C" {
#endif
int SPIDataRW (int channel, unsigned char *tx_data,unsigned char *rx_data, int len) ;
int SPISetupMode (int channel, int speed, int mode) ;
int SPISetup (int channel, int speed) ;
#ifdef __cplusplus
}
#endif
关于Linux下ioctl的理解推荐这篇博客 https://www.cnblogs.com/tdyizhen1314/p/4896689.html
关于spi的ioctl的理解推荐这两篇博客 https://blog.csdn.net/borntox/article/details/51871480
http://www.cnblogs.com/sankye/p/3955630.html