• 浅浅的 linux开发板 驱动的使用


    零、参考

    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 
    #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;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    其中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 
    #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;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意,这里传入的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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    #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;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    二、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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中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			*/
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    参考:

    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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试程序

    #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;
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    参考

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

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

  • 相关阅读:
    在开发中使用el-table宽度不一致出现抖动问题
    【FPGA】Verilog语言从零到精通
    在IIS上部署ASP.NET Core Web API和Blazor Wasm应用程序的完整指南
    Redis 16种妙用
    分布式事务的背景
    多线程初阶(二)
    Thymeleaf学习(1)—— 表达式和属性
    OWT Server整体架构分析
    发布第一个npm包的过程记录
    js中正则的使用总结
  • 原文地址:https://blog.csdn.net/qq_44179528/article/details/126724443