Saturday 22 April 2017

Create a Spirometer using the MPX7002DP Differential Pressure Sensor

The previous post discussed how to use the MSP7002DP differential pressure sensor:

Using MPX7002DP differentia pressure sensor

This post will discuss how to take that sensor and use it to make a spirometer - a measurement instrument used to measure air inhalation and exhalation.  Doctors and medical professionals use a spirometer to assess a person's respiratory function.  The information is then used to provide treatment for such conditions such as Asthma, Emphysema, chronic bronchitis etc.

If more information about Spirometry is required please check out the links below:

https://www.asthmafoundation.org.nz/your-health/living-with-copd/what-is-spirometry

https://patient.info/health/spirometry-leaflet

https://en.wikipedia.org/wiki/Spirometer

In order to make a spirometer we will need a special kind of tube known as a Pnuemotachograph, A Pneumotachograph is a tube with two distinct sections separated by a known restrictive plate.  I didn't make my own Pneumotachograph, as helpfully, a friend who is also a doctor, supplied me with one! If people do need to make their own tube they could be easily constructed or 3D printed.

A medical pneumotacograph
The two pipes shown will be attached to the MSP7002DP Differential Pressure sensor.  When someone breathes in and out through the tube a difference in pressure will be measured due to the difference in pressure in either tube and this will be passed to the microcontroller and displayed.  At the moment the results would be displayed in Pascals (Pa).  But in order to measure lung function this value needs to be converted to litres / minute.

The theory behind all this was first discovered by a very clever Swiss gentlemen called Daniel Bernoulli:

https://en.wikipedia.org/wiki/Daniel_Bernoulli

Amongst other work Mr Bernoulli discovered that one could calculate the rate at which fluids and gases flow. He noticed that if the pressure applied to a fluid or gas decreases then at the same time the speed or velocity of the fluid or gas would increase.  This has become known as the 'Bernoulli Principle' and is used considerably in science and engineering - particularly in aerodynamics.

We are going to apply Bernoulli's principle to calculate the velocity of the air flow (volumetric Flow Rate) when someone breathes into the pneumotachograph and from that calculate volumetric flow which is measured in litres / second or litres per minute.

https://www.grc.nasa.gov/www/k-12/airplane/bern.html

The mathematics is quite complicated and I have never been particularly good at mathematics...Here is the theory and the associated mathematics:

In a pneumotachograph tube the downstream pressure after the obstruction (a thin plate like flap in the spherical section) will be lower than the upstream pressure in the first part of the tube. To understand pneumotachograph tubes it is necessary to explore Bernoulli's equation.  If it can be assumed that a fluid flows in a horizontal manner (neglecting the minor elevation difference between the measuring points) then Bernoulli's equation can be expressed as:



where:

P1 is the pressure in first part of tube and P2 is the pressure in the second part of the tube.

ρ = density of air at standard temperature and pressure in kg/m3

v = flow velocity in metres / second

Again making an assumption that the air flows in each section without restriction - the continuity equation can be expressed as:



where

Q = flow rate in metres / second

A1 = cross sectional area of first part of the tube in m3
A2 = cross sectional area of the second part of the tube in m3

Combining (1) and (2), gives the equation needed:



The above equation is technically an application of the Venturi Effect.  Both Venturi and Bernoulli worked on similar experiments and could be equally credited.  More information on the Venturi Effect here:

https://en.wikipedia.org/wiki/Venturi_effect

If we apply the equation to the Pneumotachograph tube dimensions and the differential pressure measured it should be possible to calculate the flow rate (Q) and from that we can then re-arrange equation 2 and calculate V1 or V2.



From this information we can write some firmware to calculate the velocity of flow directly from the differential pressure measurement made by the MSP7002DP sensor.

My one concern with this method is that I have no information concerning the internal dimensions of the pneumotach tube provided.  I have measured it as best as I can and I have also performed some simple experiments but I suspect the accuracy will be slightly compromised because I don't have that information.  I think this will work but as I'm striving for accuracy it would be better to have the datasheet.  I may well make my own to compare to it - that way I have the mechanical data and I can share how to make a pneumotach tube with everyone else...which might be useful...

Anyway having measured the length and diameter of the tube section, the cross sectional area can easily be calculated.  The middle section of the device is (sort of) conical and therefore I need to take account of that extra area.  The area of a cone can be calculated with:




I'm guessing at the radius of the conical section as I have no way of accurately measuring it.

Lets say that the cone radius is 12 mm

Lets say that the cone height is 24 mm

