由于疫情的影响,最近在调试测温模块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
#include
#include
#include
#include
#include
#include
#include
#ifdef CONFIG_OF_GPIO
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#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 编译出来的二进制文件即可获取到温度数据.
效果如下图