将Seek热像仪连接到STM32

将热像仪连接到微控制器? 没问题! 特别是如果它是带有USB主机接口的STM32和Dadget的Seek热成像仪!


通过SeekThermal热成像仪的眼睛焊接烙铁

引言


我认为每个人都曾经遇到过像热成像仪这样的小工具,至少他们已经读过了。 在这些设备中,有整个小配件的子类,它们不是独立的设备,而是用作计算机或智能手机的机顶盒。

今天,我们将讨论将Seek热成像仪连接到STM32微控制器。 而Dadget公司为我提供了此设备。 在Geektimes的广阔发展中,人们不止一次地考虑了这种热成像仪:它主要涵盖了其在Android上的工作,以及有关将该设备连接到PC的文章。 在我的评论中,我想谈谈我自己通过USB主机将Seek热成像仪连接到STM32微控制器的经历。

硬件要求


没有那么具体! 您所有的STM32应该具有一个能够在主机模式下工作的USB接口和一些用于控制LCD屏幕的接口。 最明显的选择是采用STM32F4-发现。 我手上有STM32F746G-发现板。 因此,说明将针对该板,但是! 因为 如果代码是在CubeMX环境中生成的,则可以使用另一个EVM。 我认为我为此项目支付的款项过多。

软件部分


通过USB进行通信时,此热像仪不会实现任何类别。 所有交互都直接进行,批量请求通过端点进行。 通过将命令(请求)发送到控制端点,您可以打开热成像仪,进行校准,并使其传输一帧或几帧。 此论坛中介绍了Seek Thermal的特别详细的工作。

因此,为了使热像仪能够与STM32微控制器一起使用,我们需要:

1)以任何您喜欢的板子的USB主机为例(我以STM32F7 CubeMX样本集中的STM32 USB主机CDC为例);
2)抛出设备类的初始化过程;
3)编写方便的包装程序,以使用读/写功能来控制端点和数据端点;
4)编写将原始数据转换为显示内容的函数;
5)使用LUT(彩色查找表)将单色图像着色。 这种缺陷出现在STM32系列微控制器中,可以用LCD屏幕独立控制。

首先,让我们做一些类似于libusb的文章,这将帮助我们用以下代码链接HAL库:

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: %d\n", 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: %d\n", res); } printf(" -> ["); for (int i = 0; i < res; i++) { printf(" %02x", data[i]); } printf(" ]\n"); #endif } return res; } 


接下来,我们编写接收图片的过程。 CDC示例中没有什么特别的要评论的。

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)



给潜在买家的信息

Source: https://habr.com/ru/post/zh-CN402083/


All Articles