386|0

282

帖子

7

TA的资源

一粒金砂(高级)

楼主
 

【DigiKey创意大赛】便携生命探测仪03+ILI9488驱动DMA实现和简单界面 [复制链接]

 
【DigiKey创意大赛】便携生命探测仪03+ILI9488驱动DMA实现和简单界面
000

 
上一帖介绍了热成像相机软硬件开发环境搭建,并根据查到的一些资料初步实现了热成像基本功能。但是由于我使用的是以前别的测评留下的屏幕,在刷屏时不能使用DMA导致显示帧率很慢,留下一个问题待解决。参见本帖最后。
本来想着再买个支持SPI 16bit DMA的屏,或者换其他带屏幕的平台开发板。但是作为DIY老鸟,本着电子垃圾能用就绝不浪费的原则,我对开源显示驱动库TFT_eSPI进行详细研究。虽然该库作者Bodmer在GitHub上回复说,即使勉强使用DMA,但是16bit转18bit的CPU开销会抵消掉DMA带来的优势,但是我这个热成像相机的应用和普通的显示不一样,这个需要用到伪彩色,也就是从调色板选部分颜色就行,不用显示全部16bit色彩范围,这就给我留出来操作空间了。
这里说的伪彩色就是选一组渐变色来表示被测物不同的温度,常见的渐变色如下图。
我这里参考Adafruit_MLX90640的库,设置了256色渐变色作为温度标识,效果接近上图第二个。Adafruit_MLX90640的库提供的是16bit调色板,我需要先转换成18bit调色板,考虑ILI9488在DMA传输时是按照字节送数据,我把每个颜色存成3个字节,RGB顺序按照ILI9488的时序来,就能在DMA自动执行数据传送时不用倒腾字节顺序。时序图如下。
然后就是参照TFT_eSPI库重新写一个8bit的DMA送数据函数,如下图。
最后就是显示的时候,先设置显示窗口,再调用这个函数。其中要注意的是,DMA每次传输不能超过64KB字节,我这里为了省内存,把一帧数据分成了16份,远远小于64KB,代码如下图。
经过这样更改后,显示文字和图片还是采用TFT_eSPI库的接口函数,显示热成像图片使用新做的这个函数,既保证了兼容,还提高了效率。实测效果还不错,CPU纯跑刷屏任务可以到每秒十多帧,完全满足热成像的速度了。实际演示效果见开头视频。
显示搞定后,我做了一个简单的界面,如下图。
热成像占据左边大部分屏幕。上面是320*240点阵的热成像动态图,中间是调色板温度范围指示条。最下面显示最高温度。右半边目前留白,后面计划添加另外一个传感器数据。
本次设计采用的是Adafruit_MLX90640的库,具体代码如下。
#ifndef MXL_H
#define MXL_H

#include <Arduino.h>
#include <lcd.h>

#define MINTEMP 26
#define MAXTEMP 35
#define MLX_MIRROR 0           // Set 1 when the camera is facing the screen
#define FILTER_ENABLE 1        // 滤波器使能
#define INTERPOLATION_ENABLE 1 // 差值使能

extern uint8_t CamBuffer[CAM_W * CAM_H];            // 存储量化后的8位像素温度数据
extern bool lock;  // 简单的锁,防止拷贝温度数据的时候对内存的访问冲突

void task_mlx(void *ptr);

#endif // MXL_H

#include <Arduino.h>
#include <mxl.h>
#include <Wire.h>
#include <Adafruit_MLX90640.h>
#include <lcd.h>

#define I2C_SCL SCL
#define I2C_SDA SDA

#define MLX_I2C_ADDR 0x33
Adafruit_MLX90640 mlx;



float frame[32 * 24]; // buffer for full frame of temperatures
float temp_frame[32 * 24];
uint16_t inter_p[320 * 240];

void MxlInit(void)
{
    // I2C init
    Wire.begin(I2C_SDA, I2C_SCL);
    byte error, address;

    Wire.beginTransmission(MLX_I2C_ADDR);
    error = Wire.endTransmission();

    if (error == 0)
    {
        Serial.print(F("I2C device found at address 0x"));
        Serial.print(MLX_I2C_ADDR, HEX);
        Serial.println(F("  !"));
    }
    else if (error == 4)
    {
        Serial.print(F("Unknown error at address 0x"));
        Serial.println(MLX_I2C_ADDR, HEX);
    }

    Serial.println(F("Adafruit MLX90640 Simple Test"));
    if (!mlx.begin(MLX90640_I2CADDR_DEFAULT, &Wire))
    {
        Serial.println(F("MLX90640 not found!"));
        while (1)
            delay(10);
    }

    // mlx.setMode(MLX90640_INTERLEAVED);
    mlx.setMode(MLX90640_CHESS);
    mlx.setResolution(MLX90640_ADC_18BIT);
    mlx90640_resolution_t res = mlx.getResolution();
    mlx.setRefreshRate(MLX90640_16_HZ);
    mlx90640_refreshrate_t rate = mlx.getRefreshRate();
    Wire.setClock(800000); // max 1 MHz

    // image init

    for (int i = 0; i < 320 * 240; i++)
    {
        inter_p[i] = 255;
    }

    Serial.println("image init ok");

    // frame init
    for (int i = 0; i < 32 * 24; i++)
    {
        temp_frame[i] = MINTEMP;
    }
    Serial.println("frame init ok");

    Serial.println(F("All init over."));
}

