Linux驱动应用编程(四)IIC(获取BMP180数据)


  
   在 Linux ARM 平台上使用 I2C 时,不需要手动编写 I2C 时序是因为 Linux 内核和硬件抽象层已经处理了这些复杂的细节,提供了高层次的接口供开发者使用。
  无论是哪个IIC从机设备,我们只需要实现 IIC读数据和IIC写数据即可。然后根据不同设备的手册规则来向寄存器写或者读数据,从而实现某些特定的功能。

一、基础

1. 查看开发板手册,获取可用IIC总线

在这里插入图片描述

   香橙派OrangepiAipor引脚只有两个IIC可以使用,分别是IIC6和IIC7。分别对应如下两个设备节点。
在这里插入图片描述
  

2. 挂载从机,查看从机地址。

   我们将从机设备随便连接到IIC的其中一个上面,这里我们使用BMP180作为从机连接香橙派的IIC7总线,对应的设备节点为i2c-7。我们可以使用命令来查看IIC总线7上挂载的设备的地址。命令:i2cdetect -y -r 7。这样我们就获得了从机的地址(当然可以查看BMP180手册获得)。
在这里插入图片描述
  

3. 查看从机手册,使用命令读/写某寄存器值。

BMP180手册中寄存器地址分布如下:在这里插入图片描述

●读取 i2c7 总线上,从机设备地址为0x77 ,寄存器地址为 0xF7上的值。

i2cget -y 7 0x77 0xF7

在这里插入图片描述
在这里插入图片描述

●写入i2c7 总线上,从机设备地址为0x77 ,寄存器地址为 0xF4,值为0X2E。

i2cset 7 0x77 0xF4 0x2E

  

4. 查看从机手册通信流程。

   我们可以发现,通信大致流程就是往寄存器里写值,然后读取寄存器的值。将获取的值通过公式转换为真实的温度/压力值。

在这里插入图片描述

二、IIC常用API

1. iic数据包/报

头文件:#include <linux/i2c.h>

●数据包:
   在 Linux 内核中,struct i2c_msg 结构体用于描述 I2C 消息,这是在 I2C 总线上传输的数据块。它被用作 ioctl 调用的一部分,通过 I2C_RDWR 命令进行 I2C 读写操作。

struct i2c_msg {
    __u16 addr;   /* 从设备地址 */
    __u16 flags;  /* 消息标志,写:0, 读:1 */
    __u16 len;    /* 数据缓冲区长度 */
    __u8 *buf;    /* 数据缓冲区。对于写操作,这里存储的是要发送的数据;对于读操作,这里存储的是接收到的数据。 */
};

●数据报:包含多个数据包。

struct i2c_rdwr_ioctl_data {
    struct i2c_msg *msgs;  /* 指向 I2C 消息数组的指针 */
    __u32 nmsgs;           /* 消息的数量 */
};

2. ioctl函数

   是一个系统调用,专门用来让程序与设备进行通信。它有点像是一个“万能”函数,通过它可以向设备发送各种控制命令或者配置设备的某些参数。

int ioctl(int fd, unsigned long request, ...);
//int fd :设备的文件描述符。
//unsigned long request:请求,例如可读可写等。
//...(可变参数):根据 request 的不同,ioctl 可能需要一个或多个额外的参数。这些参数的类型和数量取决于具体的控制命令。

三、代码编写流程

1. IIC读取数据

   传入打开的IIIC设备文件描述符、要读取的从机设备地址、 要读取的寄存器地址、将数据读取到哪、读多少。
  在 I2C 通信中,通常在读取数据之前需要先向设备写入要读取的寄存器地址。这是因为在 I2C 设备中,数据的读取通常是通过向设备发送特定的寄存器地址来触发的。因此,为了从正确的寄存器地址读取数据,需要先将要读取的寄存器地址发送给设备。

