Linux中open命令实现原理以及源码分析
在一个进程中使用open()来获取一个文件描述符fd,然后通过该fd去进行一些write()、read()操作。
open()的原理是通过给定的文件路径/dev/hello,从而找到该文件路径所对应的inode信息,最后生成一个struct file结构体,该结构体在进程的打开文件列表中,返回的fd信息就是这个打开文件列表中的下标索引,所以说fd永远不会小于0。
linux驱动—ioctl函数解析
linux ioctl()详解
按Linux内核的约定方法为驱动程序选择ioctl,在驱动程序中,ioctl()函数上传送的变量cmd是应用程序用于区别设备驱动程序请求处理内容的值。一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。所以,ioctl函数里面都实现了多个的对硬件的操作,通过应用层传入的命令来调用相应的操作。
深入理解linux下write()和read()函数
write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。驱动设备在linux中相当于是文件的存在
read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。
··············································································································································································
使用 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
#include
#include
#include
#include
#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) 参数含义
参考:
Linux SPI 应用编程
********用户态SPI编程
用户空间设备操作ioctl相关头文件,控制SPI外设的具体参数
SPI设置:
1、时钟电平(CPOL)和采用阶段(CPHA)
2、时钟频率
3、片选电平
4、是否有片选信号
5、数据位数(通常为8bit)
6、数据位传输顺序(MSB或LSB)
7、3线或4线
初始化一个SPI设备如下:
#include
#include
#include
#include
#include
#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_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_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:设置成功 |
嵌入式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
#include
#include
#include
#include
#include
#include
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;
}
Linux I2C设备读写应用程序
IIC原理及Linux应用空间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的速度等配置需要修改其他文件。
可以使用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 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_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这个标志。
/* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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设备读写应用程序