Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3

    Ранее я написал статью о подключении к компьютеру тепловизионной приставки к смартфону Flir One Gen 2. Пришла пора вынуть из этой приставки модуль лептон и подключить к микроконтроллеру напрямую, собрав прибор ночного видения с разрешением 160x120 пикселей.

    Для сборки собственного тепловизионного прибора ночного видения понадобится:

    1) Плата с микроконтроллером. Я взял плату от китайских товарищей с микроконтроллером STM32F407VGT6. Хороший такой контроллер: 168 МГц частота и 192 КБ ОЗУ.



    2) Дисплей. Я взял дисплей с разрешением 320x240. Такие дисплеи бывают с различными контроллерами. Мне достался с контроллером hx8347d.



    3) Плата для подключения лептона 3 по SPI и I2C.



    4) Сам лептон 3. Самый труднодоставаемый и дорогой элемент. Чтобы его получить, я купил неисправный тепловизор Flir One Gen 2 на ebay и вынул лептон из него. Выглядит в увеличении он вот так:



    Из данного списка можно исключить пункт 3, если, конечно, вам удастся взять из неисправного тепловизора кроватку под лептон и вы сможете её распаять (а контакты у неё, к слову, будут снизу). К сожалению, расстояние между ножками у лептона достаточно маленькое, поэтому мне этот вариант не покорился.

    Чтобы всё это собрать, потребуется просто спаять всё это следующим образом:



    Также надо подключить питание. Для питания платы лептона я использую 5 В, для платы STM32 3.3 В. Для получения 5 В от батареи я использую преобразователь TEL3-0511 (входное напряжение от 4.5 до 9 В), а уже эти 5 В понижаю на обычном LP2950CZ-3.3 (кстати, греется до 70 градусов. Тут бы тоже нужно применить DC/DC конвертер, но я его ещё не купил). Кушает лептон 3, между прочим, хорошо. При питании от 6 В ток потребления всем устройством составляет около 250 мА. Когда же лептону захочется щёлкнуть шторкой для калибровки, ток возрастает до 500 мА.

    Всё вместе собранное выглядит вот так:





    Для работы с лептоном потребуется программа. Я использовал CubeMX и Keil 5. В этом случае вся программная обвязка упрощается до невозможности.

    Связь с лептоном осуществляется по SPI. I2C я ещё не использовал, так как особой надобности в нём не было. По I2C можно управлять состоянием лептона, его режимами работы, включать/отключать режим автоматической калибровки и так далее. Но для прибора ночного видения это не особо нужно.

    Для расшифровки данных я написал модуль:

    Модуль
    leptoncontrol.h
    #ifndef LEPTON_CONTROL_H
    #define LEPTON_CONTROL_H
    
    #include <stdbool.h>
    #include <stdio.h>
    
    //исходные размеры изображения (не перевёрнутое)
    #define LEPTON_ORIGINAL_IMAGE_WIDTH 160
    #define LEPTON_ORIGINAL_IMAGE_HEIGHT 120
    
    //высота кадра VoSPI
    #define VOSPI_FRAME_HEIGHT 60
    //ширина кадра VoSPI
    #define VOSPI_FRAME_WIDTH 80
    //размер пакета VoSPI а байтах (164 для RAW14 и 244 для RGB)
    #define VOSPI_PACKAGE_SIZE 164
    //размер строки пакета VoSPI в байтах
    #define VOSPI_PACKAGE_LINE_SIZE 160
    //размер сегмента VOSPI в байтах
    #define VOSPI_SEGMENT_LINE_AMOUNT 60
    
    void LEPTONCONTROL_Init(void);//инициализация
    void LEPTONCONTROL_CalculateCRC(unsigned short *crc,unsigned char byte);//вычислить crc
    bool LEPTONCONTROL_PushVoSPI(unsigned char data[VOSPI_PACKAGE_SIZE],bool *first_line);//подать данные одного пакета VoSPI на вход модуля
    unsigned short *LEPTONCONTROL_GetRAW14Ptr(void);//получить указатель на данные собранного изображения
    #endif
    

    leptoncontrol.c

    #include "leptoncontrol.h"
    #include "stm32f4xx_hal.h"
    
    static unsigned short RAW14Image[LEPTON_ORIGINAL_IMAGE_HEIGHT*LEPTON_ORIGINAL_IMAGE_WIDTH];//собираемое изображение
    static unsigned short CRCTable[256];//таблица для расчета CRC16
    
    //время для ресинхронизации в мс
    #define RESYNC_TIMEOUT_MS 19
    //код: нет сегмента
    #define NO_SEGMENT -1
    //код: ошибка пакета
    #define ERROR_PACKAGE -2
    
    //----------------------------------------------------------------------------------------------------
    //инициализация
    //----------------------------------------------------------------------------------------------------
    void LEPTONCONTROL_Init(void)
    {
     //инициалдизируем таблицу для вычисления CRC
     unsigned short code;
     for(long n=0;n<256;n++)
     {
      code=((unsigned short)n)<<8;
      for(unsigned char m=0;m<8;m++)
      {
       if(code&(1<<15)) code=(code<<1)^0x1021;
                   else code=code<<1;
      }
      CRCTable[n]=code;
     }
    }
    
    //----------------------------------------------------------------------------------------------------
    //вычислить crc
    //----------------------------------------------------------------------------------------------------
    void LEPTONCONTROL_CalculateCRC(unsigned short *crc,unsigned char byte)
    {
     *crc=CRCTable[(((*crc)>>8)^byte++)&0xFF]^((*crc)<<8);
    }
    
    //----------------------------------------------------------------------------------------------------
    //----------------------------------------------------------------------------------------------------
    long LEPTONCONTROL_ReadSegment(unsigned short *raw14_ptr,unsigned char data[VOSPI_PACKAGE_SIZE],bool *first_line)
    {
     static long current_package=-1;
     static long segment=-1;
     long n;
     *first_line=false;
     if ((data[0]&0x0F)==0x0F) return(NO_SEGMENT);//отбрасываемый пакет
     unsigned short crc=data[2];
     crc<<=8;
     crc|=data[3];
     //считаем CRC
     unsigned short crc16=0;
     LEPTONCONTROL_CalculateCRC(&crc16,data[0]&0x0F);
     LEPTONCONTROL_CalculateCRC(&crc16,data[1]);
     LEPTONCONTROL_CalculateCRC(&crc16,0);
     LEPTONCONTROL_CalculateCRC(&crc16,0);
     for(n=4;n<VOSPI_PACKAGE_SIZE;n++) LEPTONCONTROL_CalculateCRC(&crc16,data[n]);
     if (crc16!=crc) return(ERROR_PACKAGE);//ошибка CRC
    
     //определяем номер пакета
     unsigned short package=data[0]&0x0F;
     package<<=8;
     package|=data[1];
     if (package==0)
     {
      *first_line=true;
      current_package=0;
     }
     if (package==20)
     {
      unsigned char ttt=(data[0]&0x70)>>4;//номер кадра бывает только в 20 пакете
      segment=ttt;
     }
     if (current_package<0) return(NO_SEGMENT);
     if (current_package!=package)
     { 
      current_package=-1;
      return(ERROR_PACKAGE);
     }
     unsigned short *raw_ptr=raw14_ptr+current_package*VOSPI_PACKAGE_LINE_SIZE/2;
     for(n=0;n<VOSPI_PACKAGE_LINE_SIZE/2;n++,raw_ptr++)
     {
      //байты заданы в порядке big-endian: старший, младший
      unsigned short value=data[n*sizeof(short)+4];
      value<<=8;
      value|=data[n*sizeof(short)+5];
      *raw_ptr=value;
     }
     current_package++;
     if (current_package!=VOSPI_FRAME_HEIGHT) return(NO_SEGMENT);
     current_package=-1;
     return(segment); 
    }
    
    //----------------------------------------------------------------------------------------------------
    //подать данные одного пакета VoSPI на вход модуля
    //----------------------------------------------------------------------------------------------------
    bool LEPTONCONTROL_PushVoSPI(unsigned char data[VOSPI_PACKAGE_SIZE],bool *first_line)
    {
     *first_line=false;
     static long waitable_segment=1;
     long segment=LEPTONCONTROL_ReadSegment(RAW14Image+(waitable_segment-1)*VOSPI_FRAME_WIDTH*VOSPI_SEGMENT_LINE_AMOUNT,data,first_line);
     if (segment==ERROR_PACKAGE) HAL_Delay(RESYNC_TIMEOUT_MS);
     if (segment==ERROR_PACKAGE || segment==0) waitable_segment=1;
     if (segment==ERROR_PACKAGE || segment==NO_SEGMENT || segment==0) return(false);
     
     if (segment!=waitable_segment)
     {
      waitable_segment=1;
      if (segment!=1) return(false);
     }
     waitable_segment++;
     if (waitable_segment!=5) return(false);
     waitable_segment=1; 
     return(true);
    }
    //----------------------------------------------------------------------------------------------------
    //получить указатель на данные собранного изображения
    //----------------------------------------------------------------------------------------------------
    unsigned short *LEPTONCONTROL_GetRAW14Ptr(void)
    {
     return(RAW14Image);
    }
    


    Вся работа с этим модулем заключается в простой подаче данных, полученных по SPI.

    Работа с модулем
    
    while(1)
    	{
       //ищем начало кадра		
       while(1) 
       {
        HAL_SPI_Receive(&hspi1,buffer,VOSPI_PACKAGE_SIZE,0x1000);			 
        bool first_line=false;
        unsigned char *buffer_ptr=buffer;
        LEPTONCONTROL_PushVoSPI(buffer_ptr,&first_line);
        if (first_line==true) break;
       }
       //читаем остаток пакета от lepton3
       unsigned char *buffer_ptr=buffer;
       HAL_SPI_Receive(&hspi1,buffer_ptr,VOSPI_PACKAGE_SIZE*SPI_READ_VOSPI_AMOUNT,0x1000);
       buffer_ptr=buffer;
       for(long n=0;n<SPI_READ_VOSPI_AMOUNT;n++,buffer_ptr+=VOSPI_PACKAGE_SIZE) 
       {
        bool first_line=false;
        bool res=LEPTONCONTROL_PushVoSPI(buffer_ptr,&first_line);
        if (res==true) CreateImage();//расшифровываем данные и рисуем изображение
       } 
      }	
    


    После сборки кадра, показания датчика просто нормируются, приводятся к диапазону [0..255] и отображаются на дисплее в виде градаций серого. Впрочем, ничто не мешает использовать и любую палитру для раскраски изображения.

    Для вывода изображения на дисплей я использую встроенный в этот контроллер модуль FSMC в режиме шины данных 8 бит.

    Полностью программу можно скачать тут.

    Видео работы (к сожалению, снимал на старый фотоаппарат с соответствующим качеством- видеокамеры у меня сейчас при себе нет).


    P.S. Между прочим, можно подключить приставку-тепловизор Flir One Gen 2 к отладочной плате STM32F407Discovery прямо по USB. Однако, соединение получается нестабильное — тепловизор часто теряется.


    Программа для такого подключения вот тут. Может быть, кто-нибудь поймёт, в чём там дело и как сделать соединение устойчивым.

    Так же данный модуль лептон 3 легко и просто подключается к Raspberry Pi.


    В данном случае мне пришлось доработать программу из репозитория, сделав свою версию, работающую с лептоном 3.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 19

      0
      я купил неисправный тепловизор Flir One Gen 2

      И почем обошелся?
        +4
        Почти 9 тысяч с пересылкой (пересылка около 1000 стоила). На ebay их часто выбрасывают в продажу. Некоторые, говорят, удаётся починить простым полным перезапуском. Мой от такого не ожил, но я его и так покупал для разборки.
          0
          Проблем с пересылкой не возникло? Я как-то полгода назад хотел заказать Seek Thermal с Amazon. Уже сделал заказ, все оплатил и все было хорошо, напрямую доставка в РФ. Но на следующий день мне написал менеджер, и сказал, что «Мой друг, извини, но моя американская таможня не пропустит тепловизор в Россию!». И заказ отменился, и деньги вернулись на карточку…
            0
            С проблемами я не сталкивался. Я неисправный Flir One Gen 2 заказывал из Германии в январе этого года. Пришёл относительно быстро. А рабочий Flir One Gen 2 (у меня он тоже есть) я заказывал из США полтора года назад. Тоже никаких проблем не возникло. Но я заказывал напрямую у продавцов с ebay.
              +1
              Спасибо, надо будет попробовать тоже через Ebay. Уж больно цены в локальных магазинах кусаются. Для пересылки использовали курьерские службы или обычную почту? Обычной почтой могло «проскочить», курьерские же все досматривают вроде бы, в случае с Amazon был как раз второй вариант…
                +1
                Да, использовал обычную почту.
        +3
        Дисплей с пятью сенсорными клавишами что-то знакомый. Не в «Ноклы» ли его в своё время ставили?
          0
          Вряд ли. Он, скорее, для ардуины сделан. :) Их таких на алиэкспрессе тьма.
            +4

            Аха, для ардуины специально сделали с пиктограммами типа "отправить сообщение" )
            Наоборот же, типовые дисплеи, которые ставили в "ноклы", пылились на складе и сейчас их стали использовать в более востребованном сегменте.

              0
              Посмотрел на фото этой ноклы. Да, очень похоже.
                +1
                А как поняли, что на них спрос не иссякнет, так решили и с производства не снимать.
                  0
                  Скорее, с разборки. Брал на ebay шилд со вторичным экраном от нокии (правда монохромной).
            0

            Ясности ради, прибор ночного видения (ПНВ) и тепловизор — устройства различного класса и внутреннего устройства.

              +5
              Это не совсем так. Дело в том, что тепловизор также относится к ПНВ. От ПНВ требуется только лишь позволять видеть в условиях недостаточной освещённости. Это можно делать на самых разных принципах. Посмотрите на википедии статью про ПНВ:
              Наблюдение в среднем (тепловом) инфракрасном диапазоне (длина волны 7..15 мкм). В этом диапазоне излучают все твёрдые тела, нагретые до температур нашего мира: от −50 градусов Цельсия и выше. Такие ПНВ называются тепловизорами. Они показывают картинку разницы температур и не требуют никакой подсветки.

              Просто обычно ПНВ называют устройства на ЭОП с работой в ближнем ИК-диапазоне. Но это далеко не все типы ПНВ.
                +1
                Кстати, вот в конкретном описываемом ПНВ на базе тепловизора есть одна проблема, которая не позволяет мне измерять температуру и сделать измерительный тепловизор или сделать фильтрацию температур (чтобы убрать засветку от слишком горячих предметов и выделять только биологические объекты). В изначальной статье я написал о том, как пересчитать raw14 от Flir One Gen 2 в температуру. Это работает. Но это не работает с прямыми данными с самого датчика лептон. Получаются фантастические цифры. Судя по всему, Flir One Gen 2 выполняет какие-то преобразования с показаниями лептона с учётом температуры датчика. Но вот какие именно — вопрос.
                0

                Вопрос к автору. Как вы считаете, каким образом вы получили большую часть знаний, тн. базу знаний для осуществления данного проекта — университет/книги/работа/что_либо_ещё?

                  +2
                  Так данный проект вообще не требует каких-то специальных знаний. Посмотрите, по сути всё свелось к соединению готовых блоков. Что касается программы, то с CubeMX и интернетом её напишет даже тот, кто впервые видит stm32 и немножко владеет обычным Си.

                  А так — в университете я изучал электронику (по большей части, квантовую (лазеры и подобное), но два семестра была и аналоговая с цифровой схемотехника), сам по себе я занимаюсь попытками разработки ПО с 1994 года (когда мне купили спектрум :) ) по разным книжкам (на курсы я не ходил). Также я с давних пор занимался радиолюбительством (в школе — без особого понимания, как это всё работает. Ну хоть в институте немного разъяснили, что к чему, но только азы). Но особым профессионалом ни там, ни там я не стал (сделать аналоговую не типовую схему (с расчётами по полному циклу — устойчивость обратной связи и учёт всяких нелинейных эффектов) я не умею, ровно как и не представляю современную разработку ПО — читаю тут статьи и мало что понимаю в них). Вот и остаётся делать разве что такие вот проекты для собственного интереса. :)

                  Как видите, всего понемножку. :)
                  0

                  Я не понял, как это Лептон работает через SPI с закороченными на землю сигналами MISO и CS?

                    +3
                    Не MISO, а MOSI. Потому что лептону ничего не требуется передавать от STM32. А CS надо на землю подключать, чтобы просто выбрать модуль для общения.

                  Only users with full accounts can post comments. Log in, please.