/*
uint8_t slave_addr :从机地址
uint8_t reg_addr :要读取的寄存器
uint8_t* buffer:读取的数据存在哪
int length:读取的长度
*/
int iic_read(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* buffer, int length)
{
    struct i2c_msg msgs[2]; //读数据包以及写数据包
    struct i2c_rdwr_ioctl_data pack; //数据报
    uint8_t addr[1];  //存储寄存器地址
    int ret;

    addr[0] = reg_addr;  //要读取的寄存器地址。

    msgs[0].addr  = slave_addr;
    msgs[0].flags = 0;     // 写方向
    msgs[0].buf   = addr;  //向目标设备发送要读取的寄存器地址。
    msgs[0].len   = sizeof(addr);

    msgs[1].addr  = slave_addr;
    msgs[1].flags = 1;    // 读方向
    msgs[1].buf   = buffer;  //从寄存器读取的内容存到buffer中。
    msgs[1].len   = length; //读取的长度

    pack.msgs = msgs;
    pack.nmsgs = 2;

    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

2. IIC写入数据

   传入打开的IIIC设备文件描述符、要写入的从机设备地址、 要写入的寄存器地址、写什么数据、写多少。

int iic_write(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* data, int length)
{
    struct i2c_msg msg;
    struct i2c_rdwr_ioctl_data pack;
    uint8_t buffer[length + 1];  //为了包含寄存器地址,需要额外的空间
    int ret;

    buffer[0] = reg_addr; // 将寄存器地址作为第一个字节

    // 将要写入的数据拷贝到缓冲区中
    memcpy(buffer + 1, data, length);

    msg.addr   = slave_addr;
    msg.flags  = 0; // 写方向
    msg.len    = length + 1; // 包含了寄存器地址
    msg.buf    = buffer;

    pack.msgs  = &msg;
    pack.nmsgs = 1;

    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

3. 读取校验参数

在这里插入图片描述

由于在Linux-arm下是大端序,则先读取高位,再读低位。

#define Slave_Addr  0x77

void read_calibration_data(int fd, int16_t* AC1, int16_t* AC2, int16_t* AC3, 
							uint16_t* AC4, uint16_t* AC5, uint16_t* AC6, int16_t* B1, 
							int16_t* B2, int16_t* MB, int16_t* MC, int16_t* MD) 
{
    uint8_t buffer[22];  //每个数据占2个字节,共有11个数据。
    iic_read(fd, Slave_Addr , 0xAA,  buffer, 22);
    *AC1 = (buffer[0] << 8) | buffer[1];
    *AC2 = (buffer[2] << 8) | buffer[3];
    *AC3 = (buffer[4] << 8) | buffer[5];
    *AC4 = (buffer[6] << 8) | buffer[7];
    *AC5 = (buffer[8] << 8) | buffer[9];
    *AC6 = (buffer[10] << 8) | buffer[11];
    *B1 = (buffer[12] << 8) | buffer[13];
    *B2 = (buffer[14] << 8) | buffer[15];
    *MB = (buffer[16] << 8) | buffer[17];
    *MC = (buffer[18] << 8) | buffer[19];
    *MD = (buffer[20] << 8) | buffer[21];
}


//下面内容只是为了演示如何使用而已。
int main()
{
	int16_t AC1, AC2, AC3, B1, B2, MB, MC, MD;
    uint16_t AC4, AC5, AC6;
   // 读取校准数据
    read_calibration_data(fd, &AC1, &AC2, &AC3, &AC4, &AC5, &AC6, &B1, &B2, &MB, &MC, &MD);
}

4. 读取未校准的温度值

在这里插入图片描述

由于在Linux-arm下是大端序,则先读取高位,再读低位。

#define  Slave_Addr  0x77
#define  Data_Out_MSB 0xF6
#define  Data_Out_LSB 0xF7

#define  Tempture_Pressure_reg  0xF4
// 启动温度测量

int main()
{
	uint8_t send_data[1];
	uint8_t receive_data[2];
	int32_t raw_temp;  //未校准的温度数据。
	send_data[0] = 0x2e;  //要写入的数据

    if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data,1) <0) {  //开始测量温度
        perror("iic_write error");
        close(fd);
        return -1;
    }
    usleep(4500); // 等待测量完成

    // 读取未校准的温度数据
    if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_data, 2) < 0) {
        perror("iic_read error");
        close(fd);
        return -1;
    }
    raw_temp= receive_data[0]<<8|receive_data[1];  //未校准的温度值
}

5. 读取未校准的压力值

在这里插入图片描述
由于在Linux-arm下是大端序,则先读取高位,再读低位。

#define  Slave_Addr  0x77
#define  Data_Out_MSB 0xF6
#define  Data_Out_LSB 0xF7
#define  Data_Out_XLSB 0xF8
#define Tempture_Pressure_reg 0xF4
// 启动温度测量

