element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
FPGA
  • Technologies
  • More
FPGA
Blog Arty S7 50 VGA Thermal Imaging Camera
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join FPGA to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: javagoza
  • Date Created: 4 Aug 2022 11:21 PM Date Created
  • Views 14273 views
  • Likes 8 likes
  • Comments 4 comments
  • arty-s7
  • spartan 7
  • AMD XILINX
  • 7 Ways to Leave Your Spartan-6
  • fpga_projects
  • fpga
  • vivado
  • digilent
  • fpga-project
  • spartan-7
  • thermal camera
  • mlx90640
  • vitis
  • Spartan_Migration
  • pmodVGA
  • microblaze
  • iic
Related
Recommended

Arty S7 50 VGA Thermal Imaging Camera

javagoza
javagoza
4 Aug 2022
Arty S7 50 VGA Thermal Imaging Camera

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.

"7 Ways to Leave Your Spartan-6" related posts
1 Arty S7 50 First Power Up and Hardware-Only Blinky
2 Arty S7 50 First Baremetal Software Project
3 Arty S7 50 Rapid Prototyping - Environmental Monitor
4 Arty S7 50 ArtyBot Pulse Width Modulation (PWM) for Motor Speed Control
5 Arty S7 50 ArtyBot Custom AXI4 Lite IP Peripheral for Sensing Motor Rotational Speed
6 Arty S7 50 ArtyBot How to Store MicroBlaze Program in the Quad-SPI Flash from Vivado
7 Arty S7 50 ArtyBot - Bot Application Framework
8 Arty S7 50 ArtyBot becomes Emubot, an educational robot for young children
9 Arty S7 50 ArtyBot - Color sensing and line follower
10 Arty S7 50 The Spartan-6 Migration Path Game. Learning the Differences Between Spartan-6 and Spartan-7 FPGAs
11 Arty S7 50 ArtyBot ToF Sensor for Obstacle Avoidance. Vivado Hierarchical Blocks
12 "7 Ways to Leave Your Spartan-6 FPGA" program. My summary
13 Arty S7 50 VGA Thermal Imaging Camera

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.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image


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

SparkFun IR Array Breakout

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.

Arty_s7_sch-rev_b.pdf

image

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

Digilent Pmod_VGA

Reference: Pmod VGA Reference Manual - Digilent Reference

Pmod VGA Schematics


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

image


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.

image


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 Thermal Imaging Camera 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

VGA Thermal Imaging Camera VGA Hierarchy Block Design


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

image

ADD SOURCES: Add Digilent AXI DYNCLK

image

ADD SOURCES: Add Digilent rgb2vga

image

CREATE BLOCK DESIGN: New Block Design thermal_camera_bd

image

CREATE NEW HIERARCHY: In Block Design Right Click & Create Hierarchy

image

Crete Hierarchy: Name vga the new hierarchy and open it

image

Add IP Block: AXI VIdeo Direct Memory Access 

image

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.

image

Configure the AXI Video Direct Memory AccessAdvanced: Set GenLock Mode for the Read Channel Options to Master

image

Add IP: Video Timing Controller

image

Configure the Video Timing Controller: Deselect Enable Detection 

image

Add IP: AXI4-Stream to Video Out

image

Configure the AXI4-Stream to Video Out: Change FIFO Depth to 32 // Set Clock Mode to Independent // Set Timing Mode to Master

image

Add IP: Add Slice

image

Configure Slice: Din From 23

image

Add Module: rgb2vga

image

Configure rgb2vga: Set Blue, Green, and Red color depth to 4

image

Add Module: axi_dynclk

image

Hierarchy Block Diagram: Regenerate Layout

image

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

image

Create Hierarchy Pins: After pin creation

image

Hierarchy connections: Make the following hierarchy connections

image

Create External Ports: From Main Block Diagram right click the Hierarchy Pins and create the external Ports

image

Create External Ports: Name the ports, vga_red, vga_green, vga_blue, vga_hsync and vga_vsync

image

Create External Ports: After Creation

Add microblaze and peripherals. More information about this process in our introductory tutorial blogs:

  • Arty S7 50 First Baremetal Software Project
  • Arty S7 50 Rapid Prototyping - Environmental Monitor

{gallery}Add Microblaze & Peripherals

image

Add DDR3 SDRAM Memory: From Board Tab Auto connect DDR3 SDRAM

