Conectando um termovisor de busca ao STM32

Conecte o termovisor ao microcontrolador? Não tem problema! Especialmente se for um STM32 com interface USB Host e um termovisor térmico Seek da Dadget!


Ferro de solda através dos olhos de um termovisor SeekThermal

1. Introdução


Acho que todo mundo já se deparou com aparelhos como um termovisor, bem, pelo menos leu sobre eles. Entre esses dispositivos, há toda uma subclasse de gadgets que não são independentes, mas servem como uma espécie de decodificador para um computador ou smartphone.

Hoje falaremos sobre como conectar o termovisor Seek Thermal ao microcontrolador STM32. E a empresa Dadget me forneceu este dispositivo. Nas extensões do Geektimes, esse termovisor foi considerado mais de uma vez: cobriu principalmente seu trabalho com o Android, além de um artigo sobre como conectar esse dispositivo a um PC. Na minha análise, quero falar sobre minha própria experiência ao conectar um termovisor Seek Thermal ao microcontrolador STM32 por meio de um host USB.

Requisitos de hardware


Não é tão específico! Tudo o que o seu STM32 deve ter é uma interface USB capaz de funcionar no modo Host e alguma interface para controlar a tela LCD. A escolha mais óbvia é usar o STM32F4 - Discovery. Eu tinha a placa STM32F746G-Discovery em mãos. Por conseguinte, a descrição será para este fórum, mas! Porque Como o código é gerado no ambiente CubeMX, é possível usar outro EVM. Considero o pagamento aplicado por mim excessivo para este projeto.

Parte do software


Este termovisor não implementa nenhuma classe ao se comunicar via USB. Toda a interação é realizada diretamente, solicitações em massa através de terminais. Ao enviar comandos (solicitações) para o terminal de controle, você pode ativar o termovisor, calibrá-lo e transmiti-lo a um quadro ou a vários quadros. Um trabalho especialmente detalhado com o Seek Thermal é descrito neste fórum .

Assim, para o termovisor trabalhar com o microcontrolador STM32, precisamos:

1) Tome qualquer exemplo de host USB para sua placa favorita (peguei o exemplo CDC do host USB STM32 da coleção de amostras STM32F7 CubeMX);
2) Jogue fora o procedimento de inicialização da classe de dispositivo;
3) Escreva invólucros convenientes para trabalhar com as funções de leitura / gravação para controlar terminais e dados;
4) Escreva sua função para converter dados brutos em algo exibido;
5) Use LUT (Tabela de consulta de cores) para colorir uma imagem monocromática em cores. Esse problema apareceu na família de microcontroladores STM32, que pode ser controlada independentemente com telas de LCD.

Para começar, vamos fazer algo semelhante a uma peça do libusb, que nos ajudará a vincular a Biblioteca HAL ao seguinte código:

Código de procedimento do 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; } } 


Então vamos aqui e olhamos o procedimento vendor_transfer . Além disso, não é demais prestar atenção à lista de solicitações struct Request .

código do procedimento 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; } 


Em seguida, escrevemos o procedimento para receber a foto. Não há nada de especial para comentar, espionado no Exemplo do CDC.

Procedimento de recepção de dados 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; } 


Além disso, precisamos desenhar de alguma forma os dados recebidos na tela. Observo que no 20º byte de dados, que é uma matriz de 16 bits de pixels, são armazenadas informações sobre o tipo de quadro. Existem vários tipos de quadros. Estamos interessados ​​em um quadro de calibração e um quadro de trabalho. Uma moldura de calibração é obtida quando o termovisor fecha a cortina e tira uma foto da “escuridão”. Ao fotografar um quadro normal, o obturador está aberto. Assim, ao trabalhar, você sempre ouve o dispositivo clicar no obturador.

Procedimento de desenho da tela
 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; } } 


Finalmente, o ciclo principal, a partir do qual ele pode ser visto - onde o que foi cortado, onde algo foi inserido.

Ciclo principal
 #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; } } } } 


Conclusão


A operação do termovisor com um microcontrolador parece muito mais rápida do que com um smartphone. Eu recomendo este projeto para avaliar a imagem térmica dos dispositivos eletrônicos. O termovisor possui uma distância focal ajustável, o que permite considerar componentes eletrônicos individuais na placa! Em conclusão, um vídeo a partir do qual você pode avaliar a velocidade do termovisor (algo em torno de 8 a 9 fps)



Informações para potenciais compradores

Source: https://habr.com/ru/post/pt402083/


All Articles