Подключение тепловизора Seek Thermal к STM32

Подключить тепловизор к микроконтроллеру? Без проблем! Особенно если это STM32 с интерфейсом USB Host и тепловизор Seek Thermal от Даджет!
Подключение тепловизора Seek Thermal к STM32
Паяльник глазами тепловизора SeekThermal

Введение

Думаю, что все сталкивались с такими гаджетами как тепловизор, ну хотя бы читали о них. И среди этих устройств есть целый подкласс гаджетов, которые не являются самостоятельным устройством, а служат чем-то вроде приставки с компьютеру или смартфону.
Сегодня речь пойдёт о подключении тепловизора Seek Thermal к микроконтроллеру STM32. А предоставила мне данное устройство компания Даджет. На просторах Geektimes данный тепловизор рассматривался не раз: освещалась, в основном, его работа с Андройд, а также проскакивала статья о подключении данного устройства к ПК. В своём обзоре я хочу рассказать о собственном опыте подключения тепловизора Seek Thermal к микроконтроллеру STM32 через USB хост.

Аппаратные требования

Не такие уж и специфические! Всё что должен иметь Ваш STM32 — это USB интерфейс, способный работать в режиме Host и какой-нибудь интерфейс для управления ЖК экраном. Самый очевидный выбор — это взять STM32F4 — Discovery. У меня под рукой оказалась плата STM32F746G-Discovery. Соответственно описание будет для этой платы, но! Т.к. код сгенерирован в среде CubeMX, возможно применить и другую EVM. Считаю прменённую мной плату избыточной для данного проекта.

Программная часть

Данный тепловизор не реализует какой-либо класс при общении по USB. Всё взаимодействие реализовано напрямую bulk-запросами через эндпойнты. Отправляя данные на определённый эндпойнт, можно включить тепловизор, откалибровать его, и заставить передать кадр, или несколько кадров. Особенно подробно работа с Seek Thermal описана на данном форуме.
Таким образом, для работы тепловизора с микроконтроллером STM32, нам необходимо:
1) Взять любой пример USB Host для Вашей любимой платы (я взял STM32 USB Host CDC example из коллекции примеров STM32F7 CubeMX);
2) Выкинуть оттуда процедуру инициализации класса устройства;
3) Написать удобные обёртки для работы с функциями чтения/записи в управляющие эндпойнты и эндпойнты данных;
4) Написать свою функцию по преобразованию сырых данных в нечто отображаемое;
5) Задействовать LUT (color Look Up Table) для раскрашивания монохромной картинки в цветную. Эта фитча появилась в семействе микроконтроллеров STM32, которые могут самостоятельно управляться с ЖК экранами.
Для начала сделаем что-то похожее на кусочек из libusb, который поможет нам связать HAL Library с последующим кодом:

Код процедуры из libusb

int libusb_control_transfer(libusb_device_handle* dev_handle,                             uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,                             unsigned char* data, uint16_t wLength, unsigned int timeout) {      hUSBHost.Control.setup.b.bmRequestType = request_type;     hUSBHost.Control.setup.b.bRequest      = bRequest;     hUSBHost.Control.setup.b.wValue.w      = wValue;     hUSBHost.Control.setup.b.wIndex.w      = wIndex;     hUSBHost.Control.setup.b.wLength.w     = wLength;      int status;          do {         status = USBH_CtlReq(&hUSBHost, data, wLength);     } while (status == USBH_BUSY);      if (status != USBH_OK)  {         hUSBHost.RequestState = CMD_SEND;         return 0;      } else {         return wLength;     }  } 

Затем сходим сюда и подсмотрим процедуру vendor_transfer. Также, не помешает обратить внимание на список запросов struct Request.

код процедуры vendor_transfer

