1 SPI通讯协议简介
SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设 备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、 LCD 等设备与 MCU 间, 要求通讯速率较高的场合学习本章时,可与 I2C 章节对比阅读,体会两种通讯总线的差异。 下面我们分别对 SPI 协议的物理层及协议层进行讲解。
SPI通讯设备之间的常用连接方式见下图。
SPI通讯使用3条总线及片选线,3条总线分别为SCK、MOSI、MISO,片选线为SS* ,它们的作用介绍如下:
片选线(Slave Select):从设备选择信号线,常称为片选信号线, 也称为NSS、CS,以下用NSS表示。SPI通讯以NSS线置低电平为开始信号,以NSS线被拉高作为结束信号。
SCK(Serial Clock):时钟信号线,用于通讯数据同步,它由通讯主机产生。
MOSI(Master Output,Slave Input):主设备输出/从设备输入引脚。数据方向为主机到从机。
MISO(Master Input,Slave Output):主设备输入/从设备输出引脚。数据方向为从机到主机。
SPI分成了四种模式,见下表, 主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式0”与“模式3”。
SPI模式 | CPOL | CPHA | 空闲时SCK时钟 | 采样时刻 |
---|
0 | 0 | 0 | 低电平 | 奇数边沿 | 1 | 0 | 1 | 低电平 | 偶数边沿 | 2 | 1 | 0 | 高电平 | 奇数边沿 | 3 | 1 | 1 | 高电平 | 偶数边沿 |
2 相关函数
编写应用程序需要使用到spi_ioc_transfer结构体,如下所示
linux/spi/spidev.h
struct spi_ioc_transfer {
__u64 tx_buf;
__u64 rx_buf;
__u32 len;
__u32 speed_hz;
__u16 delay_usecs;
__u8 bits_per_word;
__u8 cs_change;
__u8 tx_nbits;
__u8 rx_nbits;
__u16 pad;
};
在编写应用程序时还需要使用ioctl函数设置spi相关配置,其函数原型如下
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
其中对于终端request的值常用的有以下几种
SPI_IOC_RD_MODE | 设置读取SPI模式 |
---|
SPI_IOC_WR_MODE | 设置写入SPI模式 | SPI_IOC_RD_LSB_FIRST | 设置SPI读取数据模式(LSB先行返回1) | SPI_IOC_WR_LSB_FIRST | 设置SPI写入数据模式。(0:MSB,非0:LSB) | SPI_IOC_RD_BITS_PER_WORD | 设置SPI读取设备的字长 | SPI_IOC_WR_BITS_PER_WORD | 设置SPI写入设备的字长 | SPI_IOC_RD_MAX_SPEED_HZ | 设置读取SPI设备的最大通信频率 | SPI_IOC_WR_MAX_SPEED_HZ | 设置写入SPI设备的最大通信速率 | SPI_IOC_MESSAGE(N) | 一次进行双向/多次读写操作 |
其中SPI的读写和写入可以设置为不同的参数。
3 SPI-OLED完整代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#define spi3_oled_DEV_path "/dev/spidev2.0"
static unsigned char tx_buffer[100] = "hello the world !";
static unsigned char rx_buffer[100];
static int fd;
static uint32_t mode = SPI_MODE_2;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;
static int spi_init(void);
static int transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len);
int main(int argc,char * argv[])
{
int ret;
ret = spi_init();
if( -1 == ret )
{
printf("spi_init error\n");
exit(-1);
}
ret = transfer(fd, tx_buffer, rx_buffer, sizeof(tx_buffer));
if (-1 == ret )
{
printf("transfer error...\n");
}
printf("tx_buffer: \n %s\n ", tx_buffer);
printf("rx_buffer: \n %s\n ", rx_buffer);
return 0;
}
int transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
int ret;
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long )tx,
.rx_buf = (unsigned long )rx,
.len = len,
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word =bits,
};
ret = ioctl(fd,SPI_IOC_MESSAGE(1),&tr);
if( ret == -1 )
{
return -1;
}
return 0;
}
int spi_init(void)
{
int ret;
fd = open(spi3_oled_DEV_path,O_RDWR);
if(fd < 0)
{
perror("/dev/spidev2.0");
return -1;
}
ret = ioctl(fd,SPI_IOC_RD_MODE,&mode);
if( ret == -1)
{
printf("SPI_IOC_RD_MODE error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_WR_MODE,&mode);
if( ret == -1)
{
printf("SPI_IOC_WR_MODE error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_RD_BITS_PER_WORD,&bits);
if( ret == -1)
{
printf("SPI_IOC_RD_BITS_PER_WORD error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_WR_BITS_PER_WORD,&bits);
if( ret == -1)
{
printf("SPI_IOC_WR_BITS_PER_WORD error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ,&speed);
if( ret == -1)
{
printf("SPI_IOC_WR_MAX_SPEED_HZ error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ,&speed);
if( ret == -1)
{
printf("SPI_IOC_RD_MAX_SPEED_HZ error......\n ");
goto fd_close;
}
printf("spi mode: 0x%x\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed / 1000);
return 0;
fd_close:
close(fd);
return -1;
}
|