嵌入式linux入门3-5-I2C

I2C协议与SMBus协议

由于我自己对I2C协议还算熟悉,笔记里就不涉及I2C协议本身的讲解了,这里提供两篇博客用于了解I2C以及SMBus协议:
Understanding the I2C Bus

SMBus Quick Start Guide

i2c-tools

使用i2c-tools可以方便的对I2C设备进行调试,参考以下博客获取使用方法,特别简单:

Using the Linux I2C-Tools Software

但是光看上面那篇博客还不够,想掌握更多用法(比如不指定寄存器地址直接读数据)可以在linux下安装此工具然后读man文档,开发板上的一般都阉割了man文档。

而且4.0版本之前的i2c-tools有其局限性,在使用i2cget指令进行读操作时最多只能读取前2字节,新版本新增的i2ctransfer指令已经可以读指定个数的字节了,可惜我的开发板上是老版本,还不支持这个指令,对于ATH10这种一次返回很多字节的温湿度传感器,就不能用这个读取了,它的时序是这样的:

image-20220417165154601

使用i2cget读取,就只能获取状态位和湿度数据的最高位了。通过直接使用linux下的i2c访问接口,我们可以自行编写读写指令。

i2c编程

实验平台为licheepi nano。

建议先看韦东山老师写的这篇博客:Linux应用开发【第十二章】I2C编程应用开发的12.3.2节

以下为一个控制ATH10温湿度传感器的小程序,硬件连接上,其挂载于i2c-0:

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

int execute_cmd(int fd, char *cmd);
void parse_data(unsigned char *buffer, float *temperature, float * humidness);

int main(int args, char *argv[])
{
    const unsigned char ath10_i2c_addr = 0x38; /* ATH10温湿度传感器地址 */
    int fd_i2c;

    /* 检查输入参数 */
    if (args == 1 || strcmp(argv[0], "--help") == 0)
    {
        printf("usage:\n");
        printf("%s I2C CMD...\n", argv[0]);
        printf("operation ath10 using I2C.\n");
        printf("CMD :\n");
        printf("\tinit\t- initialize sensor.\n");
        printf("\tsamp\t- sampling once.\n");
        printf("\tread\t- read data.\n");
        exit(EXIT_FAILURE);
    }

    /* 打开传入的I2C设备 */
    fd_i2c = open(argv[1], O_RDWR);
    if (fd_i2c == -1)
    {
        fprintf(stderr, "Unable to open %s - ", argv[1]);
        fflush(stderr);
        perror(NULL);
        exit(EXIT_FAILURE);
    }

    /* 设置I2C从机地址 */
    if (ioctl(fd_i2c, I2C_SLAVE_FORCE, ath10_i2c_addr) == -1)
    {
        perror("Unable to set slave address - ");
    }

    /* 遍历并处理传入的命令 */
    for (int i = 2; i < args; i++)
    {
        if (execute_cmd(fd_i2c, argv[i]) == -1)
        {
            fprintf(stderr, "Unable to recognize cmd \"%s\"\n", argv[i]);
            fflush(stderr);
            close(fd_i2c);
            exit(EXIT_FAILURE);
        }
    }

    close(fd_i2c);
    exit(EXIT_SUCCESS);
}

