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

в 15:49, , рубрики: diy или сделай сам, гаджеты, прибор ночного видения, программирование микроконтроллеров, схемотехника, тепловизор

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

Для сборки собственного тепловизионного прибора ночного видения понадобится:
1) Плата с микроконтроллером. Я взял плату от китайских товарищей с микроконтроллером STM32F407VGT6. Хороший такой контроллер: 168 МГц частота и 192 КБ ОЗУ.
Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3 - 1
2) Дисплей. Я взял дисплей с разрешением 320x240. Такие дисплеи бывают с различными контроллерами. Мне достался с контроллером hx8347d.
Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3 - 2
3) Плата для подключения лептона 3 по SPI и I2C.
Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3 - 3
4) Сам лептон 3. Самый труднодоставаемый и дорогой элемент. Чтобы его получить, я купил неисправный тепловизор Flir One Gen 2 на ebay и вынул лептон из него. Выглядит в увеличении он вот так:
Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3 - 4
Из данного списка можно исключить пункт 3, если, конечно, вам удастся взять из неисправного тепловизора кроватку под лептон и вы сможете её распаять (а контакты у неё, к слову, будут снизу). К сожалению, расстояние между ножками у лептона достаточно маленькое, поэтому мне этот вариант не покорился.

Чтобы всё это собрать, потребуется просто спаять всё это следующим образом:
Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3 - 5

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

Всё вместе собранное выглядит вот так:
Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3 - 6
Прибор ночного видения на базе тепловизионного модуля Flir Lepton 3 - 7

Для работы с лептоном потребуется программа. Я использовал 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.

Автор: da-nie

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js