浅浅的 linux开发板 驱动的使用 SPI IIC IO

发布于:2022-12-27 ⋅ 阅读:(537) ⋅ 点赞:(0)

零、参考

open()

Linux中open命令实现原理以及源码分析
在一个进程中使用open()来获取一个文件描述符fd,然后通过该fd去进行一些write()、read()操作。
open()的原理是通过给定的文件路径/dev/hello,从而找到该文件路径所对应的inode信息,最后生成一个struct file结构体,该结构体在进程的打开文件列表中,返回的fd信息就是这个打开文件列表中的下标索引,所以说fd永远不会小于0。

ioctrl()

linux驱动—ioctl函数解析
linux ioctl()详解
按Linux内核的约定方法为驱动程序选择ioctl,在驱动程序中,ioctl()函数上传送的变量cmd是应用程序用于区别设备驱动程序请求处理内容的值。一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。所以,ioctl函数里面都实现了多个的对硬件的操作,通过应用层传入的命令来调用相应的操作。

read() write()

深入理解linux下write()和read()函数
write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。驱动设备在linux中相当于是文件的存在
read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。
··············································································································································································

一、SPI驱动

参考

树莓派之SPI编程
Linux SPI 应用编程

1、打开SPI驱动设备

使用 fd_ = open(“/dev/spidev0.0”, O_RDWR);的方式打开设备;
设备的驱动接口文件在 /dev 目录下面;
例如:

/dev/spidevx.y x是SPI总线号,即一组SCLK、MOSI、MISO
y是SPI设备号,同一条总线上用不同的片选信号区分:CE0、CE1等 对于树莓派,启用SPI功能后,有一条总线,两个设备:
/dev/spidev0.0 /dev/spidev0.1

实例:使用open打开SPI0的片选0:

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#include "stdio.h"

int main (int argc, char *argv[])
{
    int fd_;
    fd_ = open("/dev/spidev0.0", O_RDWR);
    if (fd_ < 0) {
      printf("Error");
    } 
    else
    {
      printf("successful");
    }
    return 0;
} 

其中open中的第三个参数:

参数 作用
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读、写打开
O_APPEND 每次写时都加到文件的尾端
O_CREAT 若此文件不存在则创建它。使用此选择项时,需同时说明第三个参数mode,用其说明该新文件的存取许可权位。
O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。
O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。
O_NOCTTY 如果p a t h n a m e指的是终端设备,则不将此设备分配作为此进程的控制终端。
O_NONBLOCK 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。
O_SYNC 使每次w r i t e都等到物理I / O操作完成。

这些控制字都是通过“或”符号分开(|)

参考

open(/dev/ietctl, O_RDWR) 参数含义

2、控制SPI外设

参考:
Linux SPI 应用编程
********用户态SPI编程

用户空间设备操作ioctl相关头文件,控制SPI外设的具体参数
SPI设置:
1、时钟电平(CPOL)和采用阶段(CPHA)
2、时钟频率
3、片选电平
4、是否有片选信号
5、数据位数(通常为8bit)
6、数据位传输顺序(MSB或LSB)
7、3线或4线
初始化一个SPI设备如下:

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#include "stdio.h"

