rk3288 7.1 mlx90640调试

一笑奈何 提交于 2020-02-22 20:04:31

由于疫情的影响,最近在调试测温模块mlx90640 mlx90614,下面简单描述下mlx90640的驱动开发.

站在巨人(https://blog.csdn.net/qq_33487044/article/details/86565536)的肩膀上做一些更为详细的说明.

我们看技术手册上可以了解到

mlx90640是一款红外热像仪模块, 32×24 像素, I2C 接口通信,兼容 3.3V/5V 电平。采用 MLX90640 远红外热传感器阵列,可精确检测特定区域和温度范围内的目标物体,尺寸小巧,可方便集成到各种工业或智能控制应用中。
⚫ 采用 MLX90640 远红外热传感器阵列, 32×24 像素
⚫ 支持 I2C 接口通信,可设置为快速模式(速率可达 1MHz)
⚫ 噪声等效温差(NETD)仅为 0.1K RMS@1Hz 刷新率,噪声性能好
⚫ 板载电平转换电路,可兼容 3.3V/5V 的工作电平

通讯方式为 I2C,支持 I2C 高速模式(最高可达 1MHz),只能作为 I2C 总线上的从设备, SDA 和SCL 端口可以承受 5V 电压,可直接接入到 5V I2C 总线中,模块的设备地址是可以编程的,最多可以有127 个地址,出场默认值为 0x33,具体的i2c协议这边就不介绍了,根据上述我们可以得知通讯的i2c设备地址为0x33。

因此有dts的配置:

&i2c4 {
    status = "okay";
    clock-frequency = <400000>;

        mlx90640@33 {
               compatible = "mlx90640";
               reg = <0x33>;
               status = "okay";
        };
};

这里需要关注一个clock-frequency = <400000>; 正常通讯为100k就可,但由于芯片内部有个刷新率,如下图,因此当配置的刷新率越高则所需clk越大,而rk3288上最大只支持400k(3399支持1M),因此这里写了400k.

底层驱动参考了 https://blog.csdn.net/qq_33487044/article/details/86565536

#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#ifdef CONFIG_OF_GPIO
#include <linux/of_platform.h>
#endif
#include <linux/of_gpio.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <asm/param.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>

#define DRIVER_NAME     "mlx90640"
#define MLX90640_IOCTL_MAGIC            'm'
#define MLX90640_GET_DATA    _IOR(MLX90640_IOCTL_MAGIC, 1, int *)
#define MLX90640_SET_DATA    _IOR(MLX90640_IOCTL_MAGIC, 2, int *)


struct mlx90640_chip {
    struct mutex i2c_lock;
    struct i2c_client *client;
    const char *const *names;
};

struct MLX90640_R_DATA{
    uint16_t reg;
    uint16_t size;
    uint16_t buff[1664];
};

struct MLX90640_W_DATA{
    uint16_t reg;
    uint16_t size;
    uint16_t data;
};

struct mlx90640_chip *gchip = NULL;

static int mlx90640_open(struct inode *inode, struct file *filp)
{
    return 0;
}

int mlx90640_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static int i2c_master_recv_(struct i2c_client *client,char *msgbuf,uint16_t *data,uint16_t nMemAdddressRead)
{
    struct i2c_msg msgs[2];
    struct i2c_adapter *adap = client->adapter;
    int ret;
    uint16_t bytesRemaining = nMemAdddressRead * 2;
    int cnt = 0;
    int i = 0;
    uint16_t *p = data;
    char i2cData[1664];

    msgs[0].addr = client->addr;
    msgs[0].flags = I2C_M_TEN;    /* write */
    msgs[0].len = 2;
    msgs[0].buf = msgbuf;

    msgs[1].addr = client->addr;
    msgs[1].flags = I2C_M_RD | I2C_M_NOSTART;
    msgs[1].len = bytesRemaining;
    msgs[1].buf = i2cData;

    memset(i2cData,0,bytesRemaining);
    ret = i2c_transfer(adap,&msgs,2);
    for(cnt = 0;cnt < nMemAdddressRead;cnt++){
        i = cnt << 1;
        *p++ = ((uint16_t)i2cData[i] << 8) | i2cData[i+1];
    }
    return 0;
}

static int mlx90640_write_data(struct i2c_client *client,uint16_t reg,uint16_t buf)
{
    int ret = 0;
    u8 abuf[4];
    abuf[0] = reg >> 8;
    abuf[1] = reg;    
    abuf[2] = buf >> 8;
    abuf[3] = buf;    
    ret = i2c_master_send(client,abuf,4);
    return ret;
}

static int mlx90640_read_data(struct i2c_client *client,uint16_t reg,uint16_t *data,uint16_t nMemAdddressRead)
{
    int rc;
    u8 msgbuf[2];
    msgbuf[0] = reg >> 8;
    msgbuf[1] = reg;
    rc = i2c_master_recv_(client,msgbuf,data,nMemAdddressRead);
    return 0;
}

static long mlx90640_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
    struct MLX90640_R_DATA data;
    struct MLX90640_W_DATA w_data;
    mutex_lock(&gchip->i2c_lock);
    switch(cmd){
        case MLX90640_GET_DATA:
            if(copy_from_user((void*)&data,(void __user*)arg,sizeof(struct MLX90640_R_DATA))){
                printk("MLX90640_ioctl SE_IOC_GET_DATA copy_from_user error\n");
                mutex_unlock(&gchip->i2c_lock);
                return -EFAULT;
            }    
            if(mlx90640_read_data(gchip->client,data.reg,&data.buff[0],data.size) != 0){
                printk("MLX90640_ioctl read data failed,reg:0x%x ,size:%d \n",data.reg,data.size);
                mutex_unlock(&gchip->i2c_lock);
                return -EFAULT;
            }
            if(copy_to_user((void __user*)arg,(void*)&data,sizeof(struct MLX90640_R_DATA))){
                printk("MLX90640_ioctl SE_IOC_GET_DATA copy_to_user error\n");
                mutex_unlock(&gchip->i2c_lock);
                return -EFAULT;
            }
            break;
        case MLX90640_SET_DATA:
            if(copy_from_user((void*)&w_data,(void __user*)arg,sizeof(struct MLX90640_W_DATA))){
                printk("MLX90640_ioctl SE_IOC_SET_DATA copy_from_user error\n");
                mutex_unlock(&gchip->i2c_lock);
                return -EFAULT;
            }
            mlx90640_write_data(gchip->client,w_data.reg,w_data.data);
            break;
        default:
            printk("MLX90640_ioctl cmd:0x%x error\n",cmd);
            break;    
    }
    mutex_unlock(&gchip->i2c_lock);
    return 0;
}