image

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.

image

Connect the “ui_addn_clk_0”: Connect the “ui_addn_clk_0” pin to the “clk_ref_i” pin

image

Modify Constraints File: Create clock sys_clk_i

image

Run Connection Automation: Run connection Automation And connect Reset

image

Connection Automation: Connect Reset

image

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 

image

Connect Constant: Connect its output port to the “device_temp_i” port on the MIG.

image

Add Microblaze IP: Add Microblaze IP

image

Run Block Automation: 

image

image

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.

image

Run Connection Automation: Now run connection automation for the VGA Hierarchy

image

After Connection Automation

image

Connect vid_io_out_clk:Open vga Hierarchy and connect vid_io_out_clk to aclk

image

Add peripherals: From board add LEDs, Switches I2C on J3 and USB UART (UART Lite 9600 bauds)

image

Run connection automation

image

Modify constraints: PmodVGA module on JB and JC Connectors

image

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.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

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

image

Generation of a nice gradient of colors depending on the horizontal and vertical position. We work with RGB444, RGB colors, not chroma subsampling.

image

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

image

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’.

image

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:

image


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

Arty S7 Thermal Camera

Debugging the I2C communication with the Digilent Analog Discovery II. Digilent Waveforms Logic Analyzer capture

image


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

image

The sensor points to the Spartan-7 FPGA chip.

image


Thermal Camera Source Code 

Vitis project structure

image

Linker Script

image

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

image

image

image


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.


"7 Ways to Leave Your Spartan-6" related posts
1 Arty S7 50 First Power Up and Hardware-Only Blinky
2 Arty S7 50 First Baremetal Software Project
3 Arty S7 50 Rapid Prototyping - Environmental Monitor
4 Arty S7 50 ArtyBot Pulse Width Modulation (PWM) for Motor Speed Control
5 Arty S7 50 ArtyBot Custom AXI4 Lite IP Peripheral for Sensing Motor Rotational Speed
6 Arty S7 50 ArtyBot How to Store MicroBlaze Program in the Quad-SPI Flash from Vivado
7 Arty S7 50 ArtyBot - Bot Application Framework
8 Arty S7 50 ArtyBot becomes Emubot, an educational robot for young children
9 Arty S7 50 ArtyBot - Color sensing and line follower
10 Arty S7 50 The Spartan-6 Migration Path Game. Learning the Differences Between Spartan-6 and Spartan-7 FPGAs
11 Arty S7 50 ArtyBot ToF Sensor for Obstacle Avoidance. Vivado Hierarchical Blocks
12 "7 Ways to Leave Your Spartan-6 FPGA" program. My summary
13 Arty S7 50 VGA Thermal Imaging Camera
  • Sign in to reply
Parents
  • genebren
    genebren over 3 years ago

    Pretty cool thermal imager.  Looks like the FPGA is running a bit hot.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • genebren
    genebren over 3 years ago

    Pretty cool thermal imager.  Looks like the FPGA is running a bit hot.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
  • javagoza
    javagoza over 3 years ago in reply to genebren

    Thanks! The sensor read MAX Temp: 54.7, MIN Temp 31.87 during the video. 

    To improve the contrast I am calculating for each frame the maximum and minimum temperature and then I map that range with a scale of 256 colors.

    The project can be optimized a lot, now there is a lot of work assigned to the Microblaze soft processor such as image scaling that I am going to delegate to another module in HDL. And besides, it would considerably increase the frame rate, now I lose a lot of time in scaling that would not be necessary, in addition to only refreshing only half of the data each time, only the ones that the MLX90640 sensor updates each time it sends data. 

    This will change from handling 800*600 pixels (480.000 pixels) per frame to handling 16*12 (192 pixels) per frame at full scale for 800x600 resolution

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • genebren
    genebren over 3 years ago in reply to javagoza

    Your changes sound like that they should really speed things up.

    While playing around with the GridEye sensor, I kept flipping between constant rescaling and fixed scaling of the thermal data.  In the end I decided to use a fixed color scaling as it was easier for me to visualize the temperatures as they increased from ambient, where with rescaling the images look very similar as the temperatures increased.

    Have fun!

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza over 3 years ago in reply to genebren

    Thanks for mentioning the Grid-EYE sensor, I didn't know about it. I have found in element14 many references to very clever uses of the Grid-EYE sensor. Many ideas for new projects.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube