Microblaze 16×2 LCD Driver


Tutorial Overview

In this example, we will develop a driver for the 16×2 character LCD on the ML505/6/7 board. The LCD driver will be mostly a Microblaze design, as opposed to being an IP design. The physical interface to the LCD will be made through a GPIO peripheral. The signal timing requirements of the LCD will be achieved by using a Timer peripheral. The functions contained in the software application will control what is shown on the LCD. The main function of the software application will provide a simple example of how to clear the display, write a message on the LCD and to change the cursor location.

The design is built on the interface specifications contained in the LCD datasheet. The connections to the FPGA are found on page 12 of the ML50x schematics. It is recommended that you read those documents before following this design.

This tutorial contains screenshots to guide you through the entire implementation process. Click on the images to view a higher resolution.

Requirements

Before following this tutorial, you will need to do the following:

  • Buy an ML505/ML506/ML507 or XUPV5 board if you don’t already have one. Xilinx supplies the ML50x boards, but the best deal is the XUPV5 from Digilent. Click the Digilent link for more information.

 

Create the Basic Project

Follow these steps to create the basic project:

  1. Open XPS. From the dialog box, select “Base System Builder wizard” and OK.
  2. You will be asked to specify which folder to place the project. Click “Browse” and create a new folder for the project. Click “OK”.
  3. We are given the choice to create a new project or to create one using the template of another project. Tick “I would like to create a new design” and click “Next”.
  4. On the “Select Board” page, select “Xilinx” as the board vendor. Select “Virtex 5 ML505 Evaluation Platform” as the board name. Select “1″ as the board revision. Click “Next”.
  5. On the “Select Processor” page, we normally have a choice between using the PowerPC “hard” processor, or the Microblaze “soft” processor. Since the Virtex-5 does not contain any PowerPCs, we can only select Microblaze. Click “Next”.
  6. On the “Configure Microblaze” page, select the clock frequency to be 125MHz. For the BRAM local memory, select “64KB”. Select “No debug” and click “Next”.
  7. In selecting the Additional IO Interfaces, leave “RS232_Uart_1″ ticked and un-tick everything else.



  8. On the “Add Internal Peripherals” page, click “Next”.
  9. On the “Software Setup” page, select “RS232_Uart_1″ for both STDIN and STDOUT. Leave “Peripheral Test” ticked and un-tick “Memory Test”. Click “Next”.
  10. Click “Generate”.
  11. Click “Finish”.

Add the GPIO for the LCD

Now we will add a GPIO peripheral to allow the Microblaze to control the LCD.

  1. In the IP Catalog tab, open the “General Purpose IO” tree.
  2. Right click on “XPS General Purpose IO” and select “Add IP”.
  3. Connect the instance to the PLB bus by opening the “xps_gpio_0″ tree and selecting “mb_plb” for the “SPLB” bus connection.
  4. Double click on the “xps_gpio_0″ instance. We can modify the instance parameters using this dialog box. Type “7″ for the “GPIO Data Channel Width” and click OK.
  5. Select the “Ports” filter and open the “xps_gpio_0″ tree.
  6. The “GPIO_IO” net should display “No Connection”. Click on “No Connection” and type “LCD_IO” to give the net a name.
  7. Click the net again to bring down the drop-down menu and select “Make External”. The newly created net should now be in the “External Ports” tree. XPS automatically modifies the “system.mhs” file to include the new external port.
  8. Select the “Addresses” filter.
  9. Click the “xps_gpio_0″ size drop-down menu and select 64K. Then click “Generate Addresses”. XPS automatically reconfigures the memory map and gives the GPIO peripheral a base address and a high address. It also automatically modifies the “system.mhs” file to update the IP address details.

We have now created an instance of the GPIO peripheral in our design. Later, we will modify the constraints file to connect the GPIO peripheral IO port to the LCD.

Add the Timer Peripheral

We now add a timer peripheral to the project to allow the Microblaze to measure time delays. A delay function will be used to create the required signal timing for the LCD interface. Follow these steps to add the peripheral:

  1. In the IP Catalog tab, open the “DMA and Timer” tree.
  2. Right click on “XPS Timer/Counter” and select “Add IP”.
  3. Connect the instance to the PLB bus by opening the “xps_timer_0″ tree and selecting “mb_plb” for the “SPLB” bus connection.
  4. Select the “Addresses” filter.
  5. Click the “xps_timer_0″ size drop-down menu and select 64K. Then click “Generate Addresses”.

We have now created an instance of the Timer peripheral in our design.

Modify the Constraints (.UCF) File

