Monday 24 November 2014

Elbert V2 VHDL Tutorial

It's time for another FPGA tutorial using the Elbert V2 board from Numato Systems!

I found a really good FPGA online course here...so rather than re-invent the wheel I'm going to apply it to the Elbert V2 board.

Hamsterworks FPGA Tutorials

You can download a full PDF of the course from that site if you wish...

I'm going to skip with the module 1 as I have already installed the Xilinx design suite and I'm ready to go.  The tutorials are written using VHDL - which I haven't up to now been using however it is similar to verilog so we should be able to manage:

So lets begin at module 2:

I'm referencing this page here if people want to look at the original source material - Kudos to Hamsterworks:

http://hamsterworks.co.nz/mediawiki/index.php/Module_2

The idea of this module is to wire up two switches to two LEDS and have them controlled by an FPGA - in our case the Elbert V2 Development board from Numato Labs.  I know we have already done this before but this time we are going to write the source code in VHDL and not in verilog. Hamsterworks' tutorials are very good but are written in VHDL only...so lets get started!!

The first thing to do is to start a new project in Xilinx ISE 14.7 - make sure you choose all of the correct settings as shown in the screenshot below:




Choose a suitable project name and directory etc and click Next when you are ready to continue...


Ensure you also apply all the settings in the screenshot above - especially select VHDL as that is the description language we are going to use...Click Next when you are ready to continue and the summary screen will be displayed:


Click the Finish button and the main Xilinx project screen will be displayed:


Right click in the project area and select add new source file:


Now choose to add a VHDL module - I called mine Switch_LEDS:


Click next to continue:

Now fill in the information for the inputs and outputs.  I chose to follow the example by Hamsterworks but there is no reason why we couldn't have 8 switch inputs and 8 LED outputs.


Click Next to Continue and the source code summary page will be displayed:


Click finish to continue and return to the main screen which now looks like the screenshot below:


What we now need to do is edit the source code to our own satisfaction.  There are several comments present which can be removed if required - it is entirely at the coder's discretion.  We also need to make the code perform the function we want.  In our case this means we want the LEDS to mirror the state the switches are in.  I changed mine to the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity Switch_LEDS is
    Port ( Switch_One : in  STD_LOGIC;
           Switch_Two : in  STD_LOGIC;
           LED_One : out  STD_LOGIC;
           LED_Two : out  STD_LOGIC);
end Switch_LEDS;

architecture Behavioral of Switch_LEDS is

begin

 LED_One <= Switch_One;
 LED_Two <= Switch_Two;

end Behavioral;

So what does this code mean, how does it work etc...

The first line tells the compiler to use the standard library IEEE.  The second line tells the compiler to use the IEEE standard representation of a logic value....

The fourth line entity Switch_LEDS is a bit like a setup function in C or C++.  It tells the compiler that our design has inputs and outputs (ports) and that there are two inputs known as Switch_One and Switch_Two and also two Outputs known as LED_One and LED_Two.

The 'behaviour' of the function is decided on lines 15 and 16.  The '<=' operators in this case mean that the LED_One mirrors the value of Switch_One and the same for LED_Two and Switch_Two

When you are ready save the source code file and click on the 'Green Arrow' on the left side of the screen - Implement top Module:


If the implementation all went according to plan you should be presented with the following in the bottom left corner: 
  
Now we need to tell the compiler where on the Elbert V2 board the switches and LEDS are and which pins they are connected to on the FPGA.  To that we need to implement a User Constraints file.  Right click in the top right corner of the main project screen and again add a new source file:


This time select Implementation constraints file and give it a suitable file name:


Click Next when you are ready to continue:


Click Finish to continue.  We now need to edit the constraints file with the information relating to the Elbert V2 Board.  To do that we can look at the file already provided by Numato Labs and / or look at the schematic diagram which shows how the switches and LEDS are connected to the FPGA:

CONFIG VCCAUX = "3.3" ;

NET "MCLK" LOC = P129;
TIMESPEC TS_CLK = PERIOD "CLK" 12 MHz HIGH 50%;