The area of the full cone is:





but....because the shape is not a full cone...it's top section has been removed it's a type of shape known as a conical frustum and the area of a conical frustum is calculated by:









Next the tube section must be calculated.  The formula for that is:







If we now add those two figures together we have the area of the first section of the pneumotach tube.





which is 0.00309579817057 m2

The areas of each section of the tube are the same so A1 = A2.

The fluid density of air is 1.225 kg / cubed metre, lets select P1 as 320 Pa and P2 as 200 Pa and then lets plug all of the values into the first formula and find the volumetric flow rate:











Lets now apply the velocity of flow formula:







Well...that was a considerable amount of work!  I'm not convinced this is quite correct.  I would be more comfortable with applying known values for the areas for the different sections of the pneumotach tube.  Incorrect values for area will affect the accuracy...

Anyway...with the mathematics sorted we can now write a program which performs the conversions for us.

Note m/s can be converted to litres / second by multiplying by 1000.

Here is the arduino code to read in the differential pressure measurement and then calculate the volumetric flow rate and the velocity of flow:

   
 // MPX7002DP Test Code with conversion   
 // to volumetric flow rate and velocity   
 // of flow  
 //  
 // A.Lang - 2017  
   
 // This code exercises the MPX7002DP  
 // Pressure sensor connected to A0  
   
 //variables  
   
 int sampleNumber = 0; // variable to store the sample number   
 int sensorPin = A0; // select the input pin for the Pressure Sensor  
 int sensorValue = 0; // variable to store the Raw Data value coming from the sensor  
 float diffPressure = 0; // variable to store converted kPa value   
 float volumetricFlowExhale = 0; // variable to store volumetric flow rate value when Exhaling  
 float volumetricFlowInhale = 0; // variable to store volumetric flow rate value when Inhaling  
 float velocityFlowExhale = 0; // variable to store velocity of flow value when Exhaling  
 float velocityFlowInhale = 0; // variable to store velocity of flow value when Inhaling  
 float offset = 0; // variable to store offset differential pressure  
   
 //constants - these will not change  
 const float tubeArea1 = 0.003095; // area of pneumotach first section  
 const float tubeArea2 = 0.003094; // area of pneumotach second section  
 const float airDensity = 1.225;  
   
 void setup() {  
  // start serial port at 9600 bps and wait for port to open:  
  Serial.begin(9600);  
   
  pinMode(sensorPin, INPUT); // Pressure sensor is on Analogue pin 0  
   
  Serial.flush();  
  Serial.println();  
   
  //Header for CSV data  
   
  Serial.print("Sample Number,Differential Pressure, Volumetric Flow Rate (Exhale), Volumetric Flow Rate (Inhale), Velocity of Flow Exhale, Velocity of Flow Inhale,");  
  Serial.println();  
  Serial.print("       ,     Pa     ,   m^3/second       ,     m^3/second      ,      m/s     ,      m/s     ,");  
  Serial.println();  
   
 }  
   
 void loop() {  
   
  // read the value from the sensor:   
  sensorValue = analogRead(sensorPin);  
   
  // initial value   
  sensorValue = sensorValue - 48;  
   
  // increment sample counter   
  sampleNumber++;  
   
  // map the Raw data to kPa  
  diffPressure = map(sensorValue, 0, 1023, -2000, 2000);  
   
  // convert reading to a positive value  
  if (diffPressure < 0) {  
   diffPressure = diffPressure * -1;  
   
   //calculate volumetric flow rate for Inhalation  
   volumetricFlowInhale = tubeArea2 * (sqrt((2 / airDensity) * (diffPressure / (1 - sq(tubeArea2 / tubeArea1)))));  
   
   //calculate velocity of flow   
   velocityFlowInhale = volumetricFlowInhale / tubeArea2;  
  } else {  
   //calculate volumetric flow rate for Exhalation  
   volumetricFlowExhale = tubeArea1 * (sqrt((2 / airDensity) * (diffPressure / (sq(tubeArea1 / tubeArea2) - 1))));  
   
   //calculate velocity of flow   
   velocityFlowExhale = volumetricFlowExhale / tubeArea1;  
  }  
   
  // Print the results as comma separated values for easier processing  
  // in a spreadsheet program  
   
  Serial.print(sampleNumber);  
  Serial.print(",");  
  Serial.print(diffPressure);  
  Serial.print(",");  
  Serial.print(volumetricFlowExhale);  
  Serial.print(",");  
  Serial.print(volumetricFlowInhale);  
  Serial.print(",");  
  Serial.print(velocityFlowExhale);  
  Serial.print(",");  
  Serial.print(velocityFlowInhale);  
  Serial.print(",");  
  Serial.println();  
   
  // wait 100 milliseconds before the next loop  
  // for the analog-to-digital converter and  
  // pressure sensor to settle after the last reading:  
  delay(100);  
   
 }  

