i o 设备在linux中被当作啥文件,【SoC FPGA学习】十四、基于 Linux 标准文件 I/O 的设备读写...

ssize_t write(int fd, const void *buf, size_t count);

操作成功,返回实际写入的字节数,出错则返回-1, 同时设置全局变量 errno 报告具体错误的原因,比如 errno=ENOSPC 表示磁盘满了。

参数 fd 是打开文件的描述符, buf 是数据缓冲区, 存放着准备写入文件的数据, count 是请求写入的字节数。实际写入的字节数可以小于请求写的字节数。 例如, 使用 write 函数向已经打开的 FPGA 侧添加的 I2C 控制器中写入 8个字节数据的代码如下所示:

for (i = 0; i < 8; i++) //初始化要写入的数据: 0、 1„„7

tx_buf[i] = i;

len = write(fd, tx_buf, 8); //向 I2C 设备写入 8 个字节数据

if (len < 0) {

printf("write data faile \n");

return -1;

}

写完之后,需要检查返回值是否小于 0, 如果小于 0 则表明写入失败。

2.4、读取设备数据(read)

从打开的文件读取数据,可调用 read()函数实现。 read()函数原型在中定义:

ssize_t read(int fd, void *buf, size_t count);

操作成功,返回实际读取的字节数,如果已到达文件结尾,返回 0,否则返回-1 表示出错,同时设置全局变量 errno 报告具体错误的原因。实际读取的字节数,可以小于请求的字节数 count,比如下面两种情况:文件长度小于请求的长度,即还没达到请求的字节数时,就已到达文件结尾。如果文件是 50 字节长,而 read 请求读 100 字节( count=100),则首次调用 read 时,它返回 50,紧接着的下次调用,它返回 0,表示已到达文件结尾;

读设备文件时,有些设备每次返回的数据长度小于请求的字节数,如终端设备一般按行返回,即每读到一行数据就返回。

参数 fd 是调用 open()时返回的文件描述符, buf 是用来接收所读数据的缓冲区, count 是请求读取的字节数。

例如, 使用 read 函数从已经打开 FPGA 侧添加的 I2C 控制器中读取 8 个字节数据的代码如下所示:

char rx_buf[8]; /* 用于储存接收数据 */

len = read(fd, rx_buf, 8); /* 在设置的数据地址连续读入数据 */