############################################################################
# VGA
############################################################################

    #NET "HSync"              LOC = P93  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "VSync"              LOC = P92 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

    #NET "Blue[1]"            LOC = P98  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "Blue[0]"            LOC = P96  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

    #NET "Green[2]"           LOC = P102  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "Green[1]"           LOC = P101  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "Green[0]"           LOC = P99  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

    #NET "Red[2]"             LOC = P105  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "Red[1]"             LOC = P104  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "Red[0]"             LOC = P103 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

############################################################################
# Micro SD Card
############################################################################

    #NET "CLK"                LOC = P57  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "DAT0"               LOC = P83  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "DAT1"               LOC = P82  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "DAT2"               LOC = P90  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "DAT3"               LOC = P85  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "CMD"                LOC = P84  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

############################################################################
# Audio
############################################################################

    #NET "AUDIO_L"            LOC = P88  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "AUDIO_R"            LOC = P87  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ; 

############################################################################
# Seven Segment Display
############################################################################

    #NET "SevenSegment[7]"    LOC = P114  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "SevenSegment[6]"    LOC = P115  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "SevenSegment[5]"    LOC = P116  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "SevenSegment[4]"    LOC = P117  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "SevenSegment[3]"    LOC = P110  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "SevenSegment[2]"    LOC = P111  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "SevenSegment[1]"    LOC = P112  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "SevenSegment[0]"    LOC = P113 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

    #NET "Enable[3]"          LOC = P124 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "Enable[2]"          LOC = P121 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "Enable[1]"          LOC = P120  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

############################################################################
# LED
############################################################################

    #NET "LED[8]"            LOC = P46  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[7]"            LOC = P47  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[6]"            LOC = P48  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[5]"            LOC = P49  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[4]"            LOC = P50  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[3]"            LOC = P51  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[2]"            LOC = P54  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[1]"            LOC = P55  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

############################################################################
# DP Switches
############################################################################

    #NET "DPSwitch[1]"       LOC = P70  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "DPSwitch[2]"       LOC = P69  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "DPSwitch[3]"       LOC = P68  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "DPSwitch[4]"       LOC = P64  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
 #NET "DPSwitch[5]"       LOC = P63  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "DPSwitch[6]"       LOC = P60  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "DPSwitch[7]"       LOC = P59  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "DPSwitch[8]"       LOC = P58  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

############################################################################
# Switches
############################################################################

    #NET "Switch[1]"         LOC = P80  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[2]"         LOC = P79  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[3]"         LOC = P78  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[4]"         LOC = P77 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[5]"         LOC = P76 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[6]"         LOC = P75  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

############################################################################
# GPIO
############################################################################
 
# HEADER P1

    #NET "gpio_P1[0]"        LOC = P31  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P1[1]"        LOC = P32  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P1[2]"        LOC = P28  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P1[3]"        LOC = P30  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P1[4]"        LOC = P27 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P1[5]"        LOC = P29 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P1[6]"        LOC = P24 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P1[7]"        LOC = P25 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

# HEADER P6
   
 #NET "gpio_P6[8]"         LOC = P19  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P6[9]"         LOC = P21  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P6[10]"        LOC = P18  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P6[11]"        LOC = P20  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P6[12]"        LOC = P15  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P6[13]"        LOC = P16  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P6[14]"        LOC = P12 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P6[15]"        LOC = P13 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

# HEADER P2

    #NET "gpio_P2[16]"        LOC = P10  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P2[17]"        LOC = P11  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P2[18]"        LOC = P7  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P2[19]"        LOC = P8  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P2[20]"        LOC = P3  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P2[21]"        LOC = P5  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P2[22]"        LOC = P4  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P2[23]"        LOC = P6  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

# HEADER P4
    
 #NET "gpio_P4[24]"       LOC = P141  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P4[25]"       LOC = P143  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P4[26]"       LOC = P138  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P4[27]"       LOC = P139  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P4[28]"       LOC = P134  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P4[29]"       LOC = P135  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P4[30]"       LOC = P130 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P4[31]"       LOC = P132 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;


# HEADER P5
    
 #NET "gpio_P5[1]"       LOC = P125  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P5[2]"       LOC = P123  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P5[3]"       LOC = P127  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P5[4]"       LOC = P126  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P5[5]"       LOC = P131  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P5[6]"       LOC = P91  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P5[7]"       LOC = P142 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "gpio_P5[8]"       LOC = P140 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

All the above code relates to the Elbert V2 board and how the external components are connected to the FPGA device.  The schematic diagram on page two shows how the LEDS and switches are electrically connected:


So LED1 and LED2 are connected to pins 55 and 54 and the push button switch 1 and push button switch 2 are connected to pins 80 and 79.  If we read through the template user constraints file we can cut and paste the required code from the switches and LEDS sections:

############################################################################
# Switches
############################################################################

    #NET "Switch[1]"         LOC = P80  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[2]"         LOC = P79  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[3]"         LOC = P78  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[4]"         LOC = P77 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[5]"         LOC = P76 | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    #NET "Switch[6]"         LOC = P75  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

############################################################################
# LED
############################################################################

    #NET "LED[8]"            LOC = P46  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[7]"            LOC = P47  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[6]"            LOC = P48  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[5]"            LOC = P49  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[4]"            LOC = P50  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[3]"            LOC = P51  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[2]"            LOC = P54  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    #NET "LED[1]"            LOC = P55  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

We can use this information to generate our own implementation constraints file for this project which will look something similar to:

############################################################################
# Switches
############################################################################

    NET "Switch_One"         LOC = P80  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;
    NET "Switch_Two"         LOC = P79  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST | PULLUP ;

############################################################################
# LED
############################################################################

    NET "LED_Two"            LOC = P54  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
    NET "LED_One"            LOC = P55  | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;

Copy and paste the above code into the text editor window in Xilinx and save the file:


Now it is time to click on the green arrow again to re-compile the project.  If all went according to plan the following should be present in the bottom left hand corner of the screen:
 

Now is the time to generate the Bit stream file.  Right click on the Generate programming file option in the bottom left part of the screen, ensure Create Binary Configuration file is checked  and click 'run':


If that completes successfully load up the ElbertV2Config program and select the Communications port relating to your Elbert V2 PCB - make sure the board is connected to the PC via a cable and a free USB port - Mine was on COM12:


Then select the open file button and navigate to the folder containing the project we just completed. We need to select the Binary Configuration file and click on the program button:


 Once the programming is complete the reset the elbert board and push the buttons!  Pressing and holding buttons One and Two should cause the LEDS one and Two to turn off....


For fun reverse this behaviour so that the LEDS are off and pushing and holding the buttons causes then LEDS to illuminate:

It involves changing two of the lines of code with the NOT operator:

 LED_One <= NOT Switch_One;
 LED_Two <= NOT Switch_Two;

Thats all for now....Take care - Langster



Tuesday 18 November 2014

How to use a pressure sensor with a Microcontroller (MPS20N0040D-D)

A colleague of mine bought a pressure sensor from Amazon - they are all over the internet can be bought from Amazon, Hobby Components and Dealxtreme!  He wants to use it to measure water pressure in a tank.  In order to do that we need to know how to connect it up and how to obtain the electrical signal from the sensor and how to read this electrical signal with a micro controller so that we can display the result and act upon it...The sensor can be bought from the sites below:

MPS20N0040D-D-Pressure-Sensor (Amazon)

MPS20N0040D-d Pressure sensor (DealXtreme)

MPS20N0040D-D Pressure sensor (hobby Components)

The Sensor looks like this:

MPS20N0040D-D Pressure sensor
Here is a link to the datasheet for the sensor:

Pressure Sensor Datasheet

The datasheet isn't the best I have read but it does provide most of the information required.  The pressure sensor has a measurement range of 0-5 8 psi (40kpa).  The unit psi is an imperial measurement which stands for pounds per square inch.  The scale was designed for use in measuring the weight of goods (solids) however the scale can be applied to any pressure - gas or liquid.

Wikipedia Entry on the psi unit

The unit psi can be converted to an SI unit Pascals and the datasheet for the pressure sensor refers to 40 kpa as being the measurement range converted from psi to Pascals.

Wikpedia Entry on Pascals

1 Pascal (pa) = 1 kg / (metre * second)

Using mathematical formulae to define scales is technically correct but doesn't really give a real world example of what 1 pa actually feels like.  So a real world example of the pressure exuded by one pa is the weight of a £5 note (or a dollar bill) on a table is roughly equivalent to 1 pa. Popcorn kernels popping exudes roughly 2000 pa.  There are some more real world examples provided in the link below:

magnitudes of pressure

Converting psi to pascals is easy:

1 pound per square inch =
6 894.75729 pascals

So we have a sensor that is capable of measuring a range of pressures, can be driven by 5 Vdc and puts out a 0-25 mV signal.

That's enough theory for now...lets get on with using the sensor...The datasheet shows the device using a bridge connection that outputs a 0-25 mV signal.  That is a very small signal, if we were to connect the output directly to a micro-controller we wouldn't measure much unless the pressure was full scale and that output would be very low.  What needs to be done is to amplify the output of the pressure sensor in order to record the sensor signal properly.  That way we get more sensitivity and resolution - in short a better measurement device.

There are plenty of ways of amplifying electronic signals but in the case of instrumentation it is often necessary to amplify signals quite a lot of times in order to get a usable signal.  To that end we are going to design a difference amplifier. This is an application of operational amplifiers set to provide gain but also only measure the difference between the signals applied to the inputs.

Hyper-physics difference amplifier page

All about circuits - differential amplifiers

The datasheet for the Pressure sensor shows how to connect the sensor although not particularly clearly as a Wheatstone bridge.  If more information about Wheatstone bridges is required check out the link below.  It was conceived by a British Scientist and engineer - Samuel Hunter Christie in 1833 and improved by Sir Charles Wheatstone who made it popular.

Wheatstone Bridge

These measurement circuits are one of the foundations of analogue electronics and instrumentation. You can make almost any kind of sensor measurement using a Wheatstone bridge.  Here is the internal circuit diagram for the pressure sensor:

So which pins connect to what?

  • - Output (1) connects to - In on the Operational Amplifier
  • + Input (2) connects to +5 Vdc
  • + Output (3) connects to + In on the Operational Amplifier
  •    Output (4) does not connect to anything!
  • - Input (5) connects to 0 Vdc
  • - Output (6) connects to - In on the Operational Amplifier

Next we need to calculate the gain required.  We need to change 0 V - 25 mV into something larger and we also need to account for the voltage offset present (around 2 Vdc).  So first of all lets design a differential amplifier.

How to design a difference amplifier

Rather than reinvent the wheel and go through all of the theory again I used an online calculator to generate values for me.  It's a lot quicker and easier than pages of mathematical calculations.

Online Difference Amplifier Calculator

I have made several assumptions about the circuit....that the output from the sensor will be somewhere between 0.0 Vdc and 0.0025 Vdc.   I set the supply voltage to the op-amp as +5 Vdc and 0 Vdc (single supply mode).  The amplifier then gives out between 57 mV and 970 mV.  Those values are quite small so we will need to amplify that further in order to give a reasonable output into the micro-controller ADC input.  We are looking for something between 0 Vdc and 5 Vdc.

Here is the first part of the circuit.  I've drawn the sensor as resistors in the 'Wheatstone bridge' configuration.  To check the sensor was working I measured the resistance between each sensor pin with an ohm meter and found there to be 5 k-Ohms present in each part of the sensor circuit.

Lets explain the circuit....
The blue square is a rough guess at how the sensor works...It may not be entirely accurate but I don't have any more information to work from.  The amount the Wheatstone bridge varies is again a guess at 1 k-Ohms - I'm hoping it works this well!!

The green square is a simple filter to prevent external electrical noise (interference) from affecting the measurement.  We only want to measure signals from pressure sensor and nothing else.

The red square is the section designed with the calculator.  It's a standard difference amplifier with a feedback capacitor and some supply de-coupling capacitors again to prevent external interference affecting the circuit.  The gain of the amplifier is 5.6.

The output of the amplifier is still a little low to drive the ADC so lets add a non inverting amplifier to the output section so that we then get a times 3 gain and therefore a 200 mV to 3.5 Vdc swing.