It is a little more complicated than the previous code but here is essentially how it works:
  • All the variables needed are declared, the variable names should explain what their purpose is.
  • All the constants we need are declared, again their names should explain their purpose.
  • The setup function is called next.  It initialises the serial monitor and declares the sensorInput variable as an input.  Next it writes some messages to the serial monitor to create a header for a comma separated value file - an easy way to copy and paste the data from the serial monitor into a text file so it can then be important into a spreadsheet program for graphing.  More on this later.
  • The loop function is called next.  The sample count is incremented by one every time the loop runs. This is for graphing purposes - we take a sample every 100 ms.  Next the differential pressure sensor is read and the results are converted to Pascals.  If the result is negative (someone is inhaling) then the value taken is converted to a positive number - if we don't do this the calculations will not be correct - it is not possible to take the square root of a negative number (Unless the quantity is complex - not relevant here!)
  • Next volumetric flow rate is calculated for both exhalation and inhalation after that the velocity of flow rates are calculated and then all these results are sent to the serial monitor.  Then the program loops back to the start.
When the code has been uploaded to the arduino the serial monitor should provide some data.  If the pneumotach tubes are correctly connected to the MXP7002 Differential pressure sensor then when one breathes in and out of the pneumotach tube - data should be received!


If one were to copy and paste this data into an ascii text editor and save that file as a CSV extension then the data can be imported into a Spreadsheet or graphing program.  Here are graphs of some of the data taken:


That's all for now.  The next post will deal with graphing the data live to a rolling display. - Take care people - Alex!

Monday 17 April 2017

Using the MPX7002DP Differential Pressure Sensor with an Arduino Uno

Recently I started to design an open source medical device for use by doctors and medical professionals to treat various respiratory disorders.  The post is here:

Multi-functional Medical Device

As part of that I bought a differential pressure sensor breakout board which uses the MPX7002DP differential pressure sensor made by NXP.  The datasheet for the sensor is here:

MPX7002DP Datasheet

The sensor itself can be bought from various online electronics vendors including:

Farnell Electronic Components - Part Number 2080499

RS Components - Part Number 719-1197

The pressure sensor breakout board itself looks like this:


Connecting this to a microcontroller development board like the arduino uno is very simple.  The underside of the PCB contains silk screen which gives the information:


The only thing I don't particularly like about this breakout board is that the wires are not colour coded.  It makes it awkward to wire the breakout board up!  If there are different signals on the wires then use different colours!

Here is the Connection Diagram for those who prefer pictures:
For people who prefer text:

GND connects to GND on the Arduino Uno
+5V connects to +5V on the Arduino Uno
Analog connects to A0 on the Arduino (Or any analogue pin as required)

Here is a picture of my setup - Ignore the Liquid Crystal Display:


Once the connections have been made it's time to write the code which will read the pressure sensor into the arduino.  Reading the datasheet for the sensor gives the designer the information needed to use this sensor:

Lets discuss what the operating characteristics tell us:

  • The pressure range is from -2 kPa (Vacuum) to +2 kPa (Positive Pressure).  So we need to convert the Raw ADC output to account for that.  1023 bits divided by 4000 Pa is the conversion factor assuming the sensor is accurate and no other noise creeps in.
  • The sensitivity is 1 Volt / pA - so we can check our measurement data if needed.
  • The response time is 20 ms - that means there needs to be a 20 ms delay between each sensor read.

The code has to do several things and to make things easier lets draw a quick flow diagram:
From the diagram it should be pretty simple to write up some code to get this working - The code I wrote is below:

 // MPX7002DP Test Code  
 // A.Lang - 2017  
 // This code exercises the MPX7002DP  
 // Pressure sensor connected to A0  
 int sensorPin = A0;  // select the input pin for the Pressure Sensor  
 int sensorValue = 0;  // variable to store the Raw Data value coming from the sensor  
 float outputValue = 0; // variable to store converted kPa value   
 void setup() {  
  // start serial port at 9600 bps and wait for port to open:  
  Serial.begin(9600);  
  while (!Serial) {  
   ; // wait for serial port to connect. Needed for native USB port only  
  }  
  pinMode(sensorPin, INPUT);  // Pressure sensor is on Analogue pin 0  
 }  
 void loop() {  
  // read the value from the sensor:  
  sensorValue = analogRead(sensorPin);  
  // map the Raw data to kPa  
  outputValue = map(sensorValue, 0, 1023, -2000, 2000);  
  // print the results to the serial monitor:  
  Serial.print("sensor = " );  
  Serial.print(sensorValue);  
  Serial.print("\toutput = ");  
  Serial.println(outputValue);  
  // wait 100 milliseconds before the next loop  
  // for the analog-to-digital converter and  
  // pressure sensor to settle after the last reading:  
  delay(100);  
 }  

