[imx6ull]SPI接口编程-回环测试

这篇具有很好参考价值的文章主要介绍了[imx6ull]SPI接口编程-回环测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


一、SPI是什么?

SPI 是串行外设接口(Serial Peripheral Interface)的缩写。是Motorola 公司推出的一种同步串行接口技术,是一种高速的,全双工,同步的通信总线。SPI通常由一个主设备和一个或多个从设备组成。主设备选择一个从设备进行同步通信,以完成数据的交换。SPI通信采用环形结构,至少需要4根线进行连接,包括主设备数据输入(MISO)、主设备数据输出(MOSI)、时钟信号(SCLK)和片选信号(CS)。一般情况下,主设备与从设备的连接方式如下图所示:
spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件

  • 优点:支持全双工通信、通信简单、数据传输速率快。
  • 缺点:没有指定的流控制,没有应答机制确认是否接收到数据,所以跟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的上升沿采样,在下降沿切换。

spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件

  • CPOL(时钟的极性):规定SPI 总线空闲时,时钟是高电平还是低电平。
  • CPHA(时钟的相位):规定SPI 设备是在上升沿还是下降沿触发采样数据。

通过根据需求选择适当的CPOL和CPHA配置,我们可以确保主设备和从设备在SPI通信中使用相同的模式。这些模式的选择取决于具体的硬件设备和通信要求。

数据交换:

要进行主设备与从设备之间的数据交换,首先主设备需要能够访问从设备,并通过拉低从设备的NSS(片选)引脚来进行片选操作。与其他协议不同,SPI协议是一种数据传输协议,也被称为数据交换协议。在通信过程中,主设备和从设备各自拥有一个移位寄存器,实际的数据交换如下所示:
spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件

  • 主设备将要发送的数据写入自己的移位寄存器。
  • 主设备向从设备发送时钟信号(SCLK)以同步数据传输。
  • 在每个时钟周期中,主设备的移位寄存器将一个位(bit)推送到MOSI(主设备数据输出)线上,同时从设备的移位寄存器将一个位推送到MISO(主设备数据输入)线上。
  • 数据在每个时钟周期中进行交换,直到所有的位都被传输完成。
    主设备可以通过拉高NSS引脚来结束片选操作。

通过这种方式,主设备和从设备之间的数据可以进行有效的交换和传输。

二、使能SPI驱动

在原理图与我们的40pin扩展口图可见开发板上有可以使用的一路完整的SPI1总线管脚,如下:
spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件
我们所需的还是通过修改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

spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件

重启系统后可以查看/dev下是否有spi设备节点,如果有则证明spi驱动加载成功:

spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件

三、回环测试

首先我们短接MISO和MOSI管脚如下:
spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件

我们的开发板已经安装好了spidev-test工具,我们可以–h命令查看该命令的帮助信息:
spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件
这个工具使用时候,很多选项都是由缺省值的,比如默认指定的设备是spidev1.1 ,对于回环测试我们需要知道如下几个命令:

  • -D 指定spi设备节点
  • -s 设置spi传输速率,可以测试回环测试中最大传输速度
  • -v 打开发送接收回显,用于查看详细数据发送接收情况
  • -l 直接进行回环测试
  • -p 指定发送数据

测试如下:

spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件
可见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参数常用值如下:

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参数来查看帮助信息,然后执行程序可以看我们所发送的默认信息:
spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件
也可添加参数发送我们输入的信息:
spi回环测试,IGKBoard,linux,arm开发,物联网,嵌入式硬件
可见tx和rx的buffer相同,证明数据回环发送成功。文章来源地址https://www.toymoban.com/news/detail-660309.html