The GPIO peripheral that we added to the design must be linked to the external pins on the FPGA that in turn connect to the LCD. The LCD interface consists of the 7 signals listed below. They are shown with pin description, pin name from the LCD datasheet, and the net name from the ML505 schematic. In brackets are the corresponding FPGA pins that they connect to on the ML505 board.

  • Data bit 7, DB7, LCD_FPGA_DB7 (T11)
  • Data bit 6, DB6, LCD_FPGA_DB6 (G6)
  • Data bit 5, DB5, LCD_FPGA_DB5 (G7)
  • Data bit 4, DB4, LCD_FPGA_DB4 (T9)
  • Enable, E, LCD_FPGA_E (AC9)
  • Read/Write, RW, LCD_FPGA_RW (AC10)
  • Register Select, RS, LCD_FPGA_RS (J17)

Follow these steps to add those pin locations to the constraints file:

  1. Click the Project tab, open the “Project files” tree and double click the UCF file to open it.
  2. Add the following lines of code to the end of the file:
#### Module LCD_IO constraints

# LCD_FPGA_DB4
Net LCD_IO_pin<6> LOC = T9;
Net LCD_IO_pin<6> IOSTANDARD=LVCMOS33;
Net LCD_IO_pin<6> PULLDOWN;
Net LCD_IO_pin<6> SLEW=SLOW;
Net LCD_IO_pin<6> DRIVE=2;
# LCD_FPGA_DB5
Net LCD_IO_pin<5> LOC = G7;
Net LCD_IO_pin<5> IOSTANDARD=LVCMOS33;
Net LCD_IO_pin<5> PULLDOWN;
Net LCD_IO_pin<5> SLEW=SLOW;
Net LCD_IO_pin<5> DRIVE=2;
# LCD_FPGA_DB6
Net LCD_IO_pin<4> LOC = G6;
Net LCD_IO_pin<4> IOSTANDARD=LVCMOS33;
Net LCD_IO_pin<4> PULLDOWN;
Net LCD_IO_pin<4> SLEW=SLOW;
Net LCD_IO_pin<4> DRIVE=2;
# LCD_FPGA_DB7
Net LCD_IO_pin<3> LOC = T11;
Net LCD_IO_pin<3> IOSTANDARD=LVCMOS33;
Net LCD_IO_pin<3> PULLDOWN;
Net LCD_IO_pin<3> SLEW=SLOW;
Net LCD_IO_pin<3> DRIVE=2;
# LCD_FPGA_RW
Net LCD_IO_pin<2> LOC = AC10;
Net LCD_IO_pin<2> IOSTANDARD=LVCMOS33;
Net LCD_IO_pin<2> PULLDOWN;
Net LCD_IO_pin<2> SLEW=SLOW;
Net LCD_IO_pin<2> DRIVE=2;
# LCD_FPGA_RS
Net LCD_IO_pin<1> LOC = J17;
Net LCD_IO_pin<1> IOSTANDARD=LVCMOS33;
Net LCD_IO_pin<1> PULLDOWN;
Net LCD_IO_pin<1> SLEW=SLOW;
Net LCD_IO_pin<1> DRIVE=2;
# LCD_FPGA_E
Net LCD_IO_pin<0> LOC = AC9;
Net LCD_IO_pin<0> IOSTANDARD=LVCMOS33;
Net LCD_IO_pin<0> PULLDOWN;
Net LCD_IO_pin<0> SLEW=SLOW;
Net LCD_IO_pin<0> DRIVE=2;

 

Modify the Software Application

The software application controls the LCD interface signals by writing to the GPIO peripheral. It contains several functions for performing the signal sequences for writing characters to the LCD, moving the cursor, etc. To achieve the required signal timing, the application contains a delay function that uses the Timer peripheral.

  1. From the “Applications” tab, open “Sources” within the “Project: TestApp_Peripheral” tree. Open the “TestApp_Peripheral.c” source file.
  2. Replace all the code in this file with the following source and save the file.
#include "xparameters.h"
#include "xbasic_types.h"
#include "xgpio.h"
#include "xstatus.h"
#include "xtmrctr.h"

// Masks to the pins on the GPIO port

#define LCD_DB4    0x01
#define LCD_DB5    0x02
#define LCD_DB6    0x04
#define LCD_DB7    0x08
#define LCD_RW     0x10
#define LCD_RS     0x20
#define LCD_E      0x40
#define LCD_TEST   0x80

// Global variables

XGpio GpioOutput;
XTmrCtr DelayTimer;

// Function prototypes

void delay_us(Xuint32 time);
void delay_ms(Xuint32 time);
void gpio_write(Xuint32 c);
Xuint32 gpio_read(void);
void lcd_clk(void);
void lcd_set_test(void);
void lcd_reset_test(void);
void lcd_set_rs(void);
void lcd_reset_rs(void);
void lcd_set_rw(void);
void lcd_reset_rw(void);
void lcd_write(Xuint32 c);
void lcd_clear(void);
void lcd_puts(const char * s);
void lcd_putch(Xuint32 c);
void lcd_goto(Xuint32 line,Xuint32 pos);
void lcd_init(void);

// Main function

int main (void)
{
  Xuint32 status;

  // Clear the screen
  xil_printf("%c[2J",27);
  xil_printf("16x2 LCD Driver by Virtex-5 Resource\r\n");
  xil_printf("http://www.fpgadeveloper.com\r\n");

  // Initialize the Timer
  status = XTmrCtr_Initialize(&DelayTimer,
                                XPAR_XPS_TIMER_0_DEVICE_ID);
  if (status != XST_SUCCESS){
    xil_printf("Timer failed to initialize\r\n");
    return XST_FAILURE;
  }
  XTmrCtr_SetOptions(&DelayTimer, 1, XTC_DOWN_COUNT_OPTION);

  // Initialize the GPIO driver for the LCD
  status = XGpio_Initialize(&GpioOutput,
                                 XPAR_XPS_GPIO_0_DEVICE_ID);
  if (status != XST_SUCCESS){
    xil_printf("GPIO failed to initialize\r\n");
    return XST_FAILURE;
  }
  // Set the direction for all signals to be outputs
  XGpio_SetDataDirection(&GpioOutput, 1, 0x00);

  // Initialize the LCD
  lcd_init();

  // Example write to the LCD
  lcd_puts("http://www.fpga");
  lcd_goto(1,2);
  lcd_puts("developer.com");

  while(1){
  }
}

// Delay function (microseconds)
void delay_us(Xuint32 time)
{
  XTmrCtr_SetResetValue(&DelayTimer, 1, time * 125);
  XTmrCtr_Start(&DelayTimer, 1);
  while(!(XTmrCtr_IsExpired(&DelayTimer, 1))){}
  XTmrCtr_Stop(&DelayTimer, 1);
}

// Delay function (milliseconds)
void delay_ms(Xuint32 time)
{
  XTmrCtr_SetResetValue(&DelayTimer, 1, time * 125000);
  XTmrCtr_Start(&DelayTimer, 1);
  while(!(XTmrCtr_IsExpired(&DelayTimer, 1))){}
  XTmrCtr_Stop(&DelayTimer, 1);
}

// Write to GPIO outputs
void gpio_write(Xuint32 c)
{
  // Write to the GP IOs
  XGpio_DiscreteWrite(&GpioOutput, 1, c & 0x0FF);
}

// Read the GPIO outputs
Xuint32 gpio_read()
{
  // Read from the GP IOs
  return(XGpio_DiscreteRead(&GpioOutput, 1));
}

// Clock the LCD (toggles E)
void lcd_clk()
{
  Xuint32 c;
  // Get existing outputs
  c = gpio_read();
  delay_us(1);
  // Assert clock signal
  gpio_write(c | LCD_E);
  delay_us(1);
  // Deassert the clock signal
  gpio_write(c & (~LCD_E));
  delay_us(1);
}

// Assert the RS signal
void lcd_set_rs()
{
  Xuint32 c;
  // Get existing outputs
  c = gpio_read();
  // Assert RS
  gpio_write(c | LCD_RS);
  delay_us(1);
}

// Deassert the RS signal
void lcd_reset_rs()
{
  Xuint32 c;
  // Get existing outputs
  c = gpio_read();
  // Assert RS
  gpio_write(c & (~LCD_RS));
  delay_us(1);
}

// Assert the RW signal
void lcd_set_rw()
{
  Xuint32 c;
  // Get existing outputs
  c = gpio_read();
  // Assert RS
  gpio_write(c | LCD_RW);
  delay_us(1);
}

// Deassert the RW signal
void lcd_reset_rw()
{
  Xuint32 c;
  // Get existing outputs
  c = gpio_read();
  // Assert RS
  gpio_write(c & (~LCD_RW));
  delay_us(1);
}

// Write a byte to LCD (4 bit mode)
void lcd_write(Xuint32 c)
{
  Xuint32 temp;
  // Get existing outputs
  temp = gpio_read();
  temp = temp & 0xF0;
  // Set the high nibble
  temp = temp | ((c >> 4) & 0x0F);
  gpio_write(temp);
  // Clock
  lcd_clk();
  // Delay for "Write data into internal RAM 43us"
  delay_us(100);
  // Set the low nibble
  temp = temp & 0xF0;
  temp = temp | (c & 0x0F);
  gpio_write(temp);
  // Clock
  lcd_clk();
  // Delay for "Write data into internal RAM 43us"
  delay_us(100);
}

// Clear LCD
void lcd_clear(void)
{
  lcd_reset_rs();
  // Clear LCD
  lcd_write(0x01);
  // Delay for "Clear display 1.53ms"
  delay_ms(2);
}

// Write a string to the LCD
void lcd_puts(const char * s)
{
  lcd_set_rs();
  while(*s)
    lcd_write(*s++);
}

// Write character to the LCD
void lcd_putch(Xuint32 c)
{
  lcd_set_rs();
  lcd_write(c);
}

// Change cursor position
// (line = 0 or 1, pos = 0 to 15)
void lcd_goto(Xuint32 line, Xuint32 pos)
{
  lcd_reset_rs();
  pos = pos & 0x3F;
  if(line == 0)
    lcd_write(0x80 | pos);
  else
    lcd_write(0xC0 | pos);
}

// Initialize the LCD
void lcd_init(void)
{
  Xuint32 temp;

  // Write mode (always)
  lcd_reset_rw();
  // Write control bytes
  lcd_reset_rs();

  // Delay 15ms
  delay_ms(15);

  // Initialize
  temp = gpio_read();
  temp = temp | LCD_DB5;
  gpio_write(temp);
  lcd_clk();
  lcd_clk();
  lcd_clk();

  // Delay 15ms
  delay_ms(15);

  // Function Set: 4 bit mode, 1/16 duty, 5x8 font, 2 lines
  lcd_write(0x28);
  // Display ON/OFF Control: ON
  lcd_write(0x0C);
  // Entry Mode Set: Increment (cursor moves forward)
  lcd_write(0x06);

  // Clear the display
  lcd_clear();
}
  1. Right click on “Project: TestApp_Peripheral” and click “Generate Linker Script”.
  2. Click “OK” to generate the linker script.

 

Download and Test the Project

  1. Open a Hyperterminal window with the required settings. For the correct settings, see Hyperterminal Settings.
  2. Turn on the ML505 board.
  3. From the EDK, select “Device Configuration->Download Bitstream”.

The software application simply initializes the LCD, clears the display and writes a message over the two lines. The LCD should look as shown in the image below.

You can download the project files for this tutorial and try it on your ML50x board. Please select the file corresponding to your board, right-click on it and select “Save Link As”. You can also download the project files here on GitHub.

Board Virtex-5 Version Project files
ML505 XC5VLX50T LCDDriver-ML505-EDK10-1.zip
ML506 XC5VSX50T LCDDriver-ML506-EDK10-1.zip
ML507 XC5VFX70T LCDDriver-ML507-EDK10-1.zip
XUPV5 XC5VLX110T LCDDriver-ML509-EDK10-1.zip

 

Jeff holds a bachelors degree in Electrical Engineering from the University of Sydney, and has more than a decade of experience in electronic and FPGA design. He has worked for design houses in Australia and Canada developing electronic products for a wide range of industries and markets. Jeff now works as an electronic design consultant and offers electronic and FPGA design services through his company Opsero.

Facebook Twitter LinkedIn 

