一、SPI是什么?
SPI 是串行外设接口(Serial Peripheral Interface)的缩写。是Motorola 公司推出的一种同步串行接口技术,是一种高速的,全双工,同步的通信总线。SPI通常由一个主设备和一个或多个从设备组成。主设备选择一个从设备进行同步通信,以完成数据的交换。SPI通信采用环形结构,至少需要4根线进行连接,包括主设备数据输入(MISO)、主设备数据输出(MOSI)、时钟信号(SCLK)和片选信号(CS)。一般情况下,主设备与从设备的连接方式如下图所示:
- 优点:支持全双工通信、通信简单、数据传输速率快。
- 缺点:没有指定的流控制,没有应答机制确认是否接收到数据,所以跟IIC 总线协议比较在数据可靠性上有一定的缺陷。
传输模式:
SPI通信涉及四种不同的模式,而不同的从设备可能在出厂时已被配置为特定的模式,无法更改。然而,为了确保主设备和从设备之间的通信一致,我们需要根据需求对主设备的SPI模式进行配置,通过控制时钟极性(CPOL)和时钟相位(CPHA)来确定SPI主设备的通信模式。以下是四种常见的SPI模式:
模式0:CPOL=0,CPHA=0。在这种模式下,串行时钟线(SCLK)在空闲状态时保持低电平。数据在SCLK的上升沿采样,在下降沿切换。
模式1:CPOL=0,CPHA=1。在这种模式下,串行时钟线(SCLK)在空闲状态时保持低电平。数据在SCLK的下降沿采样,在上升沿切换。
模式2:CPOL=1,CPHA=0。在这种模式下,串行时钟线(SCLK)在空闲状态时保持高电平。数据在SCLK的下降沿采样,在上升沿切换。
模式3:CPOL=1,CPHA=1。在这种模式下,串行时钟线(SCLK)在空闲状态时保持高电平。数据在SCLK的上升沿采样,在下降沿切换。
- CPOL(时钟的极性):规定SPI 总线空闲时,时钟是高电平还是低电平。
- CPHA(时钟的相位):规定SPI 设备是在上升沿还是下降沿触发采样数据。
通过根据需求选择适当的CPOL和CPHA配置,我们可以确保主设备和从设备在SPI通信中使用相同的模式。这些模式的选择取决于具体的硬件设备和通信要求。
数据交换:
要进行主设备与从设备之间的数据交换,首先主设备需要能够访问从设备,并通过拉低从设备的NSS(片选)引脚来进行片选操作。与其他协议不同,SPI协议是一种数据传输协议,也被称为数据交换协议。在通信过程中,主设备和从设备各自拥有一个移位寄存器,实际的数据交换如下所示:
- 主设备将要发送的数据写入自己的移位寄存器。
- 主设备向从设备发送时钟信号(SCLK)以同步数据传输。
- 在每个时钟周期中,主设备的移位寄存器将一个位(bit)推送到MOSI(主设备数据输出)线上,同时从设备的移位寄存器将一个位推送到MISO(主设备数据输入)线上。
- 数据在每个时钟周期中进行交换,直到所有的位都被传输完成。
主设备可以通过拉高NSS引脚来结束片选操作。
通过这种方式,主设备和从设备之间的数据可以进行有效的交换和传输。
二、使能SPI驱动
在原理图与我们的40pin扩展口图可见开发板上有可以使用的一路完整的SPI1总线管脚,如下:
我们所需的还是通过修改DTOverlay配置文件来让添加引脚对SPI1的使能,方法为修改eMMC启动介质的boot分区下的config.txt文件,路径为/run /media/mmcblk1p1/config.txt
修改为如下:
# Enable SPI overlay, SPI1 conflict with UART8(NB-IoT/4G module)
dtoverlay_spi1=yes
重启系统后可以查看/dev下是否有spi设备节点,如果有则证明spi驱动加载成功:
三、回环测试
首先我们短接MISO和MOSI管脚如下:
我们的开发板已经安装好了spidev-test工具,我们可以–h命令查看该命令的帮助信息:
这个工具使用时候,很多选项都是由缺省值的,比如默认指定的设备是spidev1.1 ,对于回环测试我们需要知道如下几个命令:
- -D 指定spi设备节点
- -s 设置spi传输速率,可以测试回环测试中最大传输速度
- -v 打开发送接收回显,用于查看详细数据发送接收情况
- -l 直接进行回环测试
- -p 指定发送数据
测试如下:
可见tx与rx的信息相同,回环测试成功。
四、SPI编程实现数据传输
1.SPI相关数据结构
应用程序空间需要从SPI设备传输数据的时候,每组数据元素就是struct spi_ioc_transfer结构体类型,该结构体定义如下:
//Linux内核源码: include/uapi/linux/spi/spidev.h
//应用编程头文件: /usr/include/linux/spi/spi/spidev.h
struct spi_ioc_transfer {
__u64 tx_buf; //发送数据缓存
__u64 rx_buf; //接收数据缓存
__u32 len; //数据长度
__u32 speed_hz; //通讯速率
__u16 delay_usecs; //两个spi_ioc_transfer之间的延时,微秒
__u8 bits_per_word; //数据长度
__u8 cs_change; //取消选中片选
__u8 tx_nbits; //单次数据宽度(多数据线模式)
__u8 rx_nbits; //单次数据宽度(多数据线模式)
__u8 word_delay_usecs;
__u8 pad;
/* If the contents of 'struct spi_ioc_transfer' ever change
* incompatibly, then the ioctl number (currently 0) must change;
* ioctls with constant size fields get a bit more in the way of
* error checking than ones (like this) where that field varies.
*
* NOTE: struct layout is the same in 64bit and 32bit userspace.
*/
};
程序还需要使用到ioctl函数来对SPI进行相关的配置,函数原型如下:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
其中requset参数常用值如下:文章来源:https://www.toymoban.com/news/detail-660309.html
requset参数 | 功能 |
---|---|
SPI_IOC_RD_MODE | 设置读取SPI模式 |
SPI_IOC_WR_MODE | 设置写入SPI模式 |
SPI_IOC_RD_LSB_MODE | 设置SPI读取数据模式(LSB先行返回1) |
SPI_IOC_WR_LSB_MODE | 设置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) | 一次进行双向/多次读写操作 |
2.测试程序
/*********************************************************************************
* Copyright: (C) 2023 Deng Yonghao<dengyonghao2001@163.com>
* All rights reserved.
*
* Filename: spi_loop_test.c
* Description: This file to test SPI
*
* Version: 1.0.0(2023年03月28日)
* Author: Deng Yonghao <dengyonghao2001@163.com>
* ChangeLog: 1, Release initial version on "2023年03月28日 15时17分20秒"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#define PROG_VERSION "1.0.0"
typedef struct spi_ctx_s
{
int fd;
char dev[64];
uint8_t bits;
uint16_t delay;
uint32_t mode;
uint32_t speed;
} spi_ctx_t;
//spi初始化函数
static int spi_init(spi_ctx_t *spi_ctx);
//spi发送数据
static int transfer(spi_ctx_t *spi_ctx, uint8_t const *tx, uint8_t const *rx, size_t len);
//帮助信息函数
static void program_usage(char *progname);
int main(int argc, char *argv[])
{
int ret;
spi_ctx_t spi_ctx;
char *spi_dev = "/dev/spidev0.0";//spi默认设备
uint32_t spi_speed = 500000;//默认速率500k
char *input_tx = "Hello dengyonghao2001";//默认发送
uint8_t rx_buffer[100];//接受缓存
int opt;
char *progname=NULL;
struct option long_options[] = {
{"device", required_argument, NULL, 'd'},
{"speed", required_argument, NULL, 's'},
{"print", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
progname = (char *)basename(argv[0]);
while((opt = getopt_long(argc, argv, "d:s:p:h", long_options, NULL)) != -1)
{
switch(opt)
{
case 'd':
spi_dev = optarg;
break;
case 's':
spi_speed = atoi(optarg);
break;
case 'p':
input_tx = optarg;
break;
case 'h':
program_usage(progname);
return 0;
default:
break;
}
}
if(0 == spi_speed || !input_tx)
{
program_usage(progname);
return -1;
}
memset(&spi_ctx, 0, sizeof(spi_ctx));
strncpy(spi_ctx.dev, spi_dev, sizeof(spi_ctx.dev));
spi_ctx.bits = 8;//设置字长8bit
spi_ctx.delay = 100;//设置时延100us
spi_ctx.mode = SPI_MODE_2;//设置spi模式
spi_ctx.speed = spi_speed;//设置速率
//spi设备初始化
if(spi_init(&spi_ctx) < 0)
{
printf("spi_init error\n");
return -1;
}
printf("spi [dev %s] [fd = %d] init successfully\n", spi_ctx.dev, spi_ctx.fd);
//spi发送接受函数
if(transfer(&spi_ctx, input_tx, rx_buffer, strlen(input_tx)) < 0)
{
printf("spi transfer error\n");
return -2;
}
//打印tx_buffer和rx_buffer
printf("tx_buffer: | %s |\n", input_tx);
printf("rx_buffer: | %s |\n", rx_buffer);
return 0;
}
static void program_usage(char *progname)
{
printf("Usage: %s [OPTION]...\n", progname);
printf(" %s is a program to test IGKBoard loop spi\n", progname);
printf("\nMandatory arguments to long options are mandatory for short options too:\n");
printf(" -d[device ] Specify SPI device, such as: /dev/spidev0.0\n");
printf(" -s[speed ] max speed (Hz), such as: -s 500000\n");
printf(" -p[print ] Send data (such as: -p 1234/xde/xad)\n");
printf("\n%s version %s\n", progname, PROG_VERSION);
return;
}
static int transfer(spi_ctx_t *spi_ctx, uint8_t const *tx, uint8_t const *rx, size_t len)
{
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long )tx,
.rx_buf = (unsigned long )rx,
.len = len,
.delay_usecs = spi_ctx->delay,
.speed_hz = spi_ctx->speed,
.bits_per_word = spi_ctx->bits,
};
//发送并接收一组数据
if(ioctl(spi_ctx->fd, SPI_IOC_MESSAGE(1), &tr) < 0)
{
printf("ERROR: SPI transfer failure: %s\n", strerror(errno));
return -1;
}
return 0;
}
int spi_init(spi_ctx_t *spi_ctx)
{
int ret;
spi_ctx->fd = open(spi_ctx->dev, O_RDWR);
if(spi_ctx->fd < 0)
{
printf("open %s error\n", spi_ctx->dev);
return -1;
}
//设置SPI 接收和发送的工作模式
ret = ioctl(spi_ctx->fd, SPI_IOC_RD_MODE, &spi_ctx->mode);
if( ret < 0 )
{
printf("ERROR: SPI set SPI_IOC_RD_MODE [0x%x] failure: %s\n ", spi_ctx->mode, strerror(errno));
goto fd_close;
}
ret = ioctl(spi_ctx->fd, SPI_IOC_WR_MODE, &spi_ctx->mode);
if( ret < 0 )
{
printf("ERROR: SPI set SPI_IOC_WR_MODE [0x%x] failure: %s\n ", spi_ctx->mode, strerror(errno));
goto fd_close;
}
//设置SPI通信接收和发送的字长
ret = ioctl(spi_ctx->fd, SPI_IOC_RD_BITS_PER_WORD, &spi_ctx->bits);
if( ret < 0 )
{
printf("ERROR: SPI set SPI_IOC_RD_BITS_PER_WORD [%d] failure: %s\n ", spi_ctx->bits, strerror(errno));
goto fd_close;
}
ret = ioctl(spi_ctx->fd, SPI_IOC_WR_BITS_PER_WORD, &spi_ctx->bits);
if( ret < 0 )
{
printf("ERROR: SPI set SPI_IOC_WR_BITS_PER_WORD [%d] failure: %s\n ", spi_ctx->bits, strerror(errno));
goto fd_close;
}
//设置SPI最高工作频率
ret = ioctl(spi_ctx->fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_ctx->speed);
if( ret == -1)
{
printf("ERROR: SPI set SPI_IOC_WR_MAX_SPEED_HZ [%d] failure: %s\n ", spi_ctx->speed, strerror(errno));
goto fd_close;
}
ret = ioctl(spi_ctx->fd, SPI_IOC_RD_MAX_SPEED_HZ, &spi_ctx->speed);
if( ret == -1)
{
printf("ERROR: SPI set SPI_IOC_RD_MAX_SPEED_HZ [%d] failure: %s\n ", spi_ctx->speed, strerror(errno));
goto fd_close;
}
printf("spi mode: 0x%x\n", spi_ctx->mode);
printf("bits per word: %d\n", spi_ctx->bits);
printf("max speed: %d Hz (%d KHz)\n", spi_ctx->speed, spi_ctx->speed / 1000);
return spi_ctx->fd;
fd_close:
close(spi_ctx->fd);
return -1;
}
3.Makefile
CC=arm-linux-gnueabihf-gcc
APP_NAME=spi_loop_test
all:clean
@${CC} ${APP_NAME}.c -o ${APP_NAME}
clean:
@rm -f ${APP_NAME}
install:
@rm -f ../tftpboot/${APP_NAME}
@cp ${APP_NAME} ../tftpboot/
4.运行测试
首先我们把程序从我们的虚拟机下载到我们的开发板上,赋予可执行权限后后可以通过-h参数来查看帮助信息,然后执行程序可以看我们所发送的默认信息:
也可添加参数发送我们输入的信息:
可见tx和rx的buffer相同,证明数据回环发送成功。文章来源地址https://www.toymoban.com/news/detail-660309.html
到了这里,关于[imx6ull]SPI接口编程-回环测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!