In this post we describe how to build a low resolution thermal imaging camera with VGA output. The solution is based on the Melexis MLX90640 IR Array, the Digilent's PmodVGA module, and the AMD-Xilinx Spartan-7 FPGA of the Digilent Arty S7 50 board.
This project is part 13 of my contribution to the "7 Ways to Leave Your Spartan-6" program, learning about the Xilinx Spartan-7 FPGA on the Digilent Arty S7 50 board.
VGA Thermal Imaging Camera demo video: The video shows the capture of the VGA output of our thermal camera at 800x600 @60Hz, the communication under I2C protocol between the Microblaze soft processor and the MLX90640 IR thermal array sensor and the web camera shows us in real time the scene of the experiment.
During the video a hand is first presented to the sensor and then the sensor is pointed at the Arty S7 board where we can see the temperature difference on the Spartan-7 FPGA. The sensor reads MAX Temp: 54.7, MIN Temp 31.87 when pointed at the Spartan-7 FPGA.
Bill of materials
If you want to replicate this project you will need:
Product Name | Manufacturer | Datasheet |
Arty S7 50 | Digilent | Buy Now |
PmodVGA | Digilent | Buy Now |
SparkFun IR Array Breakout - 110 Degree FOV, MLX90640 (Qwiic) | Sparkfun | Buy |
Alternate Module: Adafruit MLX90640 24x32 IR Thermal Camera Breakout - 110 Degree FoV |
Adafruit |
Sparkfun Melexis MLX90640 IR Array
The MLX90640 SparkFun IR Array Breakout is equipped with a 32x24 array of thermopile sensors creating, in essence, a low resolution thermal imaging camera.
I recently used this sensor for another project for the AMD Xilinx Kria KV260 board. The Kria Kitchen ML Lifeguard a Smart device that monitors unattended kitchen cooking appliances using computer vision with Machine Learning.
The MLX90640 contains 768 FIR pixels. An ambient sensor is integrated to measure the ambient temperature of the chip and supply sensor to measure the VDD. The outputs of all sensors IR, Ta and VDD are stored in internal RAM and are accessible through I 2 C.
This specific IR Array Breakout features a 110°x75° field of view with a temperature measurement range of -40°C-300°C.
The MLX90640 SparkFun IR Array Breakout has pull up resistors attached to the I2C bus therefore, we do not promote I2C pullup resistors on the board. We will use the pins on the shield connector typically used for I2C signals are labeled as SCL and SDA. Remove the two shorting blocks horizontally through header J4.
The MLX90640 requires complex calculations by the host platform. Our system will need a 20,000 bytes or more of RAM.
The I2C address of the MLX90640 is 0x33 and is hardware defined. We will use an exclusive I"C channel to drive the MLX90640.
MLX90640-Datasheet-Melexis.pdf
Far Infrared Thermal Sensor Array (32x24 RES) #Melexis
Digilent's PmodVGA
The Pmod VGA provides a VGA port to any board with Pmod connectivity. The VGA port can be used to drive standard displays such as televisions and monitors. The host board must be capable of driving a fast parallel data bus in order to properly drive a display with the Pmod VGA.
- 12-bit RGB444 color depth
- Simple, high-speed R-2R resistor ladder DAC
- High-speed buffers support pixel clocks up to 150 MHZ
- Dual 12-pin Pmod connector with GPIO interface
Reference: Pmod VGA Reference Manual - Digilent Reference
Connections
We will connect the Pmod VGA module to the JB and JC ports of the Arty S7 50 board and we will connect the I2C SDA & SCL pins to the MLX90640. Connect 3V3 and GND from the shield header to the MLX90640
Project Environment
For this project I used: AMD Xilinx Vivado 2021.1,AMD Xilinx Vitis 2021.1, Digilent Arty S7 50 and Windows 10
VGA Thermal Imaging Camera System Design
The system is very simple. The camera turns on and off by flipping a switch. The soft Microblaze microcontroller connects via I2C protocol with the thermal array, recovers the temperature data, converts it to colors and stores it in the non-active buffer in the DDR3 SDRAM memory, when it finishes storing the data, it exchanges the buffer to show on the VGA display. And so while the camera is on.
FPGA Hardware Design
The system will display images from a frame buffer in DDR, using a number of components in the FPGA logic to generate the VGA color and sync signals.
I have made the design from scratch adapting it to the Microblaze and the Arty S7 50. It is not a simple design, I have required many iterations to have a functional design. It is tested and allows animations with the double buffer technique with images generated and read on DDR3 SDRAM memory.
Block Design
VGA Hierarchy Block Design
The VGA Hierarchy is based in Zybo VGA Output - Real-Time Systems - York Wiki Service adapted to the Microblaze
Step by step system block design
We will use the following sources from Digilent:
- axi_dynclk_v1_0: Dynamic Clock Generator (Digilent)
- rgb2vga_v1_0: RGB to VGA output (Digilent)
From Digilents zybo-vga-master github repository download sources for axi_dynclk_v1_0
From Digilents vivado-library-master github repository download sources for rgb2vga_v1_0
- Add sources zybo-vga-master repo axi_dynclk_v1_0
- Add sources vivado-library-master rgb2vga_v1_0
Create new Block Design
Create a new hierarchy and name it vga
Open the vga hierarchy and add the follow IP:
- Dynamic Clock Generator (Digilent): is the clock generation module, which automatically generates the corresponding pixel clock and serial clock according to different screen resolutions.
- RGB to VGA output (Digilent) : This IP accepts a Xilinx vid_io input and outputs an independently customizable color depth, properly blanked RGB pixel bus to connect to a VGA DAC.On Digilent boards the outputs directly connect to the onboard resistor ladder that serves as VGA DAC. The supported color depth varies between boards and the IP should be configured to match it.
- AXI Video Direct Memory Access (Xilinx): The AXI Video Direct Memory Access (AXI VDMA) core is a soft Xilinx IP core that provides high-bandwidth direct memory access between memory and AXI4-Stream type video target peripherals. The core provides efficient two dimensional DMA operations with independent asynchronous read and write channel operation. Initialization, status, interrupt and management registers are accessed through an AXI4-Lite slave interface.
- Video Timing Controller (Xilinx): The Xilinx Video Timing Controller LogiCORE IP is a general purpose video timing detector and generator, which automatically detects blanking and active data timing based on the input horizontal and vertical synchronization pulses.
- AXI4-Stream to Video Out (Xilinx): The Xilinx LogiCORE IP AXI4-Stream to Video Out core is designed to interface from the AXI4-Stream interface implementing a Video Protocol to a video source (parallel video data, video syncs, and blanks).This core works with the Xilinx Video Timing Controller (VTC) core. This core provides a bridge between video processing cores with AXI4-Stream interfaces and a video output.
- Slice (Xilinx) : The Slice IP core is used to rip bits off a bus net. Often there is a need to rip some bits off a wide bus net. This IP core can be instantiated to accomplish this purpose.
Follow the images for the block design construction:
{gallery}Thermal Camera Block Design |
---|
ADD SOURCES: Add Digilent AXI DYNCLK |
ADD SOURCES: Add Digilent rgb2vga |
CREATE BLOCK DESIGN: New Block Design thermal_camera_bd |
CREATE NEW HIERARCHY: In Block Design Right Click & Create Hierarchy |
Crete Hierarchy: Name vga the new hierarchy and open it |
Add IP Block: AXI VIdeo Direct Memory Access |
Configure the AXI Video Direct Memory Access: Change Frame Buffers to 2 // Verify Enable Write Channel is unchecked and Enable Read Channel is checked. // For Read Channel, use Memory Map Data Width of 64, Read Burst Size of 32, Stream Data Width of 32, and Line Buffer Depth of 4096. |
Configure the AXI Video Direct Memory AccessAdvanced: Set GenLock Mode for the Read Channel Options to Master |
Add IP: Video Timing Controller |
Configure the Video Timing Controller: Deselect Enable Detection |
Add IP: AXI4-Stream to Video Out |
Configure the AXI4-Stream to Video Out: Change FIFO Depth to 32 // Set Clock Mode to Independent // Set Timing Mode to Master |
Add IP: Add Slice |
Configure Slice: Din From 23 |
Add Module: rgb2vga |
Configure rgb2vga: Set Blue, Green, and Red color depth to 4 |
Add Module: axi_dynclk |
Hierarchy Block Diagram: Regenerate Layout |
Create Hierarchy Pins: From vga2rgb right click the output ports and create the following Pins in the vga hierarchy// vga_red, Output, vector from 3 to 0 // vga_green, Output, vector from 3 to 0 // vga_blue, Output, vector from 3 to 0 // vga_hsync, Output //vga_vsync, Output |
Create Hierarchy Pins: After pin creation |
Hierarchy connections: Make the following hierarchy connections |
Create External Ports: From Main Block Diagram right click the Hierarchy Pins and create the external Ports |
Create External Ports: Name the ports, vga_red, vga_green, vga_blue, vga_hsync and vga_vsync |
Create External Ports: After Creation |
Add microblaze and peripherals. More information about this process in our introductory tutorial blogs:
{gallery}Add Microblaze & Peripherals |
---|
Add DDR3 SDRAM Memory: From Board Tab Auto connect DDR3 SDRAM |
Delete the “clk_ref_i”: Delete the “clk_ref_i” pin. Connect the “ui_addn_clk_0” pin to the “clk_ref_i” pin by clicking and dragging from one to the other. |
Connect the “ui_addn_clk_0”: Connect the “ui_addn_clk_0” pin to the “clk_ref_i” pin |
Modify Constraints File: Create clock sys_clk_i |
Run Connection Automation: Run connection Automation And connect Reset |
Connection Automation: Connect Reset |
Add Constant IP: Double click on the block to open it's configuration wizard and modify it to have a Value of “0” and a Width of “12 |
Connect Constant: Connect its output port to the “device_temp_i” port on the MIG. |
Add Microblaze IP: Add Microblaze IP |
Run Block Automation: |
Run Connection Automation: only run connection automation for one side of the connection - the S_AXI port, as shown in the screenshot to the right. Make sure that its box is checked. Check that the Master interface is set to “/microblaze_0 (Cached)”, indicating that the microblaze local memory will act as a cache for the DDR memory, then click OK to make the connections. |
Run Connection Automation: Now run connection automation for the VGA Hierarchy |
After Connection Automation |
Connect vid_io_out_clk:Open vga Hierarchy and connect vid_io_out_clk to aclk |
Add peripherals: From board add LEDs, Switches I2C on J3 and USB UART (UART Lite 9600 bauds) |
Run connection automation |
Modify constraints: PmodVGA module on JB and JC Connectors |
Create HDL wrapper |
Constraints File
Here is the final constraints file.
## This file is a general .xdc for the Arty S7-50 Rev. E ## To use it in a project: ## - uncomment the lines corresponding to used pins ## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project ## Clock Signals #set_property -dict { PACKAGE_PIN F14 IOSTANDARD LVCMOS33 } [get_ports { CLK12MHZ }]; #IO_L13P_T2_MRCC_15 Sch=uclk #create_clock -add -name sys_clk_pin -period 83.333 -waveform {0 41.667} [get_ports { CLK12MHZ }]; set_property -dict { PACKAGE_PIN R2 IOSTANDARD SSTL135 } [get_ports { sys_clk_i }]; #IO_L12P_T1_MRCC_34 Sch=ddr3_clk[200] ##create_clock -add -name sys_clk_pin -period 10.000 -waveform {0 5.000} [get_ports { sys_clk_i }]; ## Switches #set_property -dict { PACKAGE_PIN H14 IOSTANDARD LVCMOS33 } [get_ports { sw[0] }]; #IO_L20N_T3_A19_15 Sch=sw[0] #set_property -dict { PACKAGE_PIN H18 IOSTANDARD LVCMOS33 } [get_ports { sw[1] }]; #IO_L21P_T3_DQS_15 Sch=sw[1] #set_property -dict { PACKAGE_PIN G18 IOSTANDARD LVCMOS33 } [get_ports { sw[2] }]; #IO_L21N_T3_DQS_A18_15 Sch=sw[2] #set_property -dict { PACKAGE_PIN M5 IOSTANDARD SSTL135 } [get_ports { sw[3] }]; #IO_L6N_T0_VREF_34 Sch=sw[3] ## RGB LEDs #set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { led0_r }]; #IO_L23N_T3_FWE_B_15 Sch=led0_r #set_property -dict { PACKAGE_PIN G17 IOSTANDARD LVCMOS33 } [get_ports { led0_g }]; #IO_L14N_T2_SRCC_15 Sch=led0_g #set_property -dict { PACKAGE_PIN F15 IOSTANDARD LVCMOS33 } [get_ports { led0_b }]; #IO_L13N_T2_MRCC_15 Sch=led0_b #set_property -dict { PACKAGE_PIN E15 IOSTANDARD LVCMOS33 } [get_ports { led1_r }]; #IO_L15N_T2_DQS_ADV_B_15 Sch=led1_r #set_property -dict { PACKAGE_PIN F18 IOSTANDARD LVCMOS33 } [get_ports { led1_g }]; #IO_L16P_T2_A28_15 Sch=led1_g #set_property -dict { PACKAGE_PIN E14 IOSTANDARD LVCMOS33 } [get_ports { led1_b }]; #IO_L15P_T2_DQS_15 Sch=led1_b ## LEDs #set_property -dict { PACKAGE_PIN E18 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]; #IO_L16N_T2_A27_15 Sch=led[2] #set_property -dict { PACKAGE_PIN F13 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]; #IO_L17P_T2_A26_15 Sch=led[3] #set_property -dict { PACKAGE_PIN E13 IOSTANDARD LVCMOS33 } [get_ports { led[2] }]; #IO_L17N_T2_A25_15 Sch=led[4] #set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { led[3] }]; #IO_L18P_T2_A24_15 Sch=led[5] ## Buttons #set_property -dict { PACKAGE_PIN G15 IOSTANDARD LVCMOS33 } [get_ports { btn[0] }]; #IO_L18N_T2_A23_15 Sch=btn[0] #set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { btn[1] }]; #IO_L19P_T3_A22_15 Sch=btn[1] #set_property -dict { PACKAGE_PIN J16 IOSTANDARD LVCMOS33 } [get_ports { btn[2] }]; #IO_L19N_T3_A21_VREF_15 Sch=btn[2] #set_property -dict { PACKAGE_PIN H13 IOSTANDARD LVCMOS33 } [get_ports { btn[3] }]; #IO_L20P_T3_A20_15 Sch=btn[3] ## Pmod Header JA #set_property -dict { PACKAGE_PIN L17 IOSTANDARD LVCMOS33 } [get_ports { ja[0] }]; #IO_L4P_T0_D04_14 Sch=ja_p[1] #set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { ja[1] }]; #IO_L4N_T0_D05_14 Sch=ja_n[1] #set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports { ja[2] }]; #IO_L5P_T0_D06_14 Sch=ja_p[2] #set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { ja[3] }]; #IO_L5N_T0_D07_14 Sch=ja_n[2] #set_property -dict { PACKAGE_PIN M16 IOSTANDARD LVCMOS33 } [get_ports { ja[4] }]; #IO_L7P_T1_D09_14 Sch=ja_p[3] #set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { ja[5] }]; #IO_L7N_T1_D10_14 Sch=ja_n[3] #set_property -dict { PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { ja[6] }]; #IO_L8P_T1_D11_14 Sch=ja_p[4] #set_property -dict { PACKAGE_PIN N18 IOSTANDARD LVCMOS33 } [get_ports { ja[7] }]; #IO_L8N_T1_D12_14 Sch=ja_n[4] ## Pmod Header JB set_property -dict { PACKAGE_PIN P17 IOSTANDARD LVCMOS33 } [get_ports { vga_red[0] }]; #IO_L9P_T1_DQS_14 Sch=jb_p[1] set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { vga_red[1] }]; #IO_L9N_T1_DQS_D13_14 Sch=jb_n[1] set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { vga_red[2] }]; #IO_L10P_T1_D14_14 Sch=jb_p[2] set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports { vga_red[3] }]; #IO_L10N_T1_D15_14 Sch=jb_n[2] set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { vga_blue[0] }]; #IO_L11P_T1_SRCC_14 Sch=jb_p[3] set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { vga_blue[1] }]; #IO_L11N_T1_SRCC_14 Sch=jb_n[3] set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports { vga_blue[2] }]; #IO_L12P_T1_MRCC_14 Sch=jb_p[4] set_property -dict { PACKAGE_PIN P16 IOSTANDARD LVCMOS33 } [get_ports { vga_blue[3] }]; #IO_L12N_T1_MRCC_14 Sch=jb_n[4] ## Pmod Header JC set_property -dict { PACKAGE_PIN U15 IOSTANDARD LVCMOS33 } [get_ports { vga_green[0] }]; #IO_L18P_T2_A12_D28_14 Sch=jc1/ck_io[41] set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { vga_green[1] }]; #IO_L18N_T2_A11_D27_14 Sch=jc2/ck_io[40] set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { vga_green[2] }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=jc3/ck_io[39] set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { vga_green[3] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=jc4/ck_io[38] set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { vga_hsync }]; #IO_L16P_T2_CSI_B_14 Sch=jc7/ck_io[37] set_property -dict { PACKAGE_PIN P13 IOSTANDARD LVCMOS33 } [get_ports { vga_vsync }]; #IO_L19P_T3_A10_D26_14 Sch=jc8/ck_io[36] #set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { jc[6] }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=jc9/ck_io[35] #set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { jc[7] }]; #IO_L20P_T3_A08_D24_14 Sch=jc10/ck_io[34] ## Pmod Header JD #set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { jd[0] }]; #IO_L20N_T3_A07_D23_14 Sch=jd1/ck_io[33] #set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { jd[1] }]; #IO_L21P_T3_DQS_14 Sch=jd2/ck_io[32] #set_property -dict { PACKAGE_PIN V13 IOSTANDARD LVCMOS33 } [get_ports { jd[2] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=jd3/ck_io[31] #set_property -dict { PACKAGE_PIN T12 IOSTANDARD LVCMOS33 } [get_ports { jd[3] }]; #IO_L22P_T3_A05_D21_14 Sch=jd4/ck_io[30] #set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { jd[4] }]; #IO_L22N_T3_A04_D20_14 Sch=jd7/ck_io[29] #set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { jd[5] }]; #IO_L23P_T3_A03_D19_14 Sch=jd8/ck_io[28] #set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { jd[6] }]; #IO_L23N_T3_A02_D18_14 Sch=jd9/ck_io[27] #set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { jd[7] }]; #IO_L24P_T3_A01_D17_14 Sch=jd10/ck_io[26] ## USB-UART Interface #set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { uart_rxd_out }]; #IO_25_14 Sch=uart_rxd_out #set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { uart_txd_in }]; #IO_L24N_T3_A00_D16_14 Sch=uart_txd_in ## ChipKit Outer Digital Header #set_property -dict { PACKAGE_PIN L13 IOSTANDARD LVCMOS33 } [get_ports { ck_io0 }]; #IO_0_14 Sch=ck_io[0] #set_property -dict { PACKAGE_PIN N13 IOSTANDARD LVCMOS33 } [get_ports { ck_io1 }]; #IO_L6N_T0_D08_VREF_14 Sch=ck_io[1] #set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { ck_io2 }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=ck_io[2] #set_property -dict { PACKAGE_PIN R14 IOSTANDARD LVCMOS33 } [get_ports { ck_io3 }]; #IO_L13P_T2_MRCC_14 Sch=ck_io[3] #set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { ck_io4 }]; #IO_L13N_T2_MRCC_14 Sch=ck_io[4] #set_property -dict { PACKAGE_PIN R16 IOSTANDARD LVCMOS33 } [get_ports { ck_io5 }]; #IO_L14P_T2_SRCC_14 Sch=ck_io[5] #set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { ck_io6 }]; #IO_L14N_T2_SRCC_14 Sch=ck_io[6] #set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { ck_io7 }]; #IO_L16N_T2_A15_D31_14 Sch=ck_io[7] #set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { ck_io8 }]; #IO_L17P_T2_A14_D30_14 Sch=ck_io[8] #set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { ck_io9 }]; #IO_L17N_T2_A13_D29_14 Sch=ck_io[9] ## ChipKit SPI Header ## NOTE: The ChipKit SPI header ports can also be used as digital I/O and share FPGA pins with ck_io10-13. Do not use both at the same time. #set_property -dict { PACKAGE_PIN H16 IOSTANDARD LVCMOS33 } [get_ports { ck_io10_ss }]; #IO_L22P_T3_A17_15 Sch=ck_io10_ss #set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { ck_io11_mosi }]; #IO_L22N_T3_A16_15 Sch=ck_io11_mosi #set_property -dict { PACKAGE_PIN K14 IOSTANDARD LVCMOS33 } [get_ports { ck_io12_miso }]; #IO_L23P_T3_FOE_B_15 Sch=ck_io12_miso #set_property -dict { PACKAGE_PIN G16 IOSTANDARD LVCMOS33 } [get_ports { ck_io13_sck }]; #IO_L14P_T2_SRCC_15 Sch=ck_io13_sck ## ChipKit Inner Digital Header ## Note: these pins are shared with PMOD Headers JC and JD and cannot be used at the same time as the applicable PMOD interface(s) #set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { ck_io26 }]; #IO_L24P_T3_A01_D17_14 Sch=jd10/ck_io[26] #set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { ck_io27 }]; #IO_L23N_T3_A02_D18_14 Sch=jd9/ck_io[27] #set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { ck_io28 }]; #IO_L23P_T3_A03_D19_14 Sch=jd8/ck_io[28] #set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { ck_io29 }]; #IO_L22N_T3_A04_D20_14 Sch=jd7/ck_io[29] #set_property -dict { PACKAGE_PIN T12 IOSTANDARD LVCMOS33 } [get_ports { ck_io30 }]; #IO_L22P_T3_A05_D21_14 Sch=jd4/ck_io[30] #set_property -dict { PACKAGE_PIN V13 IOSTANDARD LVCMOS33 } [get_ports { ck_io31 }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=jd3/ck_io[31] #set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { ck_io32 }]; #IO_L21P_T3_DQS_14 Sch=jd2/ck_io[32] #set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { ck_io33 }]; #IO_L20N_T3_A07_D23_14 Sch=jd1/ck_io[33] #set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { ck_io34 }]; #IO_L20P_T3_A08_D24_14 Sch=jc10/ck_io[34] #set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { ck_io35 }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=jc9/ck_io[35] #set_property -dict { PACKAGE_PIN P13 IOSTANDARD LVCMOS33 } [get_ports { ck_io36 }]; #IO_L19P_T3_A10_D26_14 Sch=jc8/ck_io[36] #set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { ck_io37 }]; #IO_L16P_T2_CSI_B_14 Sch=jc7/ck_io[37] #set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { ck_io38 }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=jc4/ck_io[38] #set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { ck_io39 }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=jc3/ck_io[39] #set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { ck_io40 }]; #IO_L18N_T2_A11_D27_14 Sch=jc2/ck_io[40] #set_property -dict { PACKAGE_PIN U15 IOSTANDARD LVCMOS33 } [get_ports { ck_io41 }]; #IO_L18P_T2_A12_D28_14 Sch=jc1/ck_io[41] ## Dedicated Analog Inputs #set_property -dict { PACKAGE_PIN J10 } [get_ports { vp_in }]; #IO_L1P_T0_AD4P_35 Sch=v_p #set_property -dict { PACKAGE_PIN K9 } [get_ports { vn_in }]; #IO_L1N_T0_AD4N_35 Sch=v_n ## ChipKit Outer Analog Header - as Single-Ended Analog Inputs ## NOTE: These ports can be used as single-ended analog inputs with voltages from 0-3.3V (ChipKit analog pins A0-A5) or as digital I/O. ## WARNING: Do not use both sets of constraints at the same time! ## NOTE: The following constraints should be used with the XADC IP core when using these ports as analog inputs. #set_property -dict { PACKAGE_PIN B13 IOSTANDARD LVCMOS33 } [get_ports { vaux0_p }]; #IO_L1P_T0_AD0P_15 Sch=ck_an_p[0] ChipKit pin=A0 #set_property -dict { PACKAGE_PIN A13 IOSTANDARD LVCMOS33 } [get_ports { vaux0_n }]; #IO_L1N_T0_AD0N_15 Sch=ck_an_n[0] ChipKit pin=A0 #set_property -dict { PACKAGE_PIN B15 IOSTANDARD LVCMOS33 } [get_ports { vaux1_p }]; #IO_L3P_T0_DQS_AD1P_15 Sch=ck_an_p[1] ChipKit pin=A1 #set_property -dict { PACKAGE_PIN A15 IOSTANDARD LVCMOS33 } [get_ports { vaux1_n }]; #IO_L3N_T0_DQS_AD1N_15 Sch=ck_an_n[1] ChipKit pin=A1 #set_property -dict { PACKAGE_PIN E12 IOSTANDARD LVCMOS33 } [get_ports { vaux9_p }]; #IO_L5P_T0_AD9P_15 Sch=ck_an_p[2] ChipKit pin=A2 #set_property -dict { PACKAGE_PIN D12 IOSTANDARD LVCMOS33 } [get_ports { vaux9_n }]; #IO_L5N_T0_AD9N_15 Sch=ck_an_n[2] ChipKit pin=A2 #set_property -dict { PACKAGE_PIN B17 IOSTANDARD LVCMOS33 } [get_ports { vaux2_p }]; #IO_L7P_T1_AD2P_15 Sch=ck_an_p[3] ChipKit pin=A3 #set_property -dict { PACKAGE_PIN A17 IOSTANDARD LVCMOS33 } [get_ports { vaux2_n }]; #IO_L7N_T1_AD2N_15 Sch=ck_an_n[3] ChipKit pin=A3 #set_property -dict { PACKAGE_PIN C17 IOSTANDARD LVCMOS33 } [get_ports { vaux10_p }]; #IO_L8P_T1_AD10P_15 Sch=ck_an_p[4] ChipKit pin=A4 #set_property -dict { PACKAGE_PIN B18 IOSTANDARD LVCMOS33 } [get_ports { vaux10_n }]; #IO_L8N_T1_AD10N_15 Sch=ck_an_n[4] ChipKit pin=A4 #set_property -dict { PACKAGE_PIN E16 IOSTANDARD LVCMOS33 } [get_ports { vaux11_p }]; #IO_L10P_T1_AD11P_15 Sch=ck_an_p[5] ChipKit pin=A5 #set_property -dict { PACKAGE_PIN E17 IOSTANDARD LVCMOS33 } [get_ports { vaux11_n }]; #IO_L10N_T1_AD11N_15 Sch=ck_an_n[5] ChipKit pin=A5 ## ChipKit Outer Analog Header - as Digital I/O ## NOTE: The following constraints should be used when using these ports as digital I/O. #set_property -dict { PACKAGE_PIN G13 IOSTANDARD LVCMOS33 } [get_ports { ck_a0 }]; #IO_0_15 Sch=ck_a[0] #set_property -dict { PACKAGE_PIN B16 IOSTANDARD LVCMOS33 } [get_ports { ck_a1 }]; #IO_L4P_T0_15 Sch=ck_a[1] #set_property -dict { PACKAGE_PIN A16 IOSTANDARD LVCMOS33 } [get_ports { ck_a2 }]; #IO_L4N_T0_15 Sch=ck_a[2] #set_property -dict { PACKAGE_PIN C13 IOSTANDARD LVCMOS33 } [get_ports { ck_a3 }]; #IO_L6P_T0_15 Sch=ck_a[3] #set_property -dict { PACKAGE_PIN C14 IOSTANDARD LVCMOS33 } [get_ports { ck_a4 }]; #IO_L6N_T0_VREF_15 Sch=ck_a[4] #set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 } [get_ports { ck_a5 }]; #IO_L11P_T1_SRCC_15 Sch=ck_a[5] ## ChipKit Inner Analog Header - as Differential Analog Inputs ## NOTE: These ports can be used as differential analog inputs with voltages from 0-1.0V (ChipKit analog pins A6-A11) or as digital I/O. ## WARNING: Do not use both sets of constraints at the same time! ## NOTE: The following constraints should be used with the XADC core when using these ports as analog inputs. #set_property -dict { PACKAGE_PIN B14 IOSTANDARD LVCMOS33 } [get_ports { vaux8_p }]; #IO_L2P_T0_AD8P_15 Sch=ad_p[8] ChipKit pin=A6 #set_property -dict { PACKAGE_PIN A14 IOSTANDARD LVCMOS33 } [get_ports { vaux8_n }]; #IO_L2N_T0_AD8N_15 Sch=ad_n[8] ChipKit pin=A7 #set_property -dict { PACKAGE_PIN D16 IOSTANDARD LVCMOS33 } [get_ports { vaux3_p }]; #IO_L9P_T1_DQS_AD3P_15 Sch=ad_p[3] ChipKit pin=A8 #set_property -dict { PACKAGE_PIN D17 IOSTANDARD LVCMOS33 } [get_ports { vaux3_n }]; #IO_L9N_T1_DQS_AD3N_15 Sch=ad_n[3] ChipKit pin=A9 ## ChipKit Inner Analog Header - as Digital I/O ## NOTE: The following constraints should be used when using the inner analog header ports as digital I/O. #set_property -dict { PACKAGE_PIN B14 IOSTANDARD LVCMOS33 } [get_ports { ck_a6 }]; #IO_L2P_T0_AD8P_15 Sch=ad_p[8] #set_property -dict { PACKAGE_PIN A14 IOSTANDARD LVCMOS33 } [get_ports { ck_a7 }]; #IO_L2N_T0_AD8N_15 Sch=ad_n[8] #set_property -dict { PACKAGE_PIN D16 IOSTANDARD LVCMOS33 } [get_ports { ck_a8 }]; #IO_L9P_T1_DQS_AD3P_15 Sch=ad_p[3] #set_property -dict { PACKAGE_PIN D17 IOSTANDARD LVCMOS33 } [get_ports { ck_a9 }]; #IO_L9N_T1_DQS_AD3N_15 Sch=ad_n[3] #set_property -dict { PACKAGE_PIN D14 IOSTANDARD LVCMOS33 } [get_ports { ck_a10 }]; #IO_L12P_T1_MRCC_15 Sch=ck_a10_r (Cannot be used as an analog input) #set_property -dict { PACKAGE_PIN D15 IOSTANDARD LVCMOS33 } [get_ports { ck_a11 }]; #IO_L12N_T1_MRCC_15 Sch=ck_a11_r (Cannot be used as an analog input) ## ChipKit I2C #set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { ck_scl }]; #IO_L24N_T3_RS0_15 Sch=ck_scl #set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { ck_sda }]; #IO_L24P_T3_RS1_15 Sch=ck_sda ## Misc. ChipKit Ports #set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { ck_ioa }]; #IO_25_15 Sch=ck_ioa #set_property -dict { PACKAGE_PIN C18 IOSTANDARD LVCMOS33 } [get_ports { ck_rst }]; #IO_L11N_T1_SRCC_15 ## Quad SPI Flash ## Note: the SCK clock signal can be driven using the STARTUPE2 primitive #set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { qspi_cs }]; #IO_L6P_T0_FCS_B_14 Sch=qspi_cs #set_property -dict { PACKAGE_PIN K17 IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[0] }]; #IO_L1P_T0_D00_MOSI_14 Sch=qspi_dq[0] #set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[1] }]; #IO_L1N_T0_D01_DIN_14 Sch=qspi_dq[1] #set_property -dict { PACKAGE_PIN L14 IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[2] }]; #IO_L2P_T0_D02_14 Sch=qspi_dq[2] #set_property -dict { PACKAGE_PIN M15 IOSTANDARD LVCMOS33 } [get_ports { qspi_dq[3] }]; #IO_L2N_T0_D03_14 Sch=qspi_dq[3] ## Configuration options, can be used for all designs set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] set_property CONFIG_VOLTAGE 3.3 [current_design] set_property CFGBVS VCCO [current_design] set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] set_property CONFIG_MODE SPIx4 [current_design] ## SW3 is assigned to a pin M5 in the 1.35v bank. This pin can also be used as ## the VREF for BANK 34. To ensure that SW3 does not define the reference voltage ## and to be able to use this pin as an ordinary I/O the following property must ## be set to enable an internal VREF for BANK 34. Since a 1.35v supply is being ## used the internal reference is set to half that value (i.e. 0.675v). Note that ## this property must be set even if SW3 is not used in the design. set_property INTERNAL_VREF 0.675 [get_iobanks 34]
VGA Driver
VGA Driver is based on RTSYork/zybo-vga: VGA output for the Digilent Zybo board (github.com) and Digilent/ZYBO (github.com)
Demo video: Animation 800x600 @60Hz with double buffer. It takes a long time to generate the images, but for the purpose of the thermal camera it will serve us well.
Capture of the digital signals in the Pmod JC connector during the making of the video.
Pmod JC output, green color [3:0], vsync & hsync
Generation of a nice gradient of colors depending on the horizontal and vertical position. We work with RGB444, RGB colors, not chroma subsampling.
The Pmod VGA module directly connected to a 19-inch Philips monitor. We have not been able to make it work at 640x480, the minimum resolution with which we have been able to generate images has been 800x600@60Hz
Example code for the animation video. Handles double buffering to refresh the screen without flickering.
/** * Example of using the Digilent display drivers for Digilent Arty S7, with animation */ #include <stdio.h> #include "xil_types.h" #include "xil_cache.h" #include "xil_printf.h" #include "xparameters.h" #include "zybo_vga/display_ctrl.h" // Frame size (based on 1440x900 resolution, 32 bits per pixel) #define MAX_FRAME (800*600) #define FRAME_STRIDE (800*4) static const int BALL_SIZE = 24; DisplayCtrl dispCtrl; // Display driver struct u32 frameBuf[DISPLAY_NUM_FRAMES][MAX_FRAME]; // Frame buffers for video data void *pFrames[DISPLAY_NUM_FRAMES]; // Array of pointers to the frame buffers int main(void) { xil_printf("Successfully started vga example\r\n"); // Initialise an array of pointers to the 2 frame buffers int i; for (i = 0; i < DISPLAY_NUM_FRAMES; i++) pFrames[i] = frameBuf[i]; // Initialise the display controller DisplayInitialize(&dispCtrl, XPAR_AXIVDMA_0_DEVICE_ID, XPAR_VTC_0_DEVICE_ID, XPAR_VGA_AXI_DYNCLK_0_BASEADDR, pFrames, FRAME_STRIDE); // Start with the first frame buffer (of two) DisplayChangeFrame(&dispCtrl, 0); // Set the display resolution DisplaySetMode(&dispCtrl, &VMODE_800x600); // Enable video output DisplayStart(&dispCtrl); // Get parameters from display controller struct int x, y; u32 stride = dispCtrl.stride / 4; u32 width = dispCtrl.vMode.width; u32 height = dispCtrl.vMode.height; u32 *frame; int right = 1; int down = 1; int xpos = 0; int ypos = 0; int vel = 4; u32 buff = dispCtrl.curFrame; while (1) { // Switch the frame we're modifying to be back buffer (1 to 0, or 0 to 1) buff = !buff; frame = dispCtrl.framePtr[buff]; // Clear the frame to white memset(frame, 0xFF, MAX_FRAME*4); // Adjust the position of the square if (right) { xpos += vel; if (xpos == width - BALL_SIZE) right = 0; } else { xpos -= vel; if (xpos <= 0) right = 1; } if (down) { ypos += vel; if (ypos == height - BALL_SIZE) { down = 0; } } else { ypos -= vel; if (ypos <= 0) { down = 1; } } // Draw black square on the screen for (x = xpos; x < xpos + BALL_SIZE; x++) { for (y = ypos; y < ypos + BALL_SIZE; y++) { frame[y * stride + x] = 0x00; } } // Flush everything out to DDR Xil_DCacheFlush() ; // Switch active frame to the back buffer DisplayChangeFrame(&dispCtrl, buff); // Wait for the frame to switch (after active frame is drawn) before continuing DisplayWaitForSync(&dispCtrl); } return 0; }
MLX90640 IR Array Driver
Communication with the thermal sensor is done through I2C protocol.
The device use I2C protocol with support of FM+ mode (up to 1MHz clock frequency) and can be only one slave on the bus.
Start / Stop conditions
Each communication session is initiated by a START condition and ends with a STOP condition. A START condition is initiated by a HIGH to LOW transition of the SDA while a STOP is generated by a LOW to HIGH transition. Both changes must be done while the SCL is HIGH.
Device addressing
The master is addressing the slave device by sending a 7-bit slave address after the START condition. The first seven bits are dedicated for the address and the 8th is Read/Write (R/W) bit. This bit indicates the direction of the transfer:
- Read (HIGH) means that the master will read the data from the slave
- Write (LOW) means that the master will send data to the slave
Acknowledge
During the 9th clock following every byte transfer the transmitter releases the SDA line. The receiver acknowledges (ACK) receiving the byte by pulling SDA line to low or does not acknowledge (NoACK) by letting the SDA ‘HIGH’.
Reading patterns
The array frame is divided in two subpages and depending of bit 12 in “Control register 1” (0x800D) – “Reading pattern” there are two modes of the pixel arrangement:
- Chess pattern mode (factory default)
- TV interleave mode
We will use the Chess pattern mode:
Setting up the environment for the test
In the image you can see the system configured for the tests, there are two Pmod modules. The Pmod VGA that we connect to the VGA display and the other Pmod TPH2 Test Point Header that allow us to spy on the signals of the pmod ports.
The Digilent Analog Discovery 2 logic analyzer is connected to the same I2C bus that the thermal array sensor is connected to.
Pmod TPH2: 12-pin Test Point Header - Digilent
Debugging the I2C communication with the Digilent Analog Discovery II. Digilent Waveforms Logic Analyzer capture
Test source code
This is the source code for the test animation. Move a small square on the screen by bouncing off the edges.
/** * 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 created in the * xparameters.h file. They are defined here such that a user can easily * change all the needed parameters in one 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, }; int main() { init_platform(); int Status; XIic_Config *ConfigPtr; /* Pointer to configuration data */ init_platform(); ConfigPtr = XIic_LookupConfig(XPAR_IIC_0_DEVICE_ID); if (ConfigPtr == NULL) { return XST_FAILURE; } // print("XIic_LookupConfig\n\r"); Status = XIic_CfgInitialize(&IicInstance, ConfigPtr, ConfigPtr->BaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("XIic_CfgInitialize\n\r"); /* * Setup the Interrupt System. */ Status = SetupInterruptSystem(&IicInstance); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("SetupInterruptSystem\n\r"); /* * Set the Transmit, Receive and Status handlers. */ XIic_SetSendHandler(&IicInstance, &IicInstance, (XIic_Handler) SendHandler); // print("XIic_SetSendHandler\n\r"); XIic_SetStatusHandler(&IicInstance, &IicInstance, (XIic_StatusHandler) StatusHandler); // print("XIic_SetStatusHandler\n\r"); XIic_SetRecvHandler(&IicInstance, &IicInstance, (XIic_Handler) ReceiveHandler); // print("XIic_SetRecvHandler\n\r"); /* * Set the Address of the Slave. */ Status = XIic_SetAddress(&IicInstance, XII_ADDR_TO_SEND_TYPE, IIC_SLAVE_ADDR); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("XIic_SetAddress\n\r"); /* * Start the IIC device. */ Status = XIic_Start(&IicInstance); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("XIic_Start\n\r"); static u16 mlx90640Frame[834]; static uint16_t eeMLX90640[832]; paramsMLX90640 mlx90640; float Ta; float emissivity = 0.95; static float mlx90640To[768]; MLX90640_DumpEE(0x33, eeMLX90640); // print("MLX90640_DumpEE\n\r"); MLX90640_ExtractParameters(eeMLX90640, &mlx90640); // print("MLX90640_ExtractParameters\n\r"); while (1) { MLX90640_GetFrameData(0x33, mlx90640Frame); // print("MLX90640_GetFrameData\n\r"); Ta = MLX90640_GetTa(mlx90640Frame, &mlx90640) - TA_SHIFT; // print("MLX90640_GetTa\n\r"); MLX90640_CalculateTo(mlx90640Frame, &mlx90640, emissivity, Ta, mlx90640To); // print("MLX90640_CalculateTo\n\r"); for (int i = 0; i < 24; i++) { for (int y = 0; y < 32; y++) { xil_printf(" %d ", (int) (mlx90640To[y + (i * 32)])); } xil_printf("\n\r"); } //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\n\r"); 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) { return XST_FAILURE; } // print("XIic_MasterSend\n\r"); usleep(1000); /* * This is for verification 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) { return XST_FAILURE; } usleep(1000); // print("XIic_MasterRecv\n\r"); 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) { return XST_FAILURE; } print("XIic_MasterSend\n\r"); /* * Wait till data is transmitted. */ // while (TransmitComplete) { // // } /* * This is for verification 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; } return 0; } /*****************************************************************************/ /** * This function setups the interrupt system so interrupts can occur for the * IIC. The function is 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 this function to fit the application. * * @param IicInstPtr contains a pointer to the instance of the IIC which * is going to be connected to the interrupt controller. * * @return XST_SUCCESS if successful else XST_FAILURE. * * @note None. * ******************************************************************************/ static int SetupInterruptSystem(XIic *IicInstPtr) { int Status; if (InterruptController.IsStarted == XIL_COMPONENT_IS_STARTED) { return XST_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; }
The VGA Thermal Imaging Camera Application
Finally we join the two functionalities in a single application, the graphic animation with VGA output with the reading of the thermal array sensor to build a thermal imaging camera application with VGA output.
With this application we can see the sensor readings in real time graphically on a VGA monitor.
As for example in the following animation we can "see" the temperatures on the surface of the Spartan-7 FPGA while testing this application.
Spartan-7 Thermal Image during VGA Thermal Imaging Camera Application execution
The sensor points to the Spartan-7 FPGA chip.
Thermal Camera Source Code
Vitis project structure
Linker Script
The application can be downloaded from the github repository GitHub - javagoza/E14SpartanMigrationProgram
For the generation of a new frame, new data is requested from the sensor. The maximum and minimum temperatures reported by the sensor are calculated. Minimum and maximum temperature are mapped with a predefined scale of 256 colors. The scale is in RGB565, we will convert RGB444 to RGB888. This process is avoidable but has been left to work with other visualization media.
Finally, the buffer to be drawn is exchanged for the active buffer and the image is displayed on the screen. Ready to generate a new frame.
/** * Example of using the Digilent display drivers for Digilent Arty S7, with animation */ #include <stdio.h> #include "xil_types.h" #include "xil_cache.h" #include "xil_printf.h" #include "xparameters.h" #include "zybo_vga/display_ctrl.h" #include "mlx90640_api.h" #include "platform.h" #include "xiic.h" #include "xintc.h" #include "xil_exception.h" #include "sleep.h" /* * The following constants map to the XPAR parameters created in the * xparameters.h file. They are defined here such that a user can easily * change all the needed parameters in one 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 // Frame size (based on 1440x900 resolution, 32 bits per pixel) #define MAX_FRAME (800*600) #define FRAME_STRIDE (800*4) DisplayCtrl dispCtrl; // Display driver struct u32 frameBuf[DISPLAY_NUM_FRAMES][MAX_FRAME]; // Frame buffers for video data void *pFrames[DISPLAY_NUM_FRAMES]; // Array of pointers to the frame buffers 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; #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 u32 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, }; int main(void) { init_platform(); int Status; XIic_Config *ConfigPtr; /* Pointer to configuration data */ init_platform(); ConfigPtr = XIic_LookupConfig(XPAR_IIC_0_DEVICE_ID); if (ConfigPtr == NULL) { return XST_FAILURE; } // print("XIic_LookupConfig\n\r"); Status = XIic_CfgInitialize(&IicInstance, ConfigPtr, ConfigPtr->BaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("XIic_CfgInitialize\n\r"); /* * Setup the Interrupt System. */ Status = SetupInterruptSystem(&IicInstance); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("SetupInterruptSystem\n\r"); /* * Set the Transmit, Receive and Status handlers. */ XIic_SetSendHandler(&IicInstance, &IicInstance, (XIic_Handler) SendHandler); // print("XIic_SetSendHandler\n\r"); XIic_SetStatusHandler(&IicInstance, &IicInstance, (XIic_StatusHandler) StatusHandler); // print("XIic_SetStatusHandler\n\r"); XIic_SetRecvHandler(&IicInstance, &IicInstance, (XIic_Handler) ReceiveHandler); // print("XIic_SetRecvHandler\n\r"); /* * Set the Address of the Slave. */ Status = XIic_SetAddress(&IicInstance, XII_ADDR_TO_SEND_TYPE, IIC_SLAVE_ADDR); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("XIic_SetAddress\n\r"); /* * Start the IIC device. */ Status = XIic_Start(&IicInstance); if (Status != XST_SUCCESS) { return XST_FAILURE; } // print("XIic_Start\n\r"); static u16 mlx90640Frame[834]; static uint16_t eeMLX90640[832]; paramsMLX90640 mlx90640; float Ta; float emissivity = 0.95; static float mlx90640To[768]; MLX90640_DumpEE(0x33, eeMLX90640); // print("MLX90640_DumpEE\n\r"); MLX90640_ExtractParameters(eeMLX90640, &mlx90640); // print("MLX90640_ExtractParameters\n\r"); xil_printf("Successfully started vga example\r\n"); // Initialise an array of pointers to the 2 frame buffers int i; for (i = 0; i < DISPLAY_NUM_FRAMES; i++) pFrames[i] = frameBuf[i]; // Initialise the display controller DisplayInitialize(&dispCtrl, XPAR_AXIVDMA_0_DEVICE_ID, XPAR_VTC_0_DEVICE_ID, XPAR_VGA_AXI_DYNCLK_0_BASEADDR, pFrames, FRAME_STRIDE); // Start with the first frame buffer (of two) DisplayChangeFrame(&dispCtrl, 0); // Set the display resolution DisplaySetMode(&dispCtrl, &VMODE_800x600); // Enable video output DisplayStart(&dispCtrl); // Get parameters from display controller struct int x, y; u32 stride = dispCtrl.stride / 4; u32 width = dispCtrl.vMode.width; u32 height = dispCtrl.vMode.height; u32 *frame; u32 buff = dispCtrl.curFrame; while (1) { MLX90640_GetFrameData(0x33, mlx90640Frame); // print("MLX90640_GetFrameData\n\r"); Ta = MLX90640_GetTa(mlx90640Frame, &mlx90640) - TA_SHIFT; // print("MLX90640_GetTa\n\r"); MLX90640_CalculateTo(mlx90640Frame, &mlx90640, emissivity, Ta, mlx90640To); float maxTemp = 0.0; float minTemp = 9999.99; for (int i = 0; i < 768; ++i) { if (mlx90640To[i] < minTemp) { minTemp = mlx90640To[i]; } if (mlx90640To[i] > maxTemp) { maxTemp = mlx90640To[i]; } } // Switch the frame we're modifying to be back buffer (1 to 0, or 0 to 1) buff = !buff; frame = dispCtrl.framePtr[buff]; // Clear the frame to white // memset(frame, 0xFF, MAX_FRAME * 4); float temp; u8 mapped; u32 colour; int scale = 4; int frameWidth = WIDTH * scale; int frameHeight = HEIGHT * scale; int yo = (height - frameHeight) / 2 ; int xo = (width - frameWidth) /2; for (y = 0; y < frameHeight; y++) for (x = 0; x < frameWidth; x++) { temp = (mlx90640To[x / scale + y / scale * 32]); mapped = map((u16) temp, minTemp, maxTemp, 0, 255); colour = (((camColors[mapped] >> 12) & 0x0F) << (BIT_DISPLAY_RED + 4)) | (((camColors[mapped] >> 7) & 0x0F) << (BIT_DISPLAY_GREEN + 4)) | ((camColors[mapped] >> 1) & 0x0F) << (BIT_DISPLAY_BLUE + 4); frame[(y+yo) * stride + x + xo] = colour; } // Flush everything out to DDR Xil_DCacheFlush() ; // Switch active frame to the back buffer DisplayChangeFrame(&dispCtrl, buff); // Wait for the frame to switch (after active frame is drawn) before continuing DisplayWaitForSync(&dispCtrl); } /* * 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; } int MLX90640_I2CRead(uint8_t slaveAddr, uint16_t startAddress, uint16_t nMemAddressRead, uint16_t *data) { // print("MLX90640_I2CRead\n\r"); 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) { return XST_FAILURE; } // print("XIic_MasterSend\n\r"); usleep(1000); /* * This is for verification 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) { return XST_FAILURE; } usleep(1000); // print("XIic_MasterRecv\n\r"); 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) { return XST_FAILURE; } print("XIic_MasterSend\n\r"); /* * Wait till data is transmitted. */ // while (TransmitComplete) { // // } /* * This is for verification 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; } return 0; } /*****************************************************************************/ /** * This function setups the interrupt system so interrupts can occur for the * IIC. The function is 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 this function to fit the application. * * @param IicInstPtr contains a pointer to the instance of the IIC which * is going to be connected to the interrupt controller. * * @return XST_SUCCESS if successful else XST_FAILURE. * * @note None. * ******************************************************************************/ static int SetupInterruptSystem(XIic *IicInstPtr) { int Status; if (InterruptController.IsStarted == XIL_COMPONENT_IS_STARTED) { return XST_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; }
{gallery}Thermal Images |
---|
Summary
We have managed to build a simple low resolution thermal imaging camera with VGA output.
There is a lot of room for improvement and optimization. Aspects to improve:
- Perform image scaling in HDL. Reduces the amount of memory to use from 800*600 pixels (480,000 pixels) per frame to 32*24 (768 pixels)
- Working the pixels as unsigned 16 instead of unsigned 32 reduces the amount of memory handled and needed by half. We only need 12 bits per pixel.
- Perform color space conversion in HDL.
- Treat only the changed sensor cells in each reading. Reduces the number of cells by half for each frame.