Here is the full analogue input stage:

I simulated the circuit just to make sure it worked.  It appears to and here is the video of the circuit for those that are interested.


We can now design the full circuit and create an arduino shield.  I have added RS485 communications as that was one of the requirements of the circuit.  I haven't discussed RS485 before but it is a fairly common serial communications protocol.  Here is the full schematic diagram:




I have also designed an Eagle Shield for it but I have actually etched this yet....


Here is the top player - note that the Instrumentation amplifier is an SOIC surface mount package which is mounted on the underside of the board.


The rest of the circuit shows the connections to the arduino and I also added a 16x2 LCD display and the communications section.

So before I do anything I always prototype a circuit and this time is no different.  I got all of the required components and attached them with wires to my breadboard and arduino.  I didn't bother with the RS485 Communications section.  That can come later, Here is how it looks:


I then wrote some very quick code to check it works:

/*
Pressure Sensor test Code
*/

// These constants won't change.  They're used to give names
// to the pins used:
const int analogInPin = A0;  // Analog input pin that the potentiometer is attached to

int sensorValue = 0;        // value read from the pressure sensor via the amplifier stage
float outputValue = 0;        // value output to the Serial port and LCD display

void setup() 
{
  // initialize serial communications at 9600 bps:
  Serial.begin(9600); 
}

void loop() 
{
  // read the analog in value:
  sensorValue = analogRead(analogInPin);            
  outputValue = map(sensorValue, 10, 1023, 0, 100); //The zero value of sensor is around 10
  
  // print the results to the serial monitor:
  Serial.print("sensor = " );                       
  Serial.print(sensorValue);      
  Serial.print("\toutput = ");      
  Serial.println(outputValue);   

  // wait 500 milliseconds before the next loop
  // for the analog-to-digital converter to settle
  // after the last reading:
  delay(100);                     
}


Once I had uploaded this to the arduino and opened a serial monitor I expected there to be a steady stream of values being output to the serial monitor - there was!  Excellent.  I then attached a small piece of tubing to the pressure sensor and blew down the tube (provided some pressure)....Nothing happened....I then checked all of my connections and swapped the LM358 OP-Amp for another one....just in case and nothing happened.  I then removed all of the connections and rebuilt the entire circuit and reconnected it to the arduino and repeated the test and nothing happened.  At this point I was beginning to think the sensor was faulty....I'll be honest - I then gave up and moved on to other things.

I later revisited the circuit and re-tested it once I had it connected up on the breadboard.  It now does work and displays both positive and negative pressure.  The sensor's resolution isn't great but it does work.  I don't have a calibrated pressure source so I can't prove the output is correct.  The connections are as drawn above.  Here is the diagram of the connections (It's probably easier to follow...)


Parts List:

1x MPS20N0040D-D Pressure Sensor

2x 10k Resistors (Brown, Black, Orange, Gold)
2x 56k Resistors (Green, Blue, Orange, Gold)
1x 1k Resistor (Brown, Black, Red, Gold)
1x 2.7k Resistor (Red, Violet, Red, Gold)

1x LM358 Op-Amp

2x 100nF Capacitors - (Optional)
2x 10uF Electrolytic Capacitors - (Optional)

1x Arduino R3

Connections wires...

The hardest part is working out the connections of the sensor...It's a 6 pin package with no discernible markings although there is a bite on one edge.  I'm not sure how I got this working but I just kept fiddling the connections until I was happy it worked.  The middle two pins are connected to 0 Vdc and 5 Vdc and the two outer pins to the right are connected to the 10k resistors and then the op-amp. I'm not sure why it works but it does...

Best of luck people...and my apologies for not getting this working correctly first time...

Update - as this post appears to be so popular I designed a breakout board - check it out!!

MPS20N0040D-D Pressure Sensor Breakout Board

If people are interested I am selling these breakout boards for £10.00 which is roughly $12.93 - Contact me if you are interested!

Here is the webstore where you can purchase a breakout board -

Lang Electronics Design - Web Store

I have also written up a post on how to calibrate the pressure sensor:

Calibrating the pressure sensor

Enjoy, Langster!