來源:OpenFPGA;;作者:碎碎思
在本文中,我們將介紹如何構建帶有VGA輸出的低分辨率熱成像。該解決方案基于Melexis MLX90640紅外陣列、FPGA S7 50開發板(AMD-Xilinx Spartan-7 FPGA,帶VGA輸出)。
Sparkfun Melexis MLX90640 紅外陣列
MLX90640 SparkFun IR Array Breakout 配備 32x24 熱電堆傳感器陣列,本質上形成低分辨率熱成像相機。
MLX90640 包含 768 個 FIR 像素。集成了一個環境傳感器,用于測量芯片的環境溫度,以及一個電源傳感器,用于測量 VDD。所有傳感器(IR、Ta 和 VDD)的輸出均存儲在內部 RAM 中,可通過 I2C 接口訪問。
該模塊具有 110°x75° 視場角,溫度測量范圍為 -40°C-300°C。
MLX90640 SparkFun 模塊已將上拉電阻連接到 I2C 總線,因此我們不推薦在開發板上使用 I2C 上拉電阻。
開發板原理圖:A rty_s7_sch-rev_b.pdf
https://digilent.com/reference/_media/reference/programmable-logic/arty-s7/arty_s7_sch-rev_b.pdf
MLX90640 需要主機平臺進行復雜的計算。我們的系統需要 20,000 字節或更多的 RAM。
MLX90640 的 I2C 地址為 0x33,由硬件定義。我們將使用專用的 I2C 通道來驅動 MLX90640。
MLX90640-數據表-Melexis.pdf
https://www.melexis.com/en/product/MLX90640/Far-Infrared-Thermal-Sensor-Array
VGA熱成像攝像機系統設計
該系統非常簡單。只需撥動開關即可開啟或關閉攝像頭。Microblaze 軟微控制器通過 I2C 協議連接到熱陣列,計算溫度數據,將其轉換為顏色并將其存儲在 DDR3 SDRAM 內存中的非活動緩沖區中。數據存儲完成后,它會將緩沖區切換至 VGA 顯示。攝像頭開啟時,所有操作均按此進行。
FPGA硬件設計
該系統將顯示來自 DDR 中的幀緩沖區的圖像,使用 FPGA 邏輯中的多個組件來生成 VGA 顏色和同步信號。
BD設計
VGA層次結構塊設計
VGA 層次結構基于Zybo VGA 輸出 - 實時系統 - York Wiki 服務, 適用于 Microblaze
https://wiki.york.ac.uk/display/RTS/Zybo+VGA+Output
MLX90640 紅外陣列驅動器
與傳感器的通信是通過 I2C 協議完成的。
該設備使用 I2C 協議,支持 FM+ 模式(高達 1MHz 時鐘頻率),并且在總線上只能作為一個從機。
開始/停止條件
每個通信會話都由一個 START 條件啟動,以一個 STOP 條件結束。START 條件由 SDA 信號由高電平跳變至低電平觸發,而 STOP 條件由低電平跳變至高電平觸發。兩種跳變都必須在 SCL 信號為高電平時進行。
測試源代碼:
/** * Example of reeding temp data from mlx90640 with the Digilent Arty S7, with animation */ #include "platform.h" #include "xil_printf.h" #include "mlx90640_api.h" #include "xiic.h" #include "xintc.h" #include "xil_exception.h" #include "sleep.h" /* * The following constants map to the XPAR parameters createdinthe * xparameters.h file. They are defined here such that a user can easily * change all the needed parametersinone place. */ #define IIC_DEVICE_ID XPAR_IIC_0_DEVICE_ID #define INTC_DEVICE_ID XPAR_INTC_0_DEVICE_ID #define IIC_INTR_ID XPAR_INTC_0_IIC_0_VEC_ID XIic IicInstance; /* The instance of the IIC device */ XIntc InterruptController; /* The instance of the Interrupt controller */ #define IIC_SLAVE_ADDR 0x33 #define IIC_SCLK_RATE 100000 volatile u8 TransmitComplete; volatile u8 ReceiveComplete; /* * The following structure contains fields that are used with the callbacks * (handlers) of the IIC driver. The driver asynchronously calls handlers * when abnormal events occur or when data has been sent or received. This * structure must be volatile to work when the code is optimized. */ volatile struct { int EventStatus; int RemainingRecvBytes; int EventStatusUpdated; int RecvBytesUpdated; } HandlerInfo; #define BL 55 #define DC 54 #define WIDTH 32 #define HEIGHT 24 #define TEST_BUFFER_SIZE 512 #define TA_SHIFT 8 /************************** Function Prototypes ******************************/ int IicRepeatedStartExample(); static int SetupInterruptSystem(XIic *IicInstPtr); static void ReceiveHandler(XIic *InstancePtr); static void SendHandler(XIic *InstancePtr); static void StatusHandler(XIic *InstancePtr, int Event); void VGA_Fill_Color(uint16_t color); int MLX90640_I2CRead(uint8_t slaveAddr, uint16_t startAddress, uint16_t nMemAddressRead, uint16_t *data); int MLX90640_I2CWrite(uint8_t slaveAddr, uint16_t writeAddress, uint16_t data); void VGA_Fill_Display(float*mlx90640Frame); void VGA_DrawPixel(uint16_t x, uint16_t y, uint16_t color); long map(long x, long in_min, long in_max, long out_min, long out_max); u8 SendBuffer[TEST_BUFFER_SIZE]; //I2C TX u8 RecvBuffer[TEST_BUFFER_SIZE]; //I2C RX u16 frame[WIDTH][HEIGHT]; const uint16_t camColors[] = { 0x480F, 0x400F, 0x400F, 0x400F, 0x4010, 0x3810, 0x3810, 0x3810, 0x3810, 0x3010, 0x3010, 0x3010, 0x2810, 0x2810, 0x2810, 0x2810, 0x2010, 0x2010, 0x2010, 0x1810, 0x1810, 0x1811, 0x1811, 0x1011, 0x1011, 0x1011, 0x0811, 0x0811, 0x0811, 0x0011, 0x0011, 0x0011, 0x0011, 0x0011, 0x0031, 0x0031, 0x0051, 0x0072, 0x0072, 0x0092, 0x00B2, 0x00B2, 0x00D2, 0x00F2, 0x00F2, 0x0112, 0x0132, 0x0152, 0x0152, 0x0172, 0x0192, 0x0192, 0x01B2, 0x01D2, 0x01F3, 0x01F3, 0x0213, 0x0233, 0x0253, 0x0253, 0x0273, 0x0293, 0x02B3, 0x02D3, 0x02D3, 0x02F3, 0x0313, 0x0333, 0x0333, 0x0353, 0x0373, 0x0394, 0x03B4, 0x03D4, 0x03D4, 0x03F4, 0x0414, 0x0434, 0x0454, 0x0474, 0x0474, 0x0494, 0x04B4, 0x04D4, 0x04F4, 0x0514, 0x0534, 0x0534, 0x0554, 0x0554, 0x0574, 0x0574, 0x0573, 0x0573, 0x0573, 0x0572, 0x0572, 0x0572, 0x0571, 0x0591, 0x0591, 0x0590, 0x0590, 0x058F, 0x058F, 0x058F, 0x058E, 0x05AE, 0x05AE, 0x05AD, 0x05AD, 0x05AD, 0x05AC, 0x05AC, 0x05AB, 0x05CB, 0x05CB, 0x05CA, 0x05CA, 0x05CA, 0x05C9, 0x05C9, 0x05C8, 0x05E8, 0x05E8, 0x05E7, 0x05E7, 0x05E6, 0x05E6, 0x05E6, 0x05E5, 0x05E5, 0x0604, 0x0604, 0x0604, 0x0603, 0x0603, 0x0602, 0x0602, 0x0601, 0x0621, 0x0621, 0x0620, 0x0620, 0x0620, 0x0620, 0x0E20, 0x0E20, 0x0E40, 0x1640, 0x1640, 0x1E40, 0x1E40, 0x2640, 0x2640, 0x2E40, 0x2E60, 0x3660, 0x3660, 0x3E60, 0x3E60, 0x3E60, 0x4660, 0x4660, 0x4E60, 0x4E80, 0x5680, 0x5680, 0x5E80, 0x5E80, 0x6680, 0x6680, 0x6E80, 0x6EA0, 0x76A0, 0x76A0, 0x7EA0, 0x7EA0, 0x86A0, 0x86A0, 0x8EA0, 0x8EC0, 0x96C0, 0x96C0, 0x9EC0, 0x9EC0, 0xA6C0, 0xAEC0, 0xAEC0, 0xB6E0, 0xB6E0, 0xBEE0, 0xBEE0, 0xC6E0, 0xC6E0, 0xCEE0, 0xCEE0, 0xD6E0, 0xD700, 0xDF00, 0xDEE0, 0xDEC0, 0xDEA0, 0xDE80, 0xDE80, 0xE660, 0xE640, 0xE620, 0xE600, 0xE5E0, 0xE5C0, 0xE5A0, 0xE580, 0xE560, 0xE540, 0xE520, 0xE500, 0xE4E0, 0xE4C0, 0xE4A0, 0xE480, 0xE460, 0xEC40, 0xEC20, 0xEC00, 0xEBE0, 0xEBC0, 0xEBA0, 0xEB80, 0xEB60, 0xEB40, 0xEB20, 0xEB00, 0xEAE0, 0xEAC0, 0xEAA0, 0xEA80, 0xEA60, 0xEA40, 0xF220, 0xF200, 0xF1E0, 0xF1C0, 0xF1A0, 0xF180, 0xF160, 0xF140, 0xF100, 0xF0E0, 0xF0C0, 0xF0A0, 0xF080, 0xF060, 0xF040, 0xF020, 0xF800, }; intmain() { init_platform(); int Status; XIic_Config *ConfigPtr; /* Pointer to configuration data */ init_platform(); ConfigPtr = XIic_LookupConfig(XPAR_IIC_0_DEVICE_ID); if(ConfigPtr == NULL) { returnXST_FAILURE; } //print("XIic_LookupConfig "); Status = XIic_CfgInitialize(&IicInstance, ConfigPtr, ConfigPtr->BaseAddress); if(Status != XST_SUCCESS) { returnXST_FAILURE; } //print("XIic_CfgInitialize "); /* * Setup the Interrupt System. */ Status = SetupInterruptSystem(&IicInstance); if(Status != XST_SUCCESS) { returnXST_FAILURE; } //print("SetupInterruptSystem "); /* * Set the Transmit, Receive and Status handlers. */ XIic_SetSendHandler(&IicInstance, &IicInstance, (XIic_Handler) SendHandler); //print("XIic_SetSendHandler "); XIic_SetStatusHandler(&IicInstance, &IicInstance, (XIic_StatusHandler) StatusHandler); //print("XIic_SetStatusHandler "); XIic_SetRecvHandler(&IicInstance, &IicInstance, (XIic_Handler) ReceiveHandler); //print("XIic_SetRecvHandler "); /* * Set the Address of the Slave. */ Status = XIic_SetAddress(&IicInstance, XII_ADDR_TO_SEND_TYPE, IIC_SLAVE_ADDR); if(Status != XST_SUCCESS) { returnXST_FAILURE; } //print("XIic_SetAddress "); /* * Start the IIC device. */ Status = XIic_Start(&IicInstance); if(Status != XST_SUCCESS) { returnXST_FAILURE; } //print("XIic_Start "); static u16 mlx90640Frame[834]; static uint16_t eeMLX90640[832]; paramsMLX90640 mlx90640; floatTa; floatemissivity = 0.95; staticfloatmlx90640To[768]; MLX90640_DumpEE(0x33, eeMLX90640); //print("MLX90640_DumpEE "); MLX90640_ExtractParameters(eeMLX90640, &mlx90640); //print("MLX90640_ExtractParameters "); while(1) { MLX90640_GetFrameData(0x33, mlx90640Frame); //print("MLX90640_GetFrameData "); Ta = MLX90640_GetTa(mlx90640Frame, &mlx90640) - TA_SHIFT; //print("MLX90640_GetTa "); MLX90640_CalculateTo(mlx90640Frame, &mlx90640, emissivity, Ta, mlx90640To); //print("MLX90640_CalculateTo "); for(int i = 0; i < 24; i++) { ? ?for?(int y = 0; y < 32; y++) { ? ? xil_printf(" %d ", (int) (mlx90640To[y + (i * 32)])); ? ?} ? ?xil_printf(" "); ? } ? //VGA_Fill_Display(&mlx90640To); ?} ?/* ? * Stop the IIC device. ? */ ?Status = XIic_Stop(&IicInstance); if?(Status != XST_SUCCESS) { return?XST_FAILURE; ?} ?cleanup_platform(); return?0; } long map(long x, long in_min, long in_max, long out_min, long out_max) { return?(x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } void VGA_Fill_Display(float?*mlx90640Frame) { // //834 // uint16_t i, j; //// uint8_t data[2]; //?float?temp; // u8 mapped; // u16 colour; // //?for?(i = 0; i < 240; i++) // ?for?(j = 0; j < 320; j++) { // // ? temp = (mlx90640Frame[((i/10)*32)+(j/10)]); // ? mapped = map((u16) temp, 0, 100, 0, 255); // ? colour = camColors[mapped]; // ? VGA_DrawPixel(0+i, 0+j, colour); // // ?} // //ST7789_UnSelect(); } void VGA_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { //?if?((x < 0) || (x >= ST7789_WIDTH) || // (y < 0) || (y >= ST7789_HEIGHT))return; // // ST7789_SetAddressWindow(x, y, x, y); // uint8_t data[] = {color >> 8, color & 0xFF}; // //ST7789_Select(); // ST7789_WriteData(data, sizeof(data)); // //ST7789_UnSelect(); } void VGA_Fill_Color(uint16_t color) { // uint16_t i, j; // ST7789_SetAddressWindow(0, 0, ST7789_WIDTH - 1, ST7789_HEIGHT - 1); // //ST7789_Select(); // //XSpiPs_SetSlaveSelect(&SpiInstance_EMIO, 0x00); // //for(i = 0; i < ST7789_WIDTH; i++) // ?for?(j = 0; j < ST7789_HEIGHT; j++) { // ? uint8_t data[] = {color >> 8, color & 0xFF}; // ST7789_WriteData(data, sizeof(data)); // } // //ST7789_UnSelect(); } int MLX90640_I2CRead(uint8_t slaveAddr, uint16_t startAddress, uint16_t nMemAddressRead, uint16_t *data) { //print("MLX90640_I2CRead "); int Status; int BusBusy; /* * Set the defaults. */ ReceiveComplete = 1; /* * Set the Repeated Start option. */ IicInstance.Options = XII_REPEATED_START_OPTION; int cnt = 0; int i = 0; u8 cmd[2] = { 0, 0 }; u8 i2cData[1664] = { 0 }; uint16_t *p; p = data; cmd[0] = startAddress >> 8; cmd[1] = startAddress & 0x00FF; Status = XIic_MasterSend(&IicInstance, cmd, 2); if(Status != XST_SUCCESS) { returnXST_FAILURE; } //print("XIic_MasterSend "); usleep(1000); /* * This isforverification that Bus is not released and still Busy. */ BusBusy = XIic_IsIicBusy(&IicInstance); ReceiveComplete = 1; IicInstance.Options = 0x0; /* * Receive the Data. */ Status = XIic_MasterRecv(&IicInstance, i2cData, 2 * nMemAddressRead); if(Status != XST_SUCCESS) { returnXST_FAILURE; } usleep(1000); //print("XIic_MasterRecv "); while(XIic_IsIicBusy(&IicInstance) == TRUE) { } for(cnt = 0; cnt < nMemAddressRead; cnt++) { ? i = cnt << 1; ? *p++ = (uint16_t) i2cData[i] * 256 + (uint16_t) i2cData[i + 1]; ?} return?0; } int MLX90640_I2CWrite(uint8_t slaveAddr, uint16_t writeAddress, uint16_t data) { ?int Status; ?int BusBusy; ?/* ? * Set the defaults. ? */ ?TransmitComplete = 1; ?/* ? * Set the Repeated Start option. ? */ ?IicInstance.Options = XII_REPEATED_START_OPTION; ?u8 cmd[4] = { 0, 0, 0, 0 }; ?static uint16_t dataCheck; ?cmd[0] = writeAddress >> 8; cmd[1] = writeAddress & 0x00FF; cmd[2] = data >> 8; cmd[3] = data & 0x00FF; /* * Send the data. */ Status = XIic_MasterSend(&IicInstance, cmd, 4); if(Status != XST_SUCCESS) { returnXST_FAILURE; } print("XIic_MasterSend "); /* * Wait till data is transmitted. */ //while(TransmitComplete) { // // } /* * This isforverification that Bus is not released and still Busy. */ BusBusy = XIic_IsIicBusy(&IicInstance); TransmitComplete = 1; IicInstance.Options = 0x0; /* * Wait till data is transmitted. */ //while((TransmitComplete) || (XIic_IsIicBusy(&IicInstance) == TRUE)) { // // } MLX90640_I2CRead(slaveAddr, writeAddress, 1, &dataCheck); if(dataCheck != data) { return-2; } return0; } /*****************************************************************************/ /** * Thisfunctionsetups the interrupt system so interrupts can occurforthe * IIC. Thefunctionis application-specific since the actual system may or * may not have an interrupt controller. The IIC device could be directly * connected to a processor without an interrupt controller. The user should * modify thisfunctionto fit the application. * * @param IicInstPtr contains a pointer to the instance of the IIC which * is going to be connected to the interrupt controller. * * @returnXST_SUCCESSifsuccessfulelseXST_FAILURE. * * @note None. * ******************************************************************************/ static int SetupInterruptSystem(XIic *IicInstPtr) { int Status; if(InterruptController.IsStarted == XIL_COMPONENT_IS_STARTED) { returnXST_SUCCESS; } /* * Initialize the interrupt controller driver so that it's ready to use. */ Status = XIntc_Initialize(&InterruptController, INTC_DEVICE_ID); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * Connect the device driver handler that will be called when an * interrupt for the device occurs, the handler defined above performs * the specific interrupt processing for the device. */ Status = XIntc_Connect(&InterruptController, IIC_INTR_ID, (XInterruptHandler) XIic_InterruptHandler, IicInstPtr); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * Start the interrupt controller so interrupts are enabled for all * devices that cause interrupts. */ Status = XIntc_Start(&InterruptController, XIN_REAL_MODE); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* * Enable the interrupts for the IIC device. */ XIntc_Enable(&InterruptController, IIC_INTR_ID); /* * Initialize the exception table. */ Xil_ExceptionInit(); /* * Register the interrupt controller handler with the exception table. */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XIntc_InterruptHandler, &InterruptController); /* * Enable non-critical exceptions. */ Xil_ExceptionEnable(); return XST_SUCCESS; } /*****************************************************************************/ /** * This Send handler is called asynchronously from an interrupt context and * indicates that data in the specified buffer has been sent. * * @param InstancePtr is a pointer to the IIC driver instance for which * the handler is being called for. * * @return None. * * @note None. * ******************************************************************************/ static void SendHandler(XIic *InstancePtr) { TransmitComplete = 0; } /*****************************************************************************/ /** * This Status handler is called asynchronously from an interrupt * context and indicates the events that have occurred. * * @param InstancePtr is a pointer to the IIC driver instance for which * the handler is being called for. * @param Event indicates the condition that has occurred. * * @return None. * * @note None. * ******************************************************************************/ static void StatusHandler(XIic *InstancePtr, int Event) { } /*****************************************************************************/ /** * This Receive handler is called asynchronously from an interrupt context and * indicates that data in the specified buffer has been Received. * * @param InstancePtr is a pointer to the IIC driver instance for which * the handler is being called for. * * @return None. * * @note None. * ******************************************************************************/ static void ReceiveHandler(XIic *InstancePtr) { ReceiveComplete = 0; }
完整代碼
將傳感器的驅動及VGA的驅動結合起來形成完整的代碼進行測試:
VGA 熱成像演示視頻:該視頻展示了熱像儀以 800x600 @60Hz 捕獲的 VGA 輸出,Microblaze 軟處理器和 MLX90640 紅外熱陣列傳感器之間的 I2C 協議下的通信。
視頻中,首先將一只手放在傳感器前,然后將傳感器指向 Arty S7 開發板,我們可以看到 Spartan-7 FPGA 上的溫差。當傳感器指向 Spartan-7 FPGA 時,讀數為最高溫度:54.7 攝氏度,最低溫度:31.87 攝氏度。
代碼鏈接
https://github.com/javagoza/E14SpartanMigrationProgram/tree/main
https://community.element14.com/technologies/fpga-group/b/blog/posts/arty-s7-50-vga-thermal-imaging-camera
總結
東西不復雜,主要就是一個傳感器的驅動+VGA驅動(很多歷程),所以該項目還有很大的改進和優化空間。需要改進的方面:
使用 HDL 執行圖像縮放。將每幀占用的內存量從 800600 像素(480,000 像素)減少到 3224 像素(768 像素)。
將像素設置為無符號 16 位而不是無符號 32 位,可以將處理和所需的內存量減少一半。我們每個像素只需要 12 位。
在 HDL 中執行顏色空間轉換。
每次讀取時僅處理發生變化的傳感器單元。每幀將單元數量減少一半。
-
傳感器
+關注
關注
2565文章
52971瀏覽量
767173 -
FPGA
+關注
關注
1645文章
22040瀏覽量
618186 -
Xilinx
+關注
關注
73文章
2185瀏覽量
125277 -
開發板
+關注
關注
25文章
5675瀏覽量
104566 -
熱成像
+關注
關注
3文章
387瀏覽量
21003
原文標題:DIY熱成像(FPGA版)
文章出處:【微信號:FPGA研究院,微信公眾號:FPGA研究院】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
關于labview程序在不同分辨率顯示器下打開時無法合理匹配的問題
請問ADXL354的最低分辨率能達到ADXL203的1mg嗎?
RF成像分辨率怎么提高
47.5Mp高分辨率、高速全局快門CMOS成像傳感器介紹
超分辨率圖像重建方法研究
基于POCS算法的圖像超分辨率重建
序列圖像超分辨率重建算法研究

基于稀疏編碼和隨機森林的超分辨率算法

基于圖像超分辨率SR極限學習機ELM的人臉識別

低分辨率位置傳感器電機系統低速性能分析

Linux時間子系統中低分辨率定時器的原理和實現
深蘭科技DeepBlueAI團隊斬獲低分辨率視頻行為識別挑戰賽的冠軍
CVPR2020 | 即插即用!將雙邊超分辨率用于語義分割網絡,提升圖像分辨率的有效策略

直接飛行時間(DToF)視頻的深度一致超分辨率重建

評論