/*
通常,"oss" 的取值范围在 0 到 3 之间,代表不同的过采样率。具体取值对应的过采样率取决于传感器型号和制造商的实现。在 BMP180 中,oss 的取值对应着以下过采样率:
  	oss = 0: 单次采样
  	oss = 1: 2 倍过采样
  	oss = 2: 4 倍过采样
  	oss = 3: 8 倍过采样
本文采用单次采样即可。
*/
int main()
{
  uint8_t send_data[1];
  uint8_t receive_data[3];
  int32_t raw_pressure;  //未校准的压力数据。
  send_data[0] = 0x34;  //要写入的数据

  if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data,1) <0) {  //开始测量压力
      perror("iic_write error");
      close(fd);
      return -1;
  }
  usleep(4500); // 等待测量完成

  // 读取未校准的压力数据
  if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_data, 3) < 0) {
      perror("iic_read error");
      close(fd);
      return -1;
  }
  raw_pressure =(receive_data[0]<<16|receive_data[1]<<8|receive_data[0]) >>8;  //未校准的压力值
}

6. 将未校准的测量值转为真实值

在这里插入图片描述

注意:代码中的右移多少位就相当于乘了2的几次方。左移相当于除。

//这里的参数很多都是校准参数。
void calculate_true_values(int32_t raw_temp, int32_t raw_pressure, int32_t* true_temp, int32_t* true_pressure, int16_t AC1, int16_t AC2, int16_t AC3, uint16_t AC4, uint16_t AC5, uint16_t AC6, int16_t B1, int16_t B2, int16_t MB, int16_t MC, int16_t MD) {
    int32_t X1, X2, X3, B3, B5, B6, B7, p;
    uint32_t B4;

    // 温度计算,
    X1 = (raw_temp - AC6) * AC5 >> 15;
    X2 = (MC << 11) / (X1 + MD);
    B5 = X1 + X2;
    *true_temp = (B5 + 8) >> 4;

    // 压力计算
    B6 = B5 - 4000;
    X1 = (B2 * (B6 * B6 >> 12)) >> 11;
    X2 = AC2 * B6 >> 11;
    X3 = X1 + X2;
    B3 = (((AC1 * 4 + X3) << 1) + 2) >> 2;
    X1 = AC3 * B6 >> 13;
    X2 = (B1 * (B6 * B6 >> 12)) >> 16;
    X3 = ((X1 + X2) + 2) >> 2;
    B4 = AC4 * (uint32_t)(X3 + 32768) >> 15;
    B7 = ((uint32_t)raw_pressure - B3) * (50000 >> 1);
    if (B7 < 0x80000000) {
        p = (B7 * 2) / B4;
    } else {
        p = (B7 / B4) * 2;
    }
    X1 = (p >> 8) * (p >> 8);
    X1 = (X1 * 3038) >> 16;
    X2 = (-7357 * p) >> 16;
    *true_pressure = p + ((X1 + X2 + 3791) >> 4);
}


四、完整代码

iic.c

#include <stdint.h>
#include <string.h>
#include "iic.h"

int iic_init(const char *device)
{            
   return open(device,O_RDWR);  //可读可写   
}

int iic_close(int fd)
{
   return close(fd);
}

/*
uint8_t slave_addr :从机地址
uint8_t reg_addr :要读取的寄存器
uint8_t* buffer:读取的数据存在哪
int length:读取的长度
*/
int iic_read(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* buffer, int length)
{
    struct i2c_rdwr_ioctl_data pack;
    struct i2c_msg msgs[2];
    uint8_t addr[1];
    int ret;

    addr[0] = reg_addr;  //要读取的寄存器地址。

    msgs[0].addr  = slave_addr;
    msgs[0].flags = 0; // 写方向
    msgs[0].len   = sizeof(addr);
    msgs[0].buf   = addr;    //向目标设备发送要读取的寄存器地址。

    msgs[1].addr  = slave_addr;
    msgs[1].flags = 1; // 读方向
    msgs[1].len   = length;
    msgs[1].buf   = buffer;

    pack.msgs = msgs;
    pack.nmsgs = 2;

    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

//IIC写数据
int iic_write(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* data, int length)
{
    struct i2c_rdwr_ioctl_data pack;
    struct i2c_msg msg;
    uint8_t buffer[length + 1]; // 为了包含寄存器地址,需要额外的空间
    int ret;

    buffer[0] = reg_addr; // 将寄存器地址作为第一个字节

    // 将要写入的数据拷贝到缓冲区中
    memcpy(buffer + 1, data, length);

    msg.addr   = slave_addr;
    msg.flags  = 0; // 写方向
    msg.len    = length + 1; // 包含了寄存器地址
    msg.buf    = buffer;

    pack.msgs  = &msg;
    pack.nmsgs = 1;

    ret = ioctl(fd, I2C_RDWR, &pack);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        return -1;
    }

    return 0;
}