// 消抖并翻转
// Filter temperature data and change camera direction
void filter_frame(float *in, float *out)
{
    if (MLX_MIRROR == 1)
    {
        for (int i = 0; i < 32 * 24; i++)
        {
            if (FILTER_ENABLE == 1)
                out[i] = (out[i] + in[i]) / 2;
            else
                out[i] = in[i];
        }
    }
    else
    {
        for (int i = 0; i < 24; i++)
            for (int j = 0; j < 32; j++)
            {
                if (FILTER_ENABLE == 1)
                    out[32 * i + 31 - j] = (out[32 * i + 31 - j] + in[32 * i + j]) / 2;
                else
                    out[32 * i + 31 - j] = in[32 * i + j];
            }
    }
}

// float to 0,255
int map_f(float in, float a, float b)
{
    if (in < a)
        return 0;

    if (in > b)
        return 255;

    return (int)((in - a) * 255 / (b - a));
}

// 32*24插值10倍到320*240
// Transform 32*24 to 320 * 240 pixel
void interpolation(float *data, uint16_t *out)
{

    for (uint8_t h = 0; h < 24; h++)
    {
        for (uint8_t w = 0; w < 32; w++)
        {
            out[h * 10 * 320 + w * 10] = map_f(data[h * 32 + w], MINTEMP, MAXTEMP);
        }
    }
    for (int h = 0; h < 240; h += 10)
    {
        for (int w = 1; w < 310; w += 10)
        {
            for (int i = 0; i < 9; i++)
            {
                out[h * 320 + w + i] = (out[h * 320 + w - 1] * (9 - i) + out[h * 320 + w + 9] * (i + 1)) / 10;
            }
        }
        for (int i = 0; i < 9; i++)
        {
            out[h * 320 + 311 + i] = out[h * 320 + 310];
        }
    }
    for (int w = 0; w < 320; w++)
    {
        for (int h = 1; h < 230; h += 10)
        {
            for (int i = 0; i < 9; i++)
            {
                out[(h + i) * 320 + w] = (out[(h - 1) * 320 + w] * (9 - i) + out[(h + 9) * 320 + w] * (i + 1)) / 10;
            }
        }
        for (int i = 0; i < 9; i++)
        {
            out[(231 + i) * 320 + w] = out[230 * 320 + w];
        }
    }
}

// Quick sort
void qusort(float s[], int start, int end) // 自定义函数 qusort()
{
    int i, j;        // 定义变量为基本整型
    i = start;       // 将每组首个元素赋给i
    j = end;         // 将每组末尾元素赋给j
    s[0] = s[start]; // 设置基准值
    while (i < j)
    {
        while (i < j && s[0] < s[j])
            j--; // 位置左移
        if (i < j)
        {
            s[i] = s[j]; // 将s[j]放到s[i]的位置上
            i++;         // 位置右移
        }
        while (i < j && s[i] <= s[0])
            i++; // 位置左移
        if (i < j)
        {
            s[j] = s[i]; // 将大于基准值的s[j]放到s[i]位置
            j--;         // 位置左移
        }
    }
    s[i] = s[0]; // 将基准值放入指定位置
    if (start < i)
        qusort(s, start, j - 1); // 对分割出的部分递归调用qusort()函数
    if (i < end)
        qusort(s, j + 1, end);
}

// The camera allows up to four non-consecutive bad points with a value of nan.
void bug_fix(float *frame)
{
    for (int i = 0; i < 768 - 1; i++)
    {
        if (isnan(frame[i]))
            frame[i] = frame[i + 1];
    }
}

uint32_t runtime = 0;
int fps = 0;
float max_temp = 0.0;
int record_index = 0;

// 热成像读取多任务
void task_mlx(void *ptr)
{
    int i = 0;

    MxlInit();

    while (true)
    {
        // 获取一帧
        // Get a frame
        if (mlx.getFrame(frame) != 0)
        {
            Serial.println(F("Get frame failed"));
            return;
        }

        bug_fix(frame);

        // 和上次结果平均,滤波
        // Filter temperature data
        filter_frame(frame, temp_frame);

        // 快排
        qusort(frame, 0, 32 * 24 - 1);
        max_temp = frame[767];
        MaxTemp = max_temp;
        // Serial.println(max_temp);
        if (INTERPOLATION_ENABLE == 1)
        {
            // 温度矩阵转换图像矩阵,将32*24插值到320*240
            // Display with 320*240 pixel
            interpolation(temp_frame, inter_p);
            lock = true;
            i = 0;
            while (i < 76800)
            {
                CamBuffer[i] = inter_p[i];
                i++;
            }
            lock = false;
        }
        else
        {
            // Display with 32*24 pixel
            for (uint8_t h = 0; h < 24; h++)
            {
                for (uint8_t w = 0; w < 32; w++)
                {
                    uint8_t colorIndex = map_f(temp_frame[h * 32 + w], MINTEMP, MAXTEMP);
                    for (uint8_t y = 0; y < 10; y++)
                    {
                        for (uint8_t x = 0; x < 10; x++)
                        {
                            CamBuffer[(h * 10 + y) * 320 + w * 10 + x] = colorIndex;
                        }
                    }
                }
            }
        }
    }
    vTaskDelete(NULL);
}

 

目前还有一个问题,就是屏幕显示的温度图像有纵向干扰条纹,我用了不同库读取数据都存在,初步怀疑是传感器的问题,但是手头只有这一个宝贝,还没办法验证,等我再查查资料,看有没有解决办法。
下一步添加另外一个传感器Adafruit BME680的读取和显示。传感器如下图。
点赞 关注
 
 

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表