It should be fairly self-explanatory.

  • The sensor output will be connected to analogue pin A0.  The actual data will be stored as an integer value in a variable called sensorPin.
  • The raw converted analogue data will be stored in an integer variable called sensorValue.
  • The converted output data in kPa will be stored in a float variable called outputData.
  • In the Setup Function the serial communications are initialised and the sensorPin variable is declared as an input.
  • In loop function the sensor data is read in from the analogue pin and mapped to a kPa value.
  • The data is then sent to the serial terminal so it can be reviewed.
  • A delay of one hundred milli-seconds is introduced to allow the system to settle.
  • Then the whole process repeats for ever!

Here is a video of the circuit in action.


The output of the sensor was actually very sensitive, more so than I anticipated and will work well for the application.  The next thing to do is to output the serial data directly to an external program so that it can be graphed which provides a much more visual and useful display.  After that the serial communications can be converted to bluetooth and then the data can be sent to a mobile phone or again to a PC but the system can be made battery powered and therefore truly wire free!

Take care people - Langster!

Wednesday 12 April 2017

Driving the VGA Port on the Mimas V2

A blog reader....in fact several blog readers asked me to write up how to drive the VGA port on the Numato Labs Mimas V2 FPGA development board.

There is some example code available from Numato Labs which shows the FPGA is working and places a message on the screen.  That code is available from their github repository here:

Numato Labs VGA Sample Code

In order to get something working as quickly as possible I forked that code. The easiest thing to do is to load up Xilinx WebISE and start a new project for the Mimas V2 with the correct parameters.  I have gone through this process in other blog posts before so I'm not going to repeat myself.  Give the project a suitable name - I called mine Mimas2_VGA but anything sensible will do.

Next add a new source file - a VHDL Module and give it a suitable name I called mine VGA_display - don't worry about providing any inputs or outputs.  This will be added when the code is written.

Next add another new source file, again a VHDL Module and call this file VGA_Synch - No inputs or outputs are needed as the code will be supplied as required.

Add another source file - a VHDL Module and call it clocking_Instant - don't add any inputs or outputs as the code will be supplied.

Add another source file - a VHDL Module - call it VGA_top_module - don't add any inputs or outputs as the code will be supplied.

Add yet another source file - An Implementation Constraints file - call it something sensible - I called mine VGA_sync.ucf

The hierachy window should look something like this:


Next ensure the VGA_top_module file is open in the text editor in Xilinx WebISE and copy and paste the code below into the text editor:

--------------------------------------------------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity VGA_top_module is

GENERIC (          
BoardDescription   : STRING   := "NUMATO LAB Mimas V2";
DeviceDescripition : STRING   := "SPARTAN6 LX9";
ClockFrequency     : INTEGER  := 100_000_000;
VGAResolution      : STRING   := "640x480 @ 60Hz";
NumberOfVGAColor   : INTEGER  := 3                 
);

PORT (
-- Inputs
-- Mimas V2 has a 100MHz input clock and an active Low reset.
CLK : IN STD_LOGIC;
RST_n : IN STD_LOGIC;

-- Outputs
   -- VGA Display
   HSync : OUT   STD_LOGIC;
   VSync : OUT   STD_LOGIC;
   Red   : OUT   STD_LOGIC_VECTOR(NumberOfVGAColor-1 downto 0);
   Green : OUT   STD_LOGIC_VECTOR(NumberOfVGAColor-1 downto 0);
   Blue  : OUT   STD_LOGIC_VECTOR(NumberOfVGAColor-1 downto 1)
);

end VGA_top_module;

architecture Behavioral of VGA_top_module is

component clocking

port (  
--Input clock 100 MHz
CLK_IN : in std_logic;

--Output
CLK_100MHz : out std_logic;
CLK_50MHz  : out std_logic);

end component;

component VGA_Synch

generic ( 
VGAResolution    : STRING ;
NumberOfVGAColor : INTEGER);
   