int vendor_transfer(bool direction, uint8_t req, uint16_t value, uint16_t index, uint8_t * data, uint8_t size, int timeout) { 	int res; 	uint8_t bmRequestType = (direction ? LIBUSB_ENDPOINT_IN : LIBUSB_ENDPOINT_OUT) 	 | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_INTERFACE; 	uint8_t bRequest = req; 	uint16_t wValue = value; 	uint16_t wIndex = index;   	uint8_t * aData = data; 	uint16_t wLength = size;  	if (!direction) { 		// to device #ifdef LOG_DEBUG 		USBH_UsrLog("ctrl_transfer(0x%x, 0x%x, 0x%x, 0x%x, %d)", bmRequestType, bRequest, wValue, wIndex, wLength); 		printf(" ["); 		for (int i = 0; i < wLength; i++) { 			printf(" %02x", data[i]); 		} 		printf(" ]n"); #endif 		res = libusb_control_transfer(handle, bmRequestType, bRequest, wValue, wIndex, aData, wLength, timeout); #ifdef LOG_DEBUG 		if (res != wLength) { 							USBH_UsrLog("Bad returned length: %dn", res); 						} #endif 	} 	else { 		// from device #ifdef LOG_DEBUG 		USBH_UsrLog("ctrl_transfer(0x%x, 0x%x, 0x%x, 0x%x, %d)", 				 bmRequestType, bRequest, wValue, wIndex, wLength); #endif 				res = libusb_control_transfer(handle, bmRequestType, bRequest, 				 wValue, wIndex, aData, wLength, timeout); #ifdef LOG_DEBUG 				if (res != wLength) { 					USBH_UsrLog("Bad returned length: %dn", res); 				} 				printf(" -> ["); 				for (int i = 0; i < res; i++) { 					printf(" %02x", data[i]); 				} 				printf(" ]n"); #endif 	} 	return res; } 

Далее, напишем процедуру приёма картинки. Тут особо комментировать нечего, подсмотрели в CDC Example.

Процедура приёма данных по USB

int CAM_ProcessReception(USBH_HandleTypeDef *phost) { USBH_URBStateTypeDef URB_Status = USBH_URB_IDLE; uint16_t length = 0; uint8_t data_rx_state = CDC_RECEIVE_DATA;  size = FRAME_WIDTH * FRAME_HEIGHT; int bufsize = size * sizeof(uint16_t);  int bsize = 0; while (data_rx_state != CDC_IDLE) {   switch(data_rx_state)   {    case CDC_RECEIVE_DATA:      USBH_BulkReceiveData (phost, &rawdata[bsize], 512, InPipe);      data_rx_state = CDC_RECEIVE_DATA_WAIT;      break;    case CDC_RECEIVE_DATA_WAIT:      URB_Status = USBH_LL_GetURBState(phost, InPipe);      /*Check the status done for reception*/     if(URB_Status == USBH_URB_DONE )     {       length = USBH_LL_GetLastXferSize(phost, InPipe);        bsize+= length;        if(((bufsize - length) > 0) && (bsize < bufsize)) //TODO       {          data_rx_state = CDC_RECEIVE_DATA;       }       else       {         data_rx_state = CDC_IDLE;        } #if (USBH_USE_OS == 1)       osMessagePut ( phost->os_event, USBH_CLASS_EVENT, 0); #endif     }     break;    default:      break;   } }   return data_rx_state; } 

Также, нам понадобится как-то рисовать полученные данные на экране. Замечу, что в 20-м байте данных, представляющих из себя 16-битный массив пикселов, хранится информация о типе кадра. Кадров бывает несколько типов. Нас интересует калибровочный кадр и рабочий кадр. Калибровочный кадр получается тогда, когда тепловизор закрывает шторку и делает снимок «темноты». При съёмке обычного кадра шторка открыта. Т.о. при работе Вы всегда слыште как девайс щёлкает шторкой.

Процедура отрисовки изображения на экране

void BSP_LCD_DrawArray(uint32_t Xpos, uint32_t Ypos, uint32_t width, uint32_t height, uint8_t bit_pixel, uint8_t *pbmp) {   uint32_t index = 0;   uint32_t index2 = 0;  // uint32_t address;   //uint32_t input_color_mode = 0;   //uint32_t Color;   static int pixel;   static int calib_pixel=0;   uint8_t Component;   static int v;   uint8_t frame_type;  frame_type = *(__IO uint8_t *) (pbmp + 20);  	switch (frame_type) { case 6: 	calib_pixel = (*(uint16_t*)pbmp); 	minpixel = calib_pixel; 	//calib_pixel = bswap_16(calib_pixel); 	break;  case 3: 	  /* Convert picture to ARGB8888 pixel format */ 	  for(index=0; index < height; index++) 	    {  		  for(index2=0; index2 < width; index2++) 		  {  			  pixel = (*(uint16_t*)pbmp);  			  //pixel = bswap_16(pixel); 			  //v = pixel - calib_pixel; 			  //v += 0x8000;  			  if (maxpixel < pixel) 				  maxpixel = pixel;  			  if (minpixel > pixel) 				  minpixel = pixel;  			  if (pixel < 0) { 				  pixel = 0; 			  	}  			  if (pixel > 0xFFFF) { 			  	pixel = 0xFFFF; 			  	}  			  v = map(pixel, 6000, 13000, 0, 255);  			 //v = (v - MAX) * 255 / (MIN - MAX);  			 if (v < 0) 				 v = 0; 			 if (v > 255) 				 v = 255;  			  BSP_LCD_DrawPixel(index2+270, index+100, (0xFF << 24) | (uint8_t)v << 16 | (uint8_t)v << 8 | (uint8_t)v); 			  pbmp += 2; 		  } 	    }   	break;  case 4:  	break;  						}  } 

Наконец, главный цикл, из которого видно — где чего обрезали, где чего вставили.

Главный цикл

#define DELAY1 10 #define USB_PIPE_NUMBER 0x81 #define FRAME_WIDTH 208 #define FRAME_HEIGHT 156  uint8_t OutPipe, InPipe; uint8_t usb_device_state; uint8_t rawdata[FRAME_HEIGHT*FRAME_WIDTH*2]; uint8_t data[64]; USBH_StatusTypeDef status; uint8_t transf_size; int size;  int main(void) {   /* Enable the CPU Cache */   CPU_CACHE_Enable();    /* STM32F7xx HAL library initialization:        - Configure the Flash ART accelerator on ITCM interface        - Configure the Systick to generate an interrupt each 1 msec        - Set NVIC Group Priority to 4        - Low Level Initialization      */   HAL_Init();    /* Configure the System clock to have a frequency of 200 MHz */   SystemClock_Config();    /* Init CDC Application */   CDC_InitApplication();    /* Init Host Library */   USBH_Init(&hUSBHost, USBH_UserProcess, 0);    /* Add Supported Class */   //USBH_RegisterClass(&hUSBHost, USBH_CDC_CLASS);    /* Start Host Process */   USBH_Start(&hUSBHost);    /* Run Application (Blocking mode) */   while (1)   {     /* USB Host Background task */     USBH_Process(&hUSBHost);      if (hUSBHost.gState == HOST_CHECK_CLASS) {      	switch (usb_device_state) {     	case 1:     		status = USBH_Get_StringDesc(&hUSBHost,hUSBHost.device.DevDesc.iManufacturer, data , 64);     		if (status == USBH_OK) {     			USBH_UsrLog("## Manufacturer : %s",  (char *)data);     			HAL_Delay(1000);     			usb_device_state = 1;     		}     		break;     	case 2:     		status = USBH_Get_StringDesc(&hUSBHost, hUSBHost.device.DevDesc.iProduct, data , 64);      		if (status == USBH_OK) {     		    			USBH_UsrLog("## Product : %s",  (char *)data);     		    			HAL_Delay(1000);     		    			usb_device_state = 2;     		    		}     		break;     	case 0:      		InPipe = USBH_AllocPipe(&hUSBHost, 0x81);       		    	              status = USBH_OpenPipe(&hUSBHost,     		    	                                          InPipe,     		    	                                          0x81,     		    	                                          hUSBHost.device.address,     		    	                                          hUSBHost.device.speed,     		    	                                          USB_EP_TYPE_BULK,     		    	                                          USBH_MAX_DATA_BUFFER);      		    	              if (status == USBH_OK)     		    	            	  usb_device_state = 3;      		break;     	case 3:     		HAL_Delay(1);      		const uint8_t data0[2] = {0x00, 0x00};     		vendor_transfer(0, SET_OPERATION_MODE, 0, 0, data0, 2);     		vendor_transfer(0, SET_OPERATION_MODE, 0, 0, data0, 2);     		vendor_transfer(0, SET_OPERATION_MODE, 0, 0, data0, 2);      		data[0] = 0x01;     		vendor_transfer(0, TARGET_PLATFORM, 0, 0, data, 1);      		data[0] = 0x00;     		data[1] = 0x00;     		vendor_transfer(0, SET_OPERATION_MODE, 0, 0, data);      		transf_size = vendor_transfer(1, GET_FIRMWARE_INFO, 0, 0, data, 4);      		transf_size = vendor_transfer(1, READ_CHIP_ID, 0, 0, data, 12);      		const uint8_t data1[6] = { 0x20, 0x00, 0x30, 0x00, 0x00, 0x00 };     		vendor_transfer(0, SET_FACTORY_SETTINGS_FEATURES, 0, 0, data1, 6);      		transf_size = vendor_transfer(1, GET_FACTORY_SETTINGS, 0, 0, data, 64);      		const uint8_t data2[6] = { 0x20, 0x00, 0x50, 0x00, 0x00, 0x00 };     		vendor_transfer(0, SET_FACTORY_SETTINGS_FEATURES, 0, 0, data2, 6);      		transf_size = vendor_transfer(1, GET_FACTORY_SETTINGS, 0, 0, data, 64);      		const uint8_t data3[6] = { 0x0c, 0x00, 0x70, 0x00, 0x00, 0x00 };     		vendor_transfer(0, SET_FACTORY_SETTINGS_FEATURES, 0, 0, data3, 6);      		transf_size = vendor_transfer(1, GET_FACTORY_SETTINGS, 0, 0, data, 24);      		const uint8_t data4[6] = { 0x06, 0x00, 0x08, 0x00, 0x00, 0x00 };     		vendor_transfer(0, SET_FACTORY_SETTINGS_FEATURES, 0, 0, data4, 6);      		vendor_transfer(1, GET_FACTORY_SETTINGS, 0, 0, data, 12);      		const uint8_t data5[2] = { 0x08, 0x00 };     		vendor_transfer(0, SET_IMAGE_PROCESSING_MODE, 0, 0, data5, 2);      		vendor_transfer(1, GET_OPERATION_MODE, 0, 0, data,2);      		const uint8_t data6[2] = { 0x08, 0x00 };     		vendor_transfer(0, SET_IMAGE_PROCESSING_MODE, 0, 0, data6, 2);      		const uint8_t data7[2] = { 0x01, 0x00 };     		vendor_transfer(0, SET_OPERATION_MODE, 0, 0, data7, 2);      		vendor_transfer(1, GET_OPERATION_MODE, 0, 0, data, 2);      		USBH_UsrLog("SeeK Thermal Init Done.n");      		size = FRAME_WIDTH * FRAME_HEIGHT;      		int bufsize = size * sizeof(uint16_t);     		status = CDC_IDLE;     		usb_device_state = 4;     		break;     	case 4:     		//while(1 ){     		 // request a frame     		data[0] = (uint8_t)(size & 0xff);     		data[1] = (uint8_t)((size>>8)&0xff);     		data[2] = 0;     		data[3] = 0;      		if (status == CDC_IDLE)     		vendor_transfer(0, 0x53, 0, 0, data, 4);     		                 status = CAM_ProcessReception(&hUSBHost);      		if (status == CDC_IDLE)                 BSP_LCD_DrawArray(10, 10, FRAME_WIDTH, FRAME_HEIGHT, 16, rawdata);      		usb_device_state = 4;      		break;      	}     	          }   } } 

Заключение
Работа тепловизора с микроконтроллером выглядит намного шустрее, чем со смартфоном. Рекомендую данный даджет для оценки тепловой картины электронных устройств. Тепловизор имеет настраиваемое фокусное расстояние, что позволяет рассматривать даже отдельные электронные компоненты на плате! В заключение видеоролик, из которого можно оценить скорость работы тепловизора (где-то 8-9 fps)

Информация для потенциальных покупателей

С 10% скидкой приобрести Тепловизор Seek Thermal можно, указав промокод GEEKT-ST2

Источник

seekthermal, даджет, тепловизор

Читайте также