Bootstrap

STM32MP157A-FSMP1A单片机移植Linux系统I2C总线驱动

由于I2C总线驱动为Linux内核自带的总线驱动,在一个新的板子上可能由于不同的定义与芯片原厂定义的I2C管脚有所不同,这时就需要开发人员对设备树信息及内核驱动进行更新。

原理图可知,I2C的SCL对应PF14,SDA对应PF15

在Linux内核中,arch/arm/boot/dts/  设备树文件 --> stm32mp157a-fsmp1a.dts,I2C对应的GPIO复用信息 --> stm32mp15-pinctrl.dtsi。

 驱动程序代码

#include <linux/init.h>       // 包含内核初始化相关的头文件
#include <linux/module.h>     // 包含内核模块相关的头文件
#include <linux/of.h>         // 包含设备树操作相关的头文件
#include <linux/gpio.h>       // 包含 GPIO 操作相关的头文件
#include <linux/of_gpio.h>    // 包含设备树 GPIO 相关的头文件
#include <linux/fs.h>         // 包含文件操作相关的头文件
#include <linux/uaccess.h>    // 包含用户空间访问内核空间相关的头文件
#include <linux/device.h>     // 包含设备相关的头文件
#include <linux/i2c.h>   // 包含 I2C 相关的头文件
#include "i2c_test.h"    // 包含自定义头文件

//创建设备号
static int major;
//创建类
static struct class *cls;
//创建设备
static struct device *device;
//创建i2c_client
static struct i2c_client *client;
//保存字符设备数据
static char i2c_buf[100];

//获取温湿度数据
int get_hum_tem(int reg)
{
    int ret = 0;

    char r_buf  = reg;
    short value;
        
    struct i2c_msg r_msg[] ={
        [0] = {
            .addr = client->addr,
            .flags = 0,
            .len = 1,
            .buf = &r_buf,
        },
        [1] = {
            .addr = client->addr,
            .flags = 1,
            .len = 2,
            .buf = (u8 *)&value,
        },
    };

    //发送读取温湿度数据的命令
    ret = i2c_transfer(client->adapter, r_msg, 2);
    if(ret != 2)
    {
        printk("i2c_transfer failed\n");
        return ret;
    }

    return value;
} //end of get_hum_tem


static int led_open(struct inode *inode, struct file *file)
{
    unsigned int cmajor;
    //保存次设备号
    cmajor = iminor(inode);

    //将次设备号保存到file结构体的private_data中
    file->private_data = (void *)cmajor;

    printk("led_open\n");
    return 0;
}

static int led_close(struct inode *inode, struct file *file)
{
    printk("led_close\n");
    return 0;
}

static int led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned int ret;

    //从内核空间读取数据到用户空间,如果读取数据的长度大于i2c_buf的长度,按照最大长度读取
    if(count > sizeof(i2c_buf))
    {
        count = sizeof(i2c_buf);
    }
    ret = copy_to_user(buf, i2c_buf, count);
    if(ret < 0)
    {
        printk("copy_to_user failed\n");
        return -1;
    }

    printk("led_read\n");
    return 0;
}

static int led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    unsigned int ret;

    //将用户空间的数据拷贝到内核空间,如果传输数据的长度大于i2c_buf的长度,按照最大长度传输
    if (count > sizeof(i2c_buf))
    {
        count = sizeof(i2c_buf);
    }
    ret = copy_from_user(i2c_buf, buf, count);
    if (ret < 0)
    {
        printk("copy_from_user failed\n");
        return -1;
    }

    
    printk("led_write\n");
    return 0;
}

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int hum = 0, tem = 0;

    //写入用户空间
    switch(cmd)
    {
        case GET_HUM:
            hum = get_hum_tem(0xE5);
            copy_to_user((int *)arg, &hum, 4);
            break;
        case GET_TEM:
            tem = get_hum_tem(0xE3);
            copy_to_user((int *)arg, &tem, 4);
            break;
        default:
        break;
    }

    printk("led_ioctl\n");
    return 0;
}

//定义file_operations结构体
struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close,
    .read = led_read,
    .write = led_write,
    .unlocked_ioctl = led_ioctl,
};

//编写i2c设备驱动
static int myi2c_probe(struct i2c_client *pdev, const struct i2c_device_id *id)
{
    printk("myi2c_probe\n");
    client = pdev;
    //1.创建设备号
    major = register_chrdev(0, "myi2c", &fops);
    if (major < 0)
    {
        printk("register_chrdev failed\n");
        return -1;
    }

    //2.创建类
    cls = class_create(THIS_MODULE, "myi2c");
    if (IS_ERR(cls))
    {
        printk("class_create failed\n");
        unregister_chrdev(major, "myi2c");
        return -1;
    }
    
    //3.创建设备
    device = device_create(cls, NULL, MKDEV(major, 0), NULL, "myi2c");
    if (IS_ERR(device))
    {
        printk("device_create failed\n");
        class_destroy(cls);
        unregister_chrdev(major, "myi2c");
        return -1;
    }


    return 0;
}

static int myi2c_remove(struct i2c_client *pdev)
{
    printk("myi2c_remove\n");    
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, "myi2c");

    return 0;
}

static const struct of_device_id myplatform_test_match_table[] = {
    { .compatible = "johnson,si7006", },
    { /* sentinel */ }
};


struct i2c_driver myi2c_driver = {
    .probe = myi2c_probe,
    .remove = myi2c_remove,
    .driver = {
        .name = "myi2c_slave",
        .of_match_table = myplatform_test_match_table,
    },
};

//一键注册宏定义
module_i2c_driver(myi2c_driver);
MODULE_LICENSE("GPL");

头文件

#ifndef __I2CTEST_H__
#define __I2CTEST_H__

#define GET_HUM _IOR('i', 1, int)
#define GET_TEM _IOR('i', 0, int)

#endif

应用程序 --> 实现通过I2C获取温湿度数据

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include "i2c_test.h"

int main()
{
    int hum = 0, tem = 0;
    float hum1, tem1; // 保存计算完毕后的温湿度数据
    int fd = 0;

    //打开设备文件
    fd = open("/dev/myi2c", O_RDWR);
    if (fd < 0)
    {
        printf("open failed\n");
        return -1;
    }

    //读取数据
    while(1)
    {
        ioctl(fd, GET_HUM, &hum);
        ioctl(fd, GET_TEM, &tem);

        //字节序转换
        hum = ntohs(hum);
        tem = ntohs(tem);

        hum1 = (float)hum * 125 / 65536 - 6;
        tem1 = tem * 175.72 / 65536 - 46.85;
        printf("hum = %f, tem = %f\n", hum1, tem1);
        sleep(1);
    }   

    //关闭设备文件
    close(fd);

    return 0;
}

;