port (  
-- Input clock 100 MHz
CLK   : in std_logic;
RST_n : in std_logic;

-- Output
HSync : out std_logic;
VSync : out std_logic;
Red   : out std_logic_vector(NumberOfVGAColor-1 downto 0);
Green : out std_logic_vector(NumberOfVGAColor-1 downto 0);
Blue  : out std_logic_vector(NumberOfVGAColor-1 downto 1));

end component;

signal  CLK_100MHz : std_logic := '0';
signal  CLK_50MHz  : std_logic := '0';

begin

clocking_Instant : clocking

port map (
CLK_IN     => CLK,
CLK_100MHz => CLK_100MHz,
CLK_50MHz  => CLK_50MHz);
 
VGA_instant : VGA_Synch

generic map(
VGAResolution => VGAResolution,
NumberOfVGAColor => NumberOfVGAColor)

port map   (
CLK   => CLK_50MHz,
RST_n => RST_n,
hsync => hsync,
vsync => vsync,
Red   => Red,
Green => Green,
Blue  => Blue);

end Behavioral;

--------------------------------------------------------------------------------------------------------------------------

Save the file -  Always a good idea!!!  I wasn't going to explain the code function line by line - Basically it defines the FPGA development board, sets the value for the source clock to 100 MHz, sets the video output resolution to be 640 by 480 with a refresh rate of 60 Hz and the number of colours available to be three (Red, Green and Blue).

Next the inputs and outputs are set.  The inputs are the 100 MHz clock and the reset pin and the output is the VGA connector.

Then the code calls for some components:

- clocking (Call some external code clock generator code which takes the 100 MHz source clock and creates a 100 MHz clock and a 50 MHz clock)

- VGA_Synch (Call some external code which drives the VGA port on the FPGA development board)

Next some internal signals are created for the two new clock signals

Next the components are instantiated and the signals are mapped between the files.

I hope that was clear!!!

Next open the file clocking.vhd into the text editor in Xilinx WebISE and copy and paste the code below into it:
-----------------------------------------------------------------
-- file: clocking.vhd