到了这里,关于[imx6ull]SPI接口编程-回环测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [imx6ull]Linux下TTY-串口编程

    TTY 是Teletype或Teletypewriter的缩写,原来是指电传打字机,在以前计算机体积很大,所以用teletype这个设备来连接到计算机,后来这种设备键盘显示器取代,但是他们都作为计算机的终端设备所存在,所以TTY沿用至今,用来泛指计算机的终端设备,它作为一个子系统既支持串口,

    2023年04月08日
    浏览(36)
  • IMX6ULL + SPI LCD(驱动IC ILI9341)显示简单的QT界面

    使用正点原子的IMX6ULL Linux开发板 开发板底板原理图版本:V2.1 核心板原理图版本:V1.6 LCD :MSP2402 (IC ILI9341) 开发板上引出的引脚是在JP6上,只看JP6会发现没有可用的SPI引脚,但是查看底板原理图中与核心板相连的位置会发现其实JP6上的UART2的TX/RX/CTS/RTS 四个引脚正好可以复用

    2024年02月06日
    浏览(53)
  • iMX6ULL驱动开发 | OLED显示屏SPI驱动实现(SH1106,ssd1306)

    周日业余时间太无聊,又不喜欢玩游戏,大家的兴趣爱好都是啥?我觉得敲代码也是一种兴趣爱好。正巧手边有一块儿0.96寸的OLED显示屏,一直在吃灰,何不把玩一把?于是说干就干,最后在我的imax6ul的linux开发板上使用spi用户态驱动成功点亮。这里总结下过程,分享给有需

    2024年02月14日
    浏览(35)
  • Linux系统下imx6ull QT编程—— C++基础(一)

    学习 C++的面向对象编程,对学习 Qt 有很大的帮助 效率上,肯定是 C 语言的 scanf 和 printf 的效率高,但是没有 C++中的 cin 和 cout 使用方便。 x 可以是任意数据类型,甚至可以写成一个表达式,这比 C 语言需要指定数据类型方便多了,endl 指的是换行符,与 C 语言的“n”效果一

    2024年02月07日
    浏览(53)
  • 【IMX6ULL驱动开发学习】02.IMX6ULL烧写Linux系统

    由于我买的是正点原子的IMX6ULL阿尔法开发板,但是我是看韦东山老师视频学习的驱动 所以这里我烧录的方法是按照韦东山老师的课程来的 这里给出烧写Linux系统用到的工具 链接:https://pan.baidu.com/s/1bD-xxn3K8xQAVkJSaJmTzQ 提取码:af6w 下载解压后,可以看到烧写工具 烧写Linux系统

    2024年02月13日
    浏览(59)
  • Linux系统下imx6ull QT编程—— C++数据封装与数据抽象(八)

    封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴

    2024年02月07日
    浏览(74)
  • 【IMX6ULL驱动开发学习】08.IMX6ULL通过GPIO子系统函数点亮LED

    通过GPIO子系统函数点亮LED 1、GPIO子系统函数 1.1 确定 led 的GPIO标号,查看内核中的gpiochip 查看 gpiochip ,以正点原子的IMX6ULL阿尔法开发板为例 查看原理图,发现led接的引脚是 GPIO1_IO3,对应 /sys/kernel/debug/gpio 中的 gpiochip0 组,gpiochip0 组从0开始算起, 所以 GPIO1_IO3 对应的标号就

    2024年02月10日
    浏览(78)
  • 【IMX6ULL驱动开发学习】22.IMX6ULL开发板读取ADC(以MQ-135为例)

    IMX6ULL一共有两个ADC,每个ADC都有八个通道,但他们共用一个ADC控制器 在imx6ull.dtsi文件中已经帮我们定义好了adc1的节点部分信息 注意 num-channels = 2; ,这个表示指定使用ADC1的两个通道,即通道1和通道2 如果你要使用多个ADC通道,修改这个值即可 配置ADC引脚的 pinctrl ,在自己的

    2024年02月12日
    浏览(63)
  • 【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】

    经过以下四个步骤,终于可以开始驱动开发了 01.安装交叉编译环境【附下载地址】 02.IMX6ULL烧写Linux系统 03.设置IMX6ULL开发板与虚拟机在同一网段 04.IMX6ULL开发板与虚拟机互传文件 一、获取内核、编译内核 二、创建vscode工作区,添加内核目录和个人目录 三、了解驱动程序编写

    2024年02月06日
    浏览(52)
  • 基于IMX6ULL的智能车载终端项目(代码开源)_imx6ull 开源 linux 项目 车载终端

    1.4 车载终端:杂项功能 车载终端是一个非常复杂且常用的装置,随着电车与自动驾驶技术的不断升级迭代,需求也与日俱增!我们往往需要在终端上附加许许多多其他的功能,比如:智能家居联动、车内环境监测、天气预报与网上浏览等! 作者强调:考虑到篇幅有限,作者

    2024年04月08日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包