int main (int argc, char *argv[])
{
  int ret = 0;
  int spifd;
  int bits = 8;
  int speed = 2000000;
  int mode = 0;

	spifd = open("/dev/spidev0.0", O_RDWR);
	if (spifd < 0)
    printf("can't open device");

  /*
	 * spi的工作模式
	 * 设置SPI总线极性及相位是使用SPI_IOC_WR_MODE命令实现
	 * 设置SPI总线的极性和相位
	 * mode的可选值为:SPI_MODE_0、SPI_MODE_1、SPI_MODE_2、SPI_MODE_3
	 */
	ret = ioctl(spifd, SPI_IOC_WR_MODE, &mode); // 
	if (ret == -1)
		printf("can't set spi mode");
 
	ret = ioctl(spifd, SPI_IOC_RD_MODE, &mode);
	if (ret == -1)
		printf("can't get spi mode");
 
	/*
	 * bits per word
	 */
	ret = ioctl(spifd, SPI_IOC_WR_BITS_PER_WORD, &bits);
	if (ret == -1)
		printf("can't set bits per word");
 
	ret = ioctl(spifd, SPI_IOC_RD_BITS_PER_WORD, &bits);
	if (ret == -1)
		printf("can't get bits per word");
 
	/*
	 * max speed hz
	 */
	ret = ioctl(spifd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		printf("can't set max speed hz");
 
	ret = ioctl(spifd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		printf("can't get max speed hz");

  	printf("end\n");
 	return 0;
} 

SPI工作模式 设置总线极性和相位

关于 SPI_IOC_WR_MODE 和 SPI_IOC_RD_MODE 参数,主要是用于设置SPI总线的极性和相位。
在 ioctl(spifd, SPI_IOC_WR_MODE, mode) 的输入中,modo参数有固定的数值:

命令 SPI_IOC_WR_MODE
调用方式 ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
功能描述 设置SPI总线的极性和相位
输入参数说明 mode的可选值为:SPI_MODE_0、SPI_MODE_1、SPI_MODE_2、SPI_MODE_3,这些值的说明参考下面内容。
返回值说明 0:设置成功 1:设置不成功

SPI 设置每字的数据位长度

设置SPI总线上每字的数据位长度是使用SPI_IOC_WR_BITS_PER_WORD命令实现:

命令 SPI_IOC_WR_BITS_PER_WORD
调用方式 ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
功能描述 设置SPI总线上每
字的数据位长度
输入参数说明 bits为每字的二制位数,取值
返回值说明 0为成功,其它值为失败

设置最大总线速率

设置SPI总线的最大速率是通过使用SPI_IOC_WR_MAX_SPEED_HZ命令实现

命令 SPI_IOC_WR_MAX_SPEED_HZ
调用方式 ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
功能描述 设置SPI总线的最大速率
输入参数说明 speed为需要设置的SPI总线的最大频率,单位为Hz
返回值说明 恒为0:设置成功

3、数据传输

参考

嵌入式linux中SPI应用开发
嵌入式linux应用读写spi简单示例

在SPI总线实现数据收/发是使用SPI_IOC_MESSAGE(n)命令实现

命令 SPI_IOC_MESSAGE(n)
调用方式 ret = ioctl(fd, SPI_IOC_MESSAGE(n), &tr);
功能描述 实现在SPI总线接收/发送数据操作,其中n的值可变
输入/输出参数说明 struct spi_ioc_transfer结构体用于封装要收/发的数据。tr参数指定向struct spi_ioc_transfer结构体的数组,数组长度为n。
返回值说明 0:操作成功 1:操作失败

其中第三个参数 tr 参数指定向 struct spi_ioc_transfer 结构体的数组具体的定义内容如下:

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; /* 片选信号 */
	 __u32 pad;
}

注意,这里传入的tr参数是结构体数组指针。使用SPI_IOC_MESSAGE(n)命令收/发的数据都需要使用struct spi_ioc_transfer结构体封装

bool SpiTransferData(const uint8_t* _tx_buffer, uint8_t* _rx_buffer, int _tx_len) 
{
    struct spi_ioc_transfer spi_message[1]; // 创建一个spi_ioc_transfer 结构体数组
    //memset(spi_message, 0, 1 * sizeof(struct spi_ioc_transfer)); // 初始化清零

    for (int i = 0; i < 1; i++) { // 对结构体数组初始化
        spi_message[i].bits_per_word = 8;             // 数据位长度
        spi_message[i].cs_change = 1;                 // 片选位置
        spi_message[i].delay_usecs = 0;               // 延时时间
        spi_message[i].len = _tx_len;                 // 数据长度
        spi_message[i].rx_buf = (uint64_t)_rx_buffer; // 数组buffer指针
        spi_message[i].tx_buf = (uint64_t)_tx_buffer; // 数组buffer指针
    }
    int rv = ioctl(fd_, SPI_IOC_MESSAGE(1), &spi_message);
    if (rv < 0) return false;
    return true;
}

最终可以得到测试程序如下:

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#include <stdio.h>
#include <string.h>

typedef unsigned char uint8_t;
typedef long int      uint64_t;

bool SpiTransferData(int &fd_, const uint8_t * _tx_buffer, uint8_t * _rx_buffer, int _tx_len) 
{
    struct spi_ioc_transfer spi_message[1]; // 创建一个spi_ioc_transfer 结构体数组
    memset(spi_message, 0, 1 * sizeof(struct spi_ioc_transfer)); // 初始化清零

    for (int i = 0; i < 1; i++) { // 对结构体数组初始化
        spi_message[i].bits_per_word = 8;             // 数据位长度
        spi_message[i].cs_change = 1;                 // 片选位置
        spi_message[i].delay_usecs = 0;               // 延时时间
        spi_message[i].len = _tx_len;                 // 数据长度
        spi_message[i].rx_buf = (uint64_t)_rx_buffer; // 数组buffer指针
        spi_message[i].tx_buf = (uint64_t)_tx_buffer; // 数组buffer指针
    }
    int rv = ioctl(fd_, SPI_IOC_MESSAGE(1), &spi_message);
    if (rv < 0) return false;
    return true;
}

int main (int argc, char *argv[])
{
  int ret = 0;
  int spifd;
  int bits = 8;
  int speed = 2000000;
  int mode = 0;

  uint8_t * _tx_buffer;
  uint8_t * _rx_buffer;
  int _tx_len;

	spifd = open("/dev/spidev0.0", O_RDWR);
	if (spifd < 0)
    printf("can't open device");

  /*
	 * spi mode
	 */
	ret = ioctl(spifd, SPI_IOC_WR_MODE, &mode);
	if (ret == -1)
		printf("can't set spi mode");
 
	ret = ioctl(spifd, SPI_IOC_RD_MODE, &mode);
	if (ret == -1)
		printf("can't get spi mode");
 
	/*
	 * bits per word
	 */
	ret = ioctl(spifd, SPI_IOC_WR_BITS_PER_WORD, &bits);
	if (ret == -1)
		printf("can't set bits per word");
 
	ret = ioctl(spifd, SPI_IOC_RD_BITS_PER_WORD, &bits);
	if (ret == -1)
		printf("can't get bits per word");
 
	/*
	 * max speed hz
	 */
	ret = ioctl(spifd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		printf("can't set max speed hz");
 
	ret = ioctl(spifd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		printf("can't get max speed hz");

  ret = SpiTransferData(spifd, _tx_buffer, _rx_buffer, _tx_len);
  if (ret == -1)
		printf("SpiTransferData error!");

  printf("end\n");
  return 0;
} 

二、IIC

参考

Linux I2C设备读写应用程序
IIC原理及Linux应用空间IIC编程

1、打开一个IIC设备

linux下一切皆文件,I2C设备也是一个文件:

int fd = open("/dev/i2c-0", O_RDWR);
if(fd < 0){
	printf("i2c-0 device open failed/n");
	return (-1);
}

其中O_RDWR参数本文上面的SPI操作,使用读、写打开的方式打开IIC驱动设备;i2c-0 后面的 0 代表了0号I2C设备。
IIC的具体参数配置,比如IIC的速度等配置需要修改其他文件。

2、IIC数据传输

可以使用ioctrl的函数接口来传输数据,也可以使用read和write的接口来传输数据;

ioctl同read和write的区别是:
1、ioctl一般是用来传递控制参数的,比如:串口的波特率、串口的流控方法(xon/xoff、DTR/DSR、RTS/CTS)等等,一般不
用来传递“主要的”数据(我不到合适的词来说明:)。
2、ioctl的语义一般是非阻塞的,read和write却省是阻塞的。
3、ioctl的接口是万能的,ioctl(fd, cmd, arg)第三个参数可以是一个整形变量,也可以是一个指向某种数据结构的指针。

在应用上通过ioctl来读写i2c设备需要使用两个结构体 i2c_msg、 i2c_rdwr_ioctl_data

i2c_msg

/*
 * I2C Message - used for pure i2c transaction, also from /dev interface
 */
struct i2c_msg {
	__u16 addr;	/* slave address			*/
	unsigned short flags;
#define I2C_M_TEN	0x10	/* we have a ten bit chip address	*/
#define I2C_M_RD	0x01
#define I2C_M_NOSTART	0x4000
#define I2C_M_REV_DIR_ADDR	0x2000
#define I2C_M_IGNORE_NAK	0x1000
#define I2C_M_NO_RD_ACK		0x0800
	short len;		/* msg length				*/
	char *buf;		/* pointer to msg data			*/
};

参考:

i2c_msg一些标志的解释

标志 作用
I2C_M_IGNORE_NAK 设置这个标志意味当前i2c_msg忽略I2C器件的ack和nack信号。
I2C_M_NOSTART 设置这个标志意味当前i2c_msg不发送start信号。注意,其实调用bit_xfer的一开始就已经发了start信号了(程序第10行),这个标记无非就是标志是否发送地址第18行。其次,如果一个i2c_msg没有定义I2C_M_NOSTART而且又不是msgs序列里的第一个i2c_msg,则回发送重复start信号,我想这就是这个标志起这个名的原因。
I2C_M_NO_RD_ACK 这个标识表示在正行读操作时不去ACK,我不知道其它芯片如果,如果是AT24C04则一定不能设这个标志位了。(下面三个标志为均为bit_doAddress函数使用,结合上面的说明,也就是这时I2C_M_NOSTART一定没有设置。)
I2C_M_RD 表示这是一个读操作,默认是把相应的位置1
I2C_M_REV_DIR_ADDR 表示把读写标志位反转,也就是读是把相应位置0
I2C_M_TEN 表示这个器件的器件地址是10Bit的。一定要搞清,这是器件地址,不是指EEPROM的ROM地址。24C02等芯片真正的器件地址只有4位永远有效(0xA),低4位用来放其它东西了(根据容量有可能是器件地址的低3位,或ROM地址的高3位)。也是说,无论什么容量,这类器件的地址只是器件地址我们只选7位模式(内核只区分10位模式和其它模式)

I2C_M_NOSTART:
设置这个标志意味当前i2c_msg不发送start信号。注意,其实调用bit_xfer的一开始就已经发了start信号了(程序第10行),这个标记无非就是标志是否发送地址第18行。其次,如果一个i2c_msg没有定义I2C_M_NOSTART而且又不是msgs序列里的第一个i2c_msg,则回发送重复start信号,我想这就是这个标志起这个名的原因。我们可以猜想,
1.msgs序列第一个数据必须是地址,同时必须不定义这个标志位
2.在进行读数据,要从写操作转变为读操作时,会发重复start信号和器件地址时,必须不定义这个标志位
3.其它情况下一的i2c_msg必须定义这个标志
以上只是我看完这个函数的理解,不一定正确。同时1和2总结下来就是发器件地址(注意,是器件地址,不是像EEPROM那样的EEPROM地址,这个地址是当数据发的)时会不设置I2C_M_NOSTART, 发数据时就设置I2C_M_NOSTART这个标志。

i2c_rdwr_ioctl_data

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
	struct i2c_msg *msgs;	/* pointers to i2c_msgs */
	__u32 nmsgs;			/* number of i2c_msgs */
};

测试程序

#include<stdio.h>
#include<linux/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ioctl.h>
#include<string.h>
#include<linux/i2c.h>
#include<linux/i2c-dev.h>
 
#define MAX_BYTES            2 
#define DEFAULT_I2C_BUS      "/dev/i2c-0"
 
int i2c_write(int fd, unsigned int addr, unsigned int offset, unsigned char *buf, unsigned int len)
{
	struct i2c_rdwr_ioctl_data msg_rdwr;
	struct i2c_msg i2cmsg;
	int i;
	unsigned char _buf[MAX_BYTES+1];
 
	if(len>MAX_BYTES)
		return -1;
	
 
	if(offset+len>256)
		return -1;
	
 
	_buf[0]=offset;
	for(i=0;i<len;i++)
	{
	    _buf[1+i]=buf[i];
	    printf("----_writedata:%x------\n",_buf[1+i]);
	}
 
	msg_rdwr.msgs = &i2cmsg;
	msg_rdwr.nmsgs = 1;
 
	i2cmsg.addr  = addr;
	i2cmsg.flags = 0; //write
	i2cmsg.len   = 1+len;
	i2cmsg.buf   = _buf;
 
	if((i=ioctl(fd,I2C_RDWR,&msg_rdwr))<0){
		perror("ioctl()");
		fprintf(stderr,"ioctl returned %d\n",i);
		return -1;
	}
 
	return 0;
}
 
int i2c_read(int fd, unsigned int addr, unsigned int offset, unsigned char *buf, unsigned int len)
{
	struct i2c_rdwr_ioctl_data msg_rdwr;
	struct i2c_msg i2cmsg;
	int i;
 
	if(len>MAX_BYTES)
		return -1;
 
	if(i2c_write(fd,addr,offset,NULL,0)<0)
		return -1;
	
	msg_rdwr.msgs = &i2cmsg;
	msg_rdwr.nmsgs = 1;
	
	i2cmsg.addr  = addr;
	i2cmsg.flags = I2C_M_RD;
	i2cmsg.len   = len;
	i2cmsg.buf   = buf;
 
	if((i=ioctl(fd,I2C_RDWR,&msg_rdwr))<0){
	    perror("ioctl()");
	    fprintf(stderr,"ioctl returned %d\n",i);
	    return -1;
	}
	return 0;
}
 
int main(int argc, char** argv)
{
	printf("----start---------\n");
	
	int fd =open(DEFAULT_I2C_BUS, O_RDWR);
 
	if (fd< 0) 
	{
		printf("open failed\n");
		return -1;
	}
 	printf("open end \n");

	unsigned int addr = 0x36;
	unsigned int offset = 0x0C;
	unsigned char writebuf[2]={0x26,0x52};
	unsigned char readbuf[2];
	unsigned int len = 2;
	
	i2c_read(fd,addr,offset,readbuf,len);
	printf("----i2c_read--write before--buff:%x-----\n",readbuf[0]);
	printf("----i2c_read--write before--buff:%x-----\n",readbuf[1]);
	
	memset(readbuf,0,sizeof(readbuf));
 
	i2c_write(fd,addr,offset,writebuf,len);
	
	i2c_read(fd,addr,offset,readbuf,len);
	printf("----i2c_read--write after--buff:%x-----\n",readbuf[0]);
	printf("----i2c_read--write after--buff:%x-----\n",readbuf[1]);
	printf("----end---------\n");
	close(fd);
	return 0;
}	

参考

linux中i2c的ioctl,write,read函数的使用

使用write和read的方法:
Linux I2C设备读写应用程序


网站公告

今日签到

点亮在社区的每一天
去签到