-- 
-- (c) Copyright 2008 - 2011 Xilinx, Inc. All rights reserved.
-- 
-- This file contains confidential and proprietary information
-- of Xilinx, Inc. and is protected under U.S. and
-- international copyright and other intellectual property
-- laws.
-- 
-- DISCLAIMER
-- This disclaimer is not a license and does not grant any
-- rights to the materials distributed herewith. Except as
-- otherwise provided in a valid license issued to you by
-- Xilinx, and to the maximum extent permitted by applicable
-- law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND
-- WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES
-- AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
-- BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-
-- INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and
-- (2) Xilinx shall not be liable (whether in contract or tort,
-- including negligence, or under any other theory of
-- liability) for any loss or damage of any kind or nature
-- related to, arising under or in connection with these
-- materials, including for any direct, or any indirect,
-- special, incidental, or consequential loss or damage
-- (including loss of data, profits, goodwill, or any type of
-- loss or damage suffered as a result of any action brought
-- by a third party) even if such damage or loss was
-- reasonably foreseeable or Xilinx had been advised of the
-- possibility of the same.
-- 
-- CRITICAL APPLICATIONS
-- Xilinx products are not designed or intended to be fail-
-- safe, or for use in any application requiring fail-safe
-- performance, such as life-support or safety devices or
-- systems, Class III medical devices, nuclear facilities,
-- applications related to the deployment of airbags, or any
-- other applications that could lead to death, personal
-- injury, or severe property or environmental damage
-- (individually and collectively, "Critical
-- Applications"). Customer assumes the sole risk and
-- liability of any use of Xilinx products in Critical
-- Applications, subject only to applicable laws and
-- regulations governing limitations on product liability.
-- 
-- THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS
-- PART OF THIS FILE AT ALL TIMES.
-- 
------------------------------------------------------------------------------
-- User entered comments
------------------------------------------------------------------------------
-- None
--
------------------------------------------------------------------------------
-- "Output    Output      Phase     Duty      Pk-to-Pk        Phase"
-- "Clock    Freq (MHz) (degrees) Cycle (%) Jitter (ps)  Error (ps)"
------------------------------------------------------------------------------
-- CLK_OUT1___100.000______0.000______50.0______200.000____150.000
-- CLK_OUT2____50.000______0.000______50.0______300.000____150.000
--
------------------------------------------------------------------------------
-- "Input Clock   Freq (MHz)    Input Jitter (UI)"
------------------------------------------------------------------------------
-- __primary_________100.000____________0.010

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
use ieee.numeric_std.all;

library unisim;
use unisim.vcomponents.all;

entity clocking is
port
 (-- Clock in ports
  CLK_IN           : in     std_logic;
  -- Clock out ports
  CLK_100MHz          : out    std_logic;
  CLK_50MHz          : out    std_logic
 );
end clocking;

architecture xilinx of clocking is
  attribute CORE_GENERATION_INFO : string;
  attribute CORE_GENERATION_INFO of xilinx : architecture is "clocking,clk_wiz_v3_6,{component_name=clocking,use_phase_alignment=true,use_min_o_jitter=false,use_max_i_jitter=false,use_dyn_phase_shift=false,use_inclk_switchover=false,use_dyn_reconfig=false,feedback_source=FDBK_AUTO,primtype_sel=DCM_SP,num_out_clk=2,clkin1_period=10.0,clkin2_period=10.0,use_power_down=false,use_reset=false,use_locked=false,use_inclk_stopped=false,use_status=false,use_freeze=false,use_clk_valid=false,feedback_type=SINGLE,clock_mgr_type=AUTO,manual_override=false}";
 -- Input clock buffering / unused connectors
  signal clkin1            : std_logic;
  -- Output clock buffering
  signal clk_out1_internal : std_logic;
  signal clkfb             : std_logic;
  signal clk0              : std_logic;
  signal clkdv             : std_logic;
  signal clkfbout          : std_logic;
  signal locked_internal   : std_logic;
  signal status_internal   : std_logic_vector(7 downto 0);
begin


  -- Input buffering
  --------------------------------------
  clkin1_buf : IBUFG
  port map
   (O => clkin1,
    I => CLK_IN);


  -- Clocking primitive
  --------------------------------------
  
  -- Instantiation of the DCM primitive
  --    * Unused inputs are tied off
  --    * Unused outputs are labeled unused
  dcm_sp_inst: DCM_SP
  generic map
   (CLKDV_DIVIDE          => 2.000,
    CLKFX_DIVIDE          => 1,
    CLKFX_MULTIPLY        => 4,
    CLKIN_DIVIDE_BY_2     => FALSE,
    CLKIN_PERIOD          => 10.0,
    CLKOUT_PHASE_SHIFT    => "NONE",
    CLK_FEEDBACK          => "1X",
    DESKEW_ADJUST         => "SYSTEM_SYNCHRONOUS",
    PHASE_SHIFT           => 0,
    STARTUP_WAIT          => FALSE)
  port map
   -- Input clock
   (CLKIN                 => clkin1,
    CLKFB                 => clkfb,
    -- Output clocks
    CLK0                  => clk0,
    CLK90                 => open,
    CLK180                => open,
    CLK270                => open,
    CLK2X                 => open,
    CLK2X180              => open,
    CLKFX                 => open,
    CLKFX180              => open,
    CLKDV                 => clkdv,
   -- Ports for dynamic phase shift
    PSCLK                 => '0',
    PSEN                  => '0',
    PSINCDEC              => '0',
    PSDONE                => open,
   -- Other control and status signals
    LOCKED                => locked_internal,
    STATUS                => status_internal,
    RST                   => '0',
   -- Unused pin, tie low
    DSSEN                 => '0');




  -- Output buffering
  -------------------------------------
  clkfb <= clk_out1_internal;


  clkout1_buf : BUFG
  port map
   (O   => clk_out1_internal,
    I   => clk0);


  CLK_100MHz <= clk_out1_internal;

  clkout2_buf : BUFG
  port map
   (O   => CLK_50MHz,
    I   => clkdv);

end xilinx;

--------------------------------------------------------------------------------------------------------------------------

Please don't ask me what this does as I don't know exactly!  I believe it takes in the 100 MHz external clock and creates two new clock signals - a 100 MHz clock and a 50 MHz clock.  It looks like the output from the DCM_clocking generator that was available for the Spartan 3A device used on the Elbert V2 board.  The same IP code generator is not available for the Spartan 6 which is why I suspect Numato have just provided code as is...

Now open the file VGA_Synch.vhd into the text editor and copy and paste in the code below:

--------------------------------------------------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
library work;

entity VGA_Synch is
    generic (   VGAResolution     : STRING  := "640x480 @ 60Hz";
                NumberOfVGAColor  : INTEGER := 3);

    Port ( CLK : in  STD_LOGIC;
           RST_n : in  STD_LOGIC;
           HSync : out  STD_LOGIC;
           VSync : out  STD_LOGIC;
           Red : out  STD_LOGIC_VECTOR   (NumberOfVGAColor-1 downto 0);
           Green : out  STD_LOGIC_VECTOR (NumberOfVGAColor-1 downto 0);
           Blue : out  STD_LOGIC_VECTOR  (NumberOfVGAColor-1 downto 1));
end VGA_Synch;

architecture Behavioral of VGA_Synch is

    -- Intermediate register used internally
        signal rgb : std_logic_vector(7 downto 0);         
    -- Set the resolution of screen
        signal hCount : integer range 0 to 1023 := 640;
        signal vCount : integer range 0 to 1023 := 480;
    -- Set the count from where it should start
        signal nextHCount : integer range 0 to 1023 := 641;
        signal nextVCount : integer range 0 to 1023 := 480;    
begin
-- Instantiate the file which contain the things to be displayed.
-- The clock which should be given here is 50MHz,so that the data is properly
-- obtained has the output is latched to VGA pins at 25MHz.      

  
output : entity work.VGA_display
           port map(clock     => CLK,
                    hcounter  => nextHCount,
                    vcounter  => nextVCount,
                    pixels    => rgb);        
        
-- The process is carried out for every positive edge of the clock i.e, 50 MHz
-- clock(clock).


  vgasignal: process(CLK,RST_n)

              variable divide_by_2 : std_logic := '0';       
              begin    
                if (RST_n = '0') then
    hCount <= 640;
    vCount <= 480;
                elsif rising_edge(CLK) then
                  -- Scale down the 50 MHz to 25 MHz so that the signal is
                  -- latched properly.

                 
                if divide_by_2 = '1' then

                -- Maximum Horizontal count is limited to 799 for 640 x 480
                -- display so that it fit's the screen.                        

               
                if(hCount = 799) then

                    hCount <= 0;
                -- Maximum Vertical count is limited to 524 for  640 x 480
                -- display so that it fit's the screen.                     

               
               if(vCount = 524) then

                  vCount <= 0;
               else
                  vCount <= vCount + 1;
               end if;
             
               else

                  hCount <= hCount + 1;
               end if; 

               -- Make sure we got the roll over covered
               if (nextHCount = 799) then        
                   nextHCount <= 0;
             
                -- Make sure we got the roll over covered

               if (nextVCount = 524) then        
                   nextVCount <= 0;
               else
                   nextVCount <= vCount + 1;
               end if;
             
               else

                   nextHCount <= hCount + 1;
               end if;
                            
               -- Check if the count is within the minimum and maximum value for
               -- proper generation of the vertical sync signal                             

               if (vCount >= 490 and vCount < 492) then
                   VSync <= '0';
               else
                   VSync <= '1';
               end if;
                                    
               -- Check if the count is within the minimum and maximum value for
               -- proper generation of the horizontal sync signal                                   

               if (hCount >= 656 and hCount < 752) then
                   HSync <= '0';
               else
                   HSync <= '1';
               end if;
                                     
               -- If in display range then display the pixel.
                  if (hCount < 640 and vCount < 480) then
                     Red <= rgb (7 downto 5);
                     Green <= rgb (4 downto 2);
                     Blue <= rgb (1 downto 0);  
                   end if;
           
               end if;

          
               divide_by_2 := not divide_by_2;
         
           end if;

       
    end process;                 


end Behavioral;

--------------------------------------------------------------------------------------------------------------------------

The first lines above the entity statment ensure the required library files are included, then the entity statement is written. In VHDL an entity statement define the pins that interact with the actual FPGA device.  In this case we are defining pins which control the clock and the VGA control pins.

The next lines of code (in the architecture statement) are simply internal variables used to help keep track of where the clock and the pixels are. Clock is the Clk signal which we divide down. rgb is a register to store which colour the pixel will be. hCount is an integer variable which counts how many pixels been drawn in a scanline. vCount is an integer variable which counts how many scanlines have been drawn. The last two variables nextHCount and nextVCount are present to ensure that once the limits of screen have been reached that the counters are reset.

Next comes the architecture begin statement. The next lines of code basically tell the system that there will be another text file which contains the code for actually placing what the designer would like on the display. The parameters which will be passed to the new file which will be called VGA_display are clock, nextHCount and nextVCount and finally rgb.

Immediately below that is  a process (kind of like a function) which divides the 100 MHz clock to
50 MHz. It does this by inverting the Clk signal when that signal is positive. It then copies the state of the Clk to the clock variable which halves the frequency of the clock.

The next lines of code check if an entire scanline has been drawn and resets the hCount to zero if this is the case. The same check is performed on the vCount to see if an entire frame has been drawn. If it has then the vCount is reset. If neither hCount or vCount has reached it's limit they are incremented and the process continues. 

The next lines of code check if the hCount variable has reached the limit for the display resolution.
If it has the count is restarted. The same check is performed on the vCount and if so then the vCount
is restarted.

Finally the next lines of code invert the clock divider and then close off the open if statements and end the process.

Lets open the file VGA_display.vhd and copy and paste the code below into it:

---------------------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL; 
use IEEE.NUMERIC_STD.ALL; 

entity VGA_display is 

-- Define the width and the height of the displayed text.
generic( 
OutputWidth  : integer := 10;   
OutputHeight : integer := 40   
);

port ( 
  -- Assuming 50MHz clock. 
  clock: in std_logic; 
  
  -- The counter tells whether the correct position on the screen is reached  
  hcounter: in integer range 0 to 1023; 
  vcounter: in integer range 0 to 1023; 
  
  -- Output the colour that should appear on the screen. 
  pixels : out std_logic_vector(7 downto 0) ); 
  
 end VGA_display; 

architecture Behavioral of VGA_display is 

-- Intermediate register telling the exact position on display on screen. 
signal x : integer range 0 to 1023 := 100; 
signal y : integer range 0 to 1023 := 80; 
    
begin 
    
 -- On every positive edge of the clock counter condition is checked,
output1: process(clock)

begin

   if rising_edge (clock) then
 
   --change the if statements below to draw shapes
   --change the pixels <= FF to change colours
   -- FF = White
   -- 00 = Black
   -- F0 = Orange 

   -- Draw a single pixel in the
   -- centre of the screen

   if (hcounter = 320) and (vcounter = 240) 
      then pixels <= x"FF";        else
      pixels <= x"00";
  end if;

   end if;

 end process;

end Behavioral;  


--------------------------------------------------------------------------------------------------------------------------

Save the file....in fact save everything!  The code above is the VHDL code which actually defines what will be displayed on the screen.  It connects its outputs to the inputs in the other files and selects which parts of the display with have colours present and which parts will be black.

Next open the file VGA_Sync.ucf into the text editor in WebISE and copy and paste the code below:

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#
# This file is a .ucf for Mimas V2                                                                    #
# To use it in your project :                                                                         #
# * Remove or comment the lines corresponding to unused pins in the project                           #
# * Rename the used signals according to the your project                                             #
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#

CONFIG VCCAUX = "3.3" ;

   NET "CLK"      LOC = V10 | IOSTANDARD = LVCMOS33 | PERIOD = 100MHz;

   NET "RST_n"       IOSTANDARD = LVCMOS33 | PULLUP;


#################################################################################
#                                                    VGA                        #
#################################################################################
    NET "HSync"               LOC = B12  | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
    NET "VSync"               LOC = A12  | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;

    NET "Red[2]"              LOC = C9   | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
    NET "Red[1]"              LOC = B9   | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
    NET "Red[0]"              LOC = A9   | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;

    NET "Green[2]"            LOC = C11  | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
    NET "Green[1]"            LOC = A10  | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
    NET "Green[0]"            LOC = C10  | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;

    NET "Blue[2]"             LOC = A11  | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;
    NET "Blue[1]"             LOC = B11  | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST ;

--------------------------------------------------------------------------------------------------------------------------

Here is what the screen should look like if everything has gone according to plan:


Go back to the file VGA_display.vhd and load that into the text editor.

Shapes are drawn on the screen by changing or expanding the following If  statement:

if (hcounter = 320) and (vcounter = 240) 
      then pixels <= x"FF";

At the moment a single white dot will be displayed in the centre of the monitor.  If a white square in the centre of the monitor was required then the following statement might be suitable:

if (hcounter >= 310) and (hcounter <= 330) and (vcounter >= 230) and (vcounter <= 250) then pixels <= x"FF";

This will create a twenty by twenty pixel white square in the centre of the display. The rest of the display will be black.  The colour could be changed by changing the value for pixels from FF to something else.

By writing some clever code or loops it should be more than possible to create complex shapes and frames as needed.

Save everything one more time just to be sure all is well and then click on the green arrow to implement the top module - wait for the processes to complete...some time later!

Then create a bit file and upload it to the Mimas V2:


If all went according to plan you should see something like the image below:


If anyone needs the project code it is available here:


That's all for now - take care people!