24 Comments


  1. Hi jeff,

    Is there any jumper settings i need to do on ML505 board for LCD driver.


    • Hi Surendra, No, there are no particular DIP switch settings or jumper settings for this tutorial.


  2. Hi Jeff,

    Thanks for your reply.

    I had tried LCD driver example on ML505 board as per your tutorial. But i am not able to display anything on the LCD. I tried other examples like ether mac. timer with interrupts and they perfectly works.

    Please let me know anythung else i need to try for LCD drivers.
    Thanks Jeff..

    Surendra


    • Did you try just downloading the project files?


  3. Hi Jeff

    i have Spartan 3AN Kit and i want to know if these steps can work on my LCD?

    Thank You


    • You will have to check the specs of the LCD on the Spartan 3AN kit and compare them with those of the ML505 LCD. If they are the same or similar LCDs, it should work if you change the pin location constraints in the UCF file.


  4. Hi Jeff,
    I also do not get the “fpgadeveloper.com” displayed on the LCD. i downloaded the project files. On FPGA, it is written “Design Loaded Using SPI Mem_” after the download is successful on the board.


    • Do you have the ML505 board or another dev kit? Did you set your DIP switches correctly?


  5. I tried this tutorial with the ML507 board. whenever I try to generate a netlist, I get an error concerning my .ucf file. IT insists that the NET LCD_IO_pin does not exist. Any ideas?


    • Did you build the project by going through the tutorial, or did you download the project files directly?


      • For some reason XPS named the NETs differently, once I changed them in the UCF, they worked fine. I’m using the PowerPC, and Xilinx 12.4, that may be the cause. Thanks for the walkthrough, really helped me on my project.


  6. Dear Jeff

    I have been learning the edk for some time.
    I have no board to support EDK.
    Now I have some question about EDK
    1 in the xps ,we when design the project using the c language,and can we use modelsim to simulate it with verilog
    2 from the xilinx official , we find it can not offter examples code in EDK and SDK
    ,if we learn continue, how learn ?
    3 if we have no board, we cae simulate it using software such as modelsim
    4 in xps, we generate the simulation file with verilog, we can simulate the c /c++ language behavior in modelsim
    5 your LCD example is fun, we use it for practice

    thank you


    • Hi,
      1. I’m not sure I completely understand your question, but yes, you can simulate XPS projects.
      2. Without a board, it will be hard to verify your designs, but you can still learn by simulating your work.
      3. Yes, you can simulate in modelsim.
      4. Is that a question?
      5. Thanks!


  7. Hi Jeff,

    it’s awesome. I remember that as I began to use EDK I also read your some other tutorials. This example works fine with XUPV5LX110T board. I have followed your steps to create it because I am using ISE 13.2 and some IP cores have newest version.

    By the way, I think you forgot to mention that we also need to change the external ports name manually after Step 7, because its default name is “xps_gpio_0_pin”. Maybe because I am using ISE 13.2, it’s a little bit different. Anyway, good tutorial for someone who is getting started with LCD just like me.

    Keep it up. Thanks very much.


  8. Hi Jeff,

    I tried to run your tutorial on XUPV5-LX110T, I followed all the steps as you suggested and build the project that was fine, now when I want to generate the bitstream or to use the serial interface to program the device I get an Error indicating all the constraints relating to LCD pin numbers are not valid

    for instance :

    ERROR:ConstraintSystem:59 – Constraint <Net LCD_IO_pin DRIVE=2;>
    [system.ucf(52)]: NET “LCD_IO_pin” not found. Please verify that:
    1. The specified design element actually exists in the original design.
    2. The specified object is spelled correctly in the constraint source file.
    ERROR:Xflow – Program ngdbuild returned error code 2. Aborting flow execution… ”

    I already changed the project options from ML505 to XUPV5 and it did nt help,

    2.
    I tried to open the original project you have put on your site to see if I could figure the differences between two or not, now the issue is that since I musing XPS 12.3 , the project should be updated, I followed the IP updating wizard thing but at the end it cannot find ” microblaze-v7-10-d ” , what do you suggest ?

    3. thank you for your time and tutorials

    rooz


    • Hi again,
      for part 1, I figured out that XPS could nt handle the alias u chose, i.e. “LCD_IO”, i dont know why, and I used the original net name in the following fashion : “Net xps_gpio_0_GPIO_IO_pin[1] LOC = J17 | IOSTANDARD=LVCMOS33;” , I think it helped,
      thank you,
      rooz


  9. Hi, Jeff!
    I followed your steps and succeeded in controlling the LCD on ML605. But I don’t quite understand part of the source code. Can you explain a little bit the functions “lcd_goto” and “lcd_init”? Thanks for your sharing!


  10. Dear Jeff

    I want to design an IP to drive that LCD,and the process seems very different from the design above.so can you show me how to deal with it ,I have tried many times,but the LCD isn’t work and I really don’t know where the problem exists,I’m looking forwand to your response.

    thank you!


  11. hi jeff,

    im a newbee in learning EDK and SDK. Can u please guide me in where to find more information on xilinx FPGA related C functions (like Xil_ICacheInvalidate(),etc ) and datatypes ( xuint, XGpio).

    Also if i have a basic knowledge on writing C programs, would i be able to write code for my embedded system project using the xilinx FPGA?


  12. i tried this tutorial using an xps 12.2
    when i gnerate my bitstream at the end ,there is an errors :
    ERROR:Xflow – Program ngdbuild returned error code 2. Aborting flow execution…
    make: *** [__xps/system_routed] Error 1
    i would like to khnow why ??this error is occured if i follow exactely the tuto


  13. Hi
    I was going through EDK. But i have ML402 vertex 4 kit
    can i do these on thsi kit

    also i want to know the exact use of XPS type of designing


  14. Nice project. I will try this but , I think making a lcd drive using arduino is a lot easier and cheaper than by using ml50x board.


  15. I have tried your sample design using EDk 13.2. But when it upgrades the design it gives an error message saying ‘clock_generator_v2_01_a was not found’. Can you please advice me how to resolve this issue?