static struct file_operations mlx90640_fops =
{
    .owner = THIS_MODULE,
    .open = mlx90640_open,
    .release = mlx90640_release,
    .unlocked_ioctl = mlx90640_ioctl,
};

static struct miscdevice mlx90640_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "mlx90640",
    .fops = &mlx90640_fops,
};

static int mlx90640_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
    struct mlx90640_chip *chip;
    struct i2c_adapter *adapter = client->adapter;
    int ret;

        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)){
            return -ENODEV;
        }
        
    chip = devm_kzalloc(&client->dev,sizeof(struct mlx90640_chip), GFP_KERNEL);
    if (chip == NULL)
        return -ENOMEM;

    chip->names = DRIVER_NAME;

    chip->client = client;

    mutex_init(&chip->i2c_lock);

    gchip = chip;

    struct MLX90640_R_DATA data;
    data.reg = 0x8000;
    data.size = 1;    
    mlx90640_read_data(gchip->client,data.reg,&data.buff[0],data.size);
#if 0
    struct MLX90640_W_DATA w_data;
    w_data.reg = 0x800D;
    w_data.size = 1;
    w_data.data = 0x1291;
    MLX90640_write_data(gchip->client,w_data.reg,w_data.data);
#endif
    data.reg = 0x800D;
    mlx90640_read_data(gchip->client,data.reg,&data.buff[1],data.size);
    printk("harris mlx90640_probe read 0x8000->0x%x 0x800D->0x%x\n",data.buff[0],data.buff[1]);

    ret = misc_register(&mlx90640_dev);

    return 0;
}

static int mlx90640_remove(struct i2c_client *client)
{
    return 0;
}

static const struct i2c_device_id mlx90640_dt_id[] = {
    {"mlx90640", 0},
    { }
};
MODULE_DEVICE_TABLE(i2c, mlx90640_dt_id);

static const struct of_device_id mlx90640_dt_ids[] = {
    { .compatible = "mlx90640", },
    { }
};
MODULE_DEVICE_TABLE(i2c, mlx90640_dt_ids);

static struct i2c_driver mlx90640_driver = {
    .probe        = mlx90640_probe,
    .remove        = mlx90640_remove,
    .driver = {
        .name    = DRIVER_NAME,
        .owner = THIS_MODULE,    
        .of_match_table = of_match_ptr(mlx90640_dt_ids),
    },
    .id_table = mlx90640_dt_id,
};

static int __init mlx90640_init(void)
{
    return i2c_add_driver(&mlx90640_driver);
}

static void __exit mlx90640_exit(void)
{
    i2c_del_driver(&mlx90640_driver);
}

subsys_initcall(mlx90640_init);
module_exit(mlx90640_exit);

MODULE_AUTHOR("xxx");
MODULE_DESCRIPTION("i2c driver for mlx90640");
MODULE_LICENSE("GPL");