if (len < 0) {

printf("read data faile \n");

return -1;

注意, 读取数据完成后,需要检查实际读取到的数据大小,即 read 函数的返回值, 如果小于 0 则表明读取失败。

2.5、杂项操作(ioctl)

文件 I/O 操作还有很多不好归到 read()/write()的,只好放到这个函数中。尤其是设备文件, 比如修改设备寄存器的值等, read()/write()函数会比较的麻烦,因此通过 ioctl()函数提供设备特有的操作。 ioctl()是文件 I/O 的杂项函数,其函数原型在中定义:

int ioctl(int fd, int cmd, …);

一般情况下, 操作成功返回 0,失败返回-1, 由 errno 报告具体错误原因。但有的设备文件可能会返回一个正值表示输出参数,其含义取决于具体的设备文件。

参数 fd 是打开文件的描述符,参数 cmd 是文件的操作命令,这个参数的取值还决定后面的参数含义,“„”表示从参数是可选的、类型不确定的。ioctl()的 cmd 操作命令是文件专有的,不同的文件, cmd 往往是不同的,没有共用性,比如嵌入式系统中的设备文件, SPI 和 I2C 所支持的 ioctl()操作命令就不同。对于 SPI 设备,支持使用 ioctl()来设置 SPI 的工作模式。而I2C 则不支持该命令。 同时, I2C 设备支持通过 ioctl()指定 I2C 总线上的从设备地址,而 SPI 则没有此命令。 以下为使用 ioctl()函数指定 SPI 工作模式和使用ioctl()函数设定 I2C 总线上从设备地址的代码:

//设置 SPI 工作在模式 0

ret = ioctl(fd_spi, SPI_IOC_WR_MODE, 0);

//设置 I2C 从设备地址为 0x78

ret = ioctl(fd_i2c, I2C_SLAVE, 0x78 >> 1);

ioctl()函数在设备文件中应用非常的广泛,很多情况下,使用 ioctl()函数来操作硬件设备, 会比直接使用 write()或 read()函数拥有更高的效率。

2.6、关闭设备

文件 I/O 操作完成后,应该调用 close()关闭打开的文件,释放打开文件时所占用的系统资源。 close()函数原型在文件中定义:

int close(int fd);

如果文件顺利关闭,返回 0,否则返回-1, 同时设置全局变量 errno 报告具体错误的原因。参数 fd 是打开文件时调用 open()或 creat()函数返回的文件描述符。

2.7、其他操作

关于文件 I/0,还有其他的一些常用函数,例如 fsync()、 fseek()。 在设备文件中,由于这两个函数不如前面讲的 5 个用的普遍,因此这里不做介绍。但仍然需要说明的是,这两个函数虽然针对设备文件使用的不是很多,但是在普通文件的应用中,却有重要的功能价值。

三、使用文件 IO 实现 I2C 编程

使用文件 I/O 来编程使用 I2C 控制器, 就比使用基于虚拟地址映射的方式要简单方便许多了。不仅如此,由于基于文件 I/O 的操作使用的是 Linux 内核驱动的方式来获取 I2C 控制器中的寄存器数据的,而在 Linux 内核中, 可以很方便的注册中断,以使用中断的方式来完成数据的收发,因此能够提高程序的运行效率。

在基于虚拟地址映射的 Linux 硬件编程中, 我们介绍了如何自己编写驱动程序,使用 I2C 控制器映射到 linux 用户空间的虚拟地址来完成数据的传输。 本节实验将使用文件 I/O 的方式,实现通过 I2C 控制器完成 EEPROM 存储器读写的功能。

对于 I2C 总线上的每一个设备,都有一个器件地址,当访问该设备时必须先指定其器件地址, I2C 设备驱动提供了一个 ioctl 命令用来指定需要操作的设备地址, 该命令的 16 进制值为 0x0703,使用时,仅需在 ioctl 函数中传入该命令即可设置从设备地址, 代码如下所示:

#define I2C_SLAVE 0x0703

#define I2C_ADDR 0xA2

ret = ioctl(fd, I2C_SLAVE, I2C_ADDR >> 1); /* 设置从机地址 */

学习代码如下:

#include

#include

#include

#include

#include

#include

#include

#include

#define I2C_SLAVE 0x0703

#define I2C_TENBIT 0x0704

#define I2C_ADDR 0xA0

#define DATA_LEN 8

#define I2C_DEV_NAME "/dev/i2c-2"

int main(int arg, char*args[]) {

int ret, len;

int i, flag = 0;

int fd;

char tx_buf[DATA_LEN + 1]; /* 用于储存数据地址和发送数据 */

char rx_buf[DATA_LEN]; /* 用于储存接收数据 */

char addr[1]; /* 用于储存读/写的数据地址 */

addr[0] = 0; /* 数据地址设置为 0 */

fd = open(I2C_DEV_NAME, O_RDWR); /* 打开 I2C 总线设备 */

if (fd < 0) {

printf("open %s failed\n", I2C_DEV_NAME);

return -1;

}

ret = ioctl(fd, I2C_SLAVE, I2C_ADDR >> 1); /* 设置从机地址 */

if (ret < 0) {

printf("setenv address faile ret: %x \n", ret);

return -1;

}

/* 由于没有设置从机地址长度,所以使用默认的地址长度为 8 */

tx_buf[0] = addr[0]; /* 发数据时,第一个发送是数据地址 */

for (i = 1; i < DATA_LEN; i++) /* 初始化要写入的数据: 0、 1„„7 */

tx_buf[i] = i;

len = write(fd, tx_buf, DATA_LEN + 1); /* 把数据写入到 AT24C02, */

if (len < 0) {

printf("write data faile \n");

return -1;

}

usleep(1000 * 100); /* 需要延迟一段时间才能完成写入 EEPROM */

len = write(fd, addr, 1); /* 设置数据地址 */

if (len < 0) {

printf("write data addr faile \n");

return -1;

}

len = read(fd, rx_buf, DATA_LEN); /* 在设置的数据地址连续读入数据 */

if (len < 0) {

printf("read data faile \n");

return -1;

}

printf("read from eeprom:");

for (i = 0; i < DATA_LEN - 1; i++) { /* 对比写入数据和读取的数据 */

printf(" %x", rx_buf[i]);

if (rx_buf[i] != tx_buf[i + 1])

flag = 1;

}

printf("\n");

if (!flag) { /* 如果写入/读取数据一致,打印测试成功 */

printf("eeprom write and read test sussecced!\r\n");

} else { /* 如果写入/读取数据不一致,打印测试失败 */

printf("eeprom write and read test failed!\r\n");

}

return 0;

}

四、小节

本章通过简单的例子,介绍了使用文件 I/O 的方式操作 FPGA 侧添加的见外设控制器的方法。 相较于使用虚拟地址映射的方式实现 FPGA 侧设备驱动,使用 Linux 系统支持的驱动程序来完成这些外设的控制不仅简化了程序写过程, 提高了程序可移植性,而且内核驱动方式实现的设备驱动,能够方便的实现中断的功能,从而提高控制器的运行效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值