//读取校准参数
void read_calibration_data(int fd, int16_t* AC1, int16_t* AC2, int16_t* AC3, uint16_t* AC4, uint16_t* AC5, uint16_t* AC6, int16_t* B1, int16_t* B2, int16_t* MB, int16_t* MC, int16_t* MD) {
    uint8_t buffer[22];
    iic_read(fd, Slave_Addr, 0xAA, buffer, 22);
    *AC1 = (buffer[0] << 8) | buffer[1];
    *AC2 = (buffer[2] << 8) | buffer[3];
    *AC3 = (buffer[4] << 8) | buffer[5];
    *AC4 = (buffer[6] << 8) | buffer[7];
    *AC5 = (buffer[8] << 8) | buffer[9];
    *AC6 = (buffer[10] << 8) | buffer[11];
    *B1 = (buffer[12] << 8) | buffer[13];
    *B2 = (buffer[14] << 8) | buffer[15];
    *MB = (buffer[16] << 8) | buffer[17];
    *MC = (buffer[18] << 8) | buffer[19];
    *MD = (buffer[20] << 8) | buffer[21];
}

//计算真实值
void calculate_true_values(int32_t raw_temp, int32_t raw_pressure, int32_t* true_temp, int32_t* true_pressure, int16_t AC1, int16_t AC2, int16_t AC3, uint16_t AC4, uint16_t AC5, uint16_t AC6, int16_t B1, int16_t B2, int16_t MB, int16_t MC, int16_t MD) {
    int32_t X1, X2, X3, B3, B5, B6, B7, p;
    uint32_t B4;

    // 温度计算
    X1 = (raw_temp - AC6) * AC5 >> 15;
    X2 = (MC << 11) / (X1 + MD);
    B5 = X1 + X2;
    *true_temp = (B5 + 8) >> 4;

    // 压力计算
    B6 = B5 - 4000;
    X1 = (B2 * (B6 * B6 >> 12)) >> 11;
    X2 = AC2 * B6 >> 11;
    X3 = X1 + X2;
    B3 = (((AC1 * 4 + X3) << 1) + 2) >> 2;
    X1 = AC3 * B6 >> 13;
    X2 = (B1 * (B6 * B6 >> 12)) >> 16;
    X3 = ((X1 + X2) + 2) >> 2;
    B4 = AC4 * (uint32_t)(X3 + 32768) >> 15;
    B7 = ((uint32_t)raw_pressure - B3) * (50000 >> 1);
    if (B7 < 0x80000000) {
        p = (B7 * 2) / B4;
    } else {
        p = (B7 / B4) * 2;
    }
    X1 = (p >> 8) * (p >> 8);
    X1 = (X1 * 3038) >> 16;
    X2 = (-7357 * p) >> 16;
    *true_pressure = p + ((X1 + X2 + 3791) >> 4);
}

iic.h

#ifndef __IIC_H
#define __IIC_H

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>

#define  Slave_Addr 0x77
#define  Data_Out_MSB 0xF6
#define  Data_Out_LSB 0xF7
#define  Data_Out_XLSB 0xF8
#define  Tempture_Pressure_reg 0xF4


int iic_init(const char *device);
int iic_close(int fd);
int iic_read(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* buffer, int length);
int iic_write(int fd, uint8_t slave_addr, uint8_t reg_addr, uint8_t* data, int length);
void read_calibration_data(int fd, int16_t* AC1, int16_t* AC2, int16_t* AC3, uint16_t* AC4, uint16_t* AC5, uint16_t* AC6, int16_t* B1, int16_t* B2, int16_t* MB, int16_t* MC, int16_t* MD);
void calculate_true_values(int32_t raw_temp, int32_t raw_pressure, int32_t* true_temp, int32_t* true_pressure, int16_t AC1, int16_t AC2, int16_t AC3, uint16_t AC4, uint16_t AC5, uint16_t AC6, int16_t B1, int16_t B2, int16_t MB, int16_t MC, int16_t MD) ;
#endif

main.c