驱动主要是实现了i2c的一个通讯接口,并封装了一个设备节点,供给应用层去调用,这里需要注意的是如果是64位的芯片的话需要参考之前的文章,封装一个.compat_ioctl 接口才能被调用,具体封装参考(https://blog.csdn.net/u011938662/article/details/80004731).

淘宝给的stm32上面的驱动文件里MLX90640_API.c MLX90640_API.h 则在android上面是要作为jni层进行封装,封装好后,应用层即可调用获取温度数据,如果不封装jni的话则可以写了简单的bin文件测试,如下.

#include <stdio.h>     
#include <stdlib.h>
#include <sys/ioctl.h>  
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#include "MLX90640_API.h"

#define DEVICE_NAME "/dev/mlx90640"
#define  Rate2HZ   0x02
#define  Rate4HZ   0x03
#define  Rate8HZ   0x04
#define  Rate16HZ  0x05
#define  Rate32HZ  0x06

#define     RefreshRate Rate16HZ
#define  TA_SHIFT 8 //Default shift for MLX90640 in open air

struct w_SE_DATA write_data;
struct SE_DATA read_data;
int mlx90640_fd = -1;  
paramsMLX90640 mlx90640;
    
int MLX90640_Open(char *filename)
{
    uint16_t i = 0;
    static uint16_t eeMLX90640[832];
    uint16_t frame[834];
    globe_write_data = &write_data;
    globe_read_data = &read_data;
    mlx90640_fd = open(filename, O_RDWR);  
    printf("mlx90640 open mlx90640_fd=%d\n",mlx90640_fd);      
    if(mlx90640_fd > 0){
        MLX90640_SetRefreshRate(mlx90640_fd,RefreshRate);
        MLX90640_SetChessMode(mlx90640_fd);
        MLX90640_DumpEE(mlx90640_fd, eeMLX90640);
        MLX90640_ExtractParameters(eeMLX90640, &mlx90640);
        #if 1
        for(i=0;i<3;i++)
        {
            MLX90640_GetFrameData(mlx90640_fd, frame);
            usleep(150000);
        }        
        #endif
        return mlx90640_fd;
    }
    else{
            printf("Failed to open device %s\n", DEVICE_NAME);  
            return -1;          
    }
}

int MLX90640_Close(int fd)
{
    fflush(stdout);
    close(fd);
    return 0;
}

void MLX90640_Get_Temperature_Data(int fd)
{
    uint16_t double_count = 0;
    uint16_t i=0,j=0;
    float Ta,tr;
    float emissivity=0.95;    
    static float mlx90640To_Temp[768];    
    static float mlx90640To[768];
    uint16_t frame[834];
    
    for(double_count = 0; double_count < 2;){
        if(0x00 == MLX90640_GetFrameData(fd, frame)){
            Ta = MLX90640_GetTa(frame, &mlx90640);        
            tr = Ta - TA_SHIFT;
            printf("mlx90640 Ta:%f tr:%f\n",Ta,tr);
            if(0 == double_count)
                MLX90640_CalculateTo(frame, &mlx90640, emissivity, tr, mlx90640To);
            else
                MLX90640_CalculateTo(frame, &mlx90640, emissivity, tr, mlx90640To_Temp);
                    
            if(1 == double_count){
                //merge data
                for(i = 0,j = 0;i<768;i++){        
                    if(i%32 == 0 && i != 0){
                        j++;
                    }
                    if(0 == j%2 && 1 == i%2){
                        mlx90640To[i] = mlx90640To_Temp[i-1];
                    }
                    else if(1 == j%2 && 0 == i%2){
                        mlx90640To[i] = mlx90640To_Temp[i+1];
                    }
                }
                printf("\n==========================Measure Temperature==========================\n");
                for(i = 0; i < 768; i++){
                    if(i%32 == 0 && i != 0){
                        printf("\n");
                    }
                    printf("%2.2f ",mlx90640To[i]);
                }
                printf("\n==========================Measure Temperature==========================\n");    
            }            
            double_count++;    
        }
    }
    
}

 

int main()  
{  
    int fd;
    mlx90640_fd = MLX90640_Open(DEVICE_NAME);
    
    if(mlx90640_fd < 0){
        return 0;
    }
    
    while(1){
        MLX90640_Get_Temperature_Data(mlx90640_fd);
        sleep(1);
    }

    MLX90640_Close(mlx90640_fd);
 
    return 0;  
}  


这里只贴了主文件,需要注意的是在MLX90640_Get_Temperature_Data中里面的循环,在看mlx90640的时候就可以了解到其工作模式有TV模式跟Chess,如下图:

按照例程给的使用是chess模式,因此每次读取的时候是获取到排列为一个交替一个的,因此在函数MLX90640_Get_Temperature_Data里面做了个混合,取2帧数据合为1帧,这样可以避免0的出现.

最后通过在android终端下运行./system/bin/mlx90640 编译出来的二进制文件即可获取到温度数据.

效果如下图

最后驱动跟测试的代码已经上传到

链接:https://pan.baidu.com/s/1YZo2dxNkNgxitnfKyDXwEA
提取码:7uib

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!