/* 执行指令,指令识别失败时返回-1 */
int execute_cmd(int fd, char *cmd)
{
    const int cmd_read_recv_len = 6; /* 读取时需要读取的字节数 */

    const unsigned char cmd_init[] = {0xF1, 0x08, 0x00}; /* 初始化指令 */
    const unsigned char cmd_samp[] = {0xAC, 0x00, 0x00}; /* 采样指令 */

    unsigned char read_buffer[cmd_read_recv_len];

    if (strcmp(cmd, "init") == 0)
    {
        /* 初始化指令 */
        if (write(fd, cmd_init, sizeof(cmd_init)) == -1)
        {
            perror("Unable to send cmd \"init\" - ");
        }
    }
    else if (strcmp(cmd, "samp") == 0)
    {
        /* 采样指令 */
        if (write(fd, cmd_samp, sizeof(cmd_samp)) == -1)
        {
            perror("Unable to send cmd \"samp\" - ");
        }
    }
    else if (strcmp(cmd, "read") == 0)
    {
        /* 读取指令 */
        int read_size;
        read_size = read(fd, read_buffer, cmd_read_recv_len);
        if (read_size == -1)
        {
            perror("Unable to read data - ");
        }

        /* 打印获取到的数据 */
        if (read_size == cmd_read_recv_len)
        {
            float temperature, humidness;
            parse_data(read_buffer, &temperature, &humidness);
            printf("T = %.2f℃, H = %.2f%%\n", temperature, humidness);
        }
        else
        {
            fprintf(stderr, "Invalid data\n");
        }
    }
    else
    {
        /* 未分辨的命令 */
        return -1;
    }
    return 0;
}

/* 解析收到的数据,计算出温湿度 */
void parse_data(unsigned char *buffer, float *temperature, float * humidness)
{
    unsigned int t_data = 0;
    unsigned int h_data = 0;

    /* 所收数据的第4字节低4位以及第5、6字节为温度数据 */
    t_data = ((buffer[3] & 0x0F) << 16) | (buffer[4] << 8) | buffer[5];

    /* 所收数据的第2、3字节以及第4字节的高4位为湿度数据 */
    h_data = (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3] >> 4);

    /* 使用数据手册的换算公式进行换算 */
    *temperature = t_data / powf(2, 20) * 200 - 50;
    *humidness = h_data / powf(2, 20);
}

编译用的简易makefile:

CC := arm-linux-gcc

all : i2c_ath10.o
    ${CC} -o ath10 $^

%.o : %c %h
    ${CC} -c -o $@ $<

.PHONY clean :
    rm ath10 *.o

编译好生成的ath10文件发送到开发板,使用chmod +x添加可执行权限,就可以直接执行获取传感器的温湿度信息了,执行结果如下:

# 使用i2c-0,先init初始化,然后smap采样,最后read读取结果
> ./ath10 /dev/i2c-0 init samp read
T = 21.66℃, H = 9.65%

踩坑记录

  1. 编译出的程序在开发板上运行时提示Segmentation fault
    要注意,编译内核时使用的交叉编译工具链并不一定适用于编译应用程序,licheepi nano的根文件系统为使用buildroot构建,根文件系统内的系统动态链接库由buildroot下载的交叉编译工具链生成,这个工具链位于buildroot/output/host下,要使用这个交叉编译工具链来编译应用程序。使用编译内核的交叉编译工具链编译应用程序的话,由于编译时动态链接的运行库和开发板根文件系统内的动态链接库版本可能不匹配,会导致程序运行时,找不到对应的动态链接文件而运行失败。
    对于licheepi nano而言,编译内核时的交叉工具链为arm-linux-gnueabi-gcc,而buildroot在构建根文件系统时,默认往里面放的arm-linux-musleabi-gcc的动态链接库,所以编译应用程序时使用buildroot下载的arm-linux-musleabi-gcc。

  2. 运行编译器出现:error while loading shared libraries: libmpfr.so.4: cannot open shared object file: No such file or directory
    这个我一开始以为是我写的程序要链接这个动态链接库,而编译器找不到,可是编译器的lib链接目录下又有这个文件,百思不得其解。后来醒悟过来,淦了,是编译器本身要运行时找不到这个动态链接库,跟我写的程序根本没关系(TAT白瞎我琢磨半天)。电脑里一般有libmpfr.so.6这个动态链接库,解决方法为创建这个文件的软链接命名为libmpfr.so.4

    sudo ln -s /usr/lib/x86_64-linux-gnu/libmpfr.so.6 /usr/lib/x86_64-linux-gnu/libmpfr.so.4

发表评论

您的电子邮箱地址不会被公开。