#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdint.h>
#include <linux/i2c.h>
#include <string.h>
#include "iic.h"



int main() {
    int fd;
    int32_t raw_temp, raw_pressure, true_temp, true_pressure;
    int16_t AC1, AC2, AC3, B1, B2, MB, MC, MD;
    uint16_t AC4, AC5, AC6;
	uint8_t send_data[1];
	uint8_t receive_temp[2];
	uint8_t receive_pressure[3];
	
	
    // 打开I2C设备
    fd = iic_init("/dev/i2c-7");
    if (fd < 0) {
        perror("iic_init error");
        return -1;
    }

    // 读取校准数据
    read_calibration_data(fd, &AC1, &AC2, &AC3, &AC4, &AC5, &AC6, &B1, &B2, &MB, &MC, &MD);

    //开始测量温度 
    send_data[0] = 0x2e;  //要写入的数据
    if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data, 1) <0) { 
        perror("iic_write error");
        close(fd);
        return -1;
    }
    usleep(4500); // 等待测量完成

    // 读取未校准的温度数据
    if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_temp, 2) < 0) {
        perror("iic_read error");
        close(fd);
        return -1;
    }
    raw_temp= receive_temp[0]<<8|receive_temp[1];  //未校准的温度值
    
	
	
	send_data[0] = 0x34;  //要写入的数据
    if (iic_write(fd, Slave_Addr, Tempture_Pressure_reg , send_data, 1) <0) {  //开始测量压力
        perror("iic_write error");
        close(fd);
        return -1;
    }
    usleep(4500); // 等待测量完成

    // 读取未校准的压力数据
    if (iic_read(fd, Slave_Addr, Data_Out_MSB , receive_pressure, 3) < 0) {
        perror("iic_read error");
        close(fd);
        return -1;
    }
    raw_pressure =(receive_pressure[0]<<16|receive_pressure[1]<<8|receive_pressure[0]) >>8;  //未校准的压力值
  
    // 计算实际温度和压力
    calculate_true_values(raw_temp, raw_pressure, &true_temp, &true_pressure, AC1, AC2, AC3, AC4, AC5, AC6, B1, B2, MB, MC, MD);

    // 输出实际温度和压力
    printf("True Temperature: %.2f C\n", true_temp / 10.0);
    printf("True Pressure: %.2f hPa\n", true_pressure / 100.0);

    // 关闭I2C设备
    close(fd);
    return 0;
}

在这里插入图片描述

相关推荐

  1. linux 驱动编程笔记

    2024-06-08 18:48:04       7 阅读
  2. Linux PWM 应用编程

    2024-06-08 18:48:04       31 阅读
  3. Linux GPIO 应用编程

    2024-06-08 18:48:04       31 阅读

最近更新

  1. dolphinscheduler独立集群部署文档(海豚调度)

    2024-06-08 18:48:04       0 阅读
  2. C# —— 三目运算符及实例

    2024-06-08 18:48:04       0 阅读
  3. python 多线程条件竞争利用失败print不显示的原因

    2024-06-08 18:48:04       0 阅读
  4. 与君共勉:坚持+努力

    2024-06-08 18:48:04       0 阅读
  5. ReentrantLock与AQS:深入剖析多线程同步的艺术

    2024-06-08 18:48:04       0 阅读
  6. 快速上手:如何在npm发布自己的插件包

    2024-06-08 18:48:04       0 阅读

热门阅读

  1. window.clearInterval(timer) 清除定时器

    2024-06-08 18:48:04       3 阅读
  2. Docker

    Docker

    2024-06-08 18:48:04      4 阅读
  3. Redis命令使用示例(一)

    2024-06-08 18:48:04       4 阅读
  4. Ansible——user模块

    2024-06-08 18:48:04       4 阅读
  5. SD-WAN加速跨国服务器访问

    2024-06-08 18:48:04       5 阅读
  6. Spring Boot中实现规则引擎源码教程

    2024-06-08 18:48:04       3 阅读
  7. android:text 总为大写字母的原因

    2024-06-08 18:48:04       3 阅读
  8. input只允许输入数字

    2024-06-08 18:48:04       4 阅读
  9. 数据结构汇总学习(ing)

    2024-06-08 18:48:04       5 阅读
  10. 注解 - @RequestBody

    2024-06-08 18:48:04       4 阅读
  11. 阿里云一键登录号码认证服务

    2024-06-08 18:48:04       5 阅读