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

Connecting PMOD LCD to Minized - step by step

bartokon
bartokon
12 Apr 2022
Hello there!
I have recently created a tutorial for Minized that covers SPI PMOD LCD connection and displaying some random data on it. Here are step-by-step instructions on how to connect SPI LCD to Minized.
First, we need to create Vivado project:
{gallery}Vivado project creation

image

image

image

image

image

PMOD LCD
The TFTLCD is using ILI9341 as its driver, configured in 4-spi mode.
For communication, it uses the following pins:
  • CS - chip select (CSX)
  • RS - register select (D/CX)
  • CLK - spi clock (SCL)
  • MOSI - master output slave input (SDA)
On the page 35 of the documentation, we can see the following the waveform:
image
From the waveform, we can deduce that:
  • When CS is high, all commands and parameters are ignored
  • We are starting the transition by holding CS in low state
  • At the next positive edge of the SCL, the oldest bit - D7 is being sampled, then on the next edge D6 etc...
  • On the last bit of the byte - D0, the driver also samples D/CX signal. Before that, D/CX is ignored.
If we know how the LCD is receiving the data, we need to think how we can create an IP block that generates the same waveform. For that, we need at least 3 modules. SPI TX (spi_4l_8b.v) and memory module (spi_4l_8b_fifo.v) for configuration data. Some commands require a delay before sending the next command, for example,"software reset" or "sleep off". For that, we will add another module (spi_4l_8b_cmd_delay.v) that detects a particular command and stalls SPI transactions for a fixed amount of time.
IP development
TX module only responsibility is sending data to the LCD. When input AXI4-stream interface is valid, the module copies data to "command" reg and sends the data bit by bit.
module spi_4l_8b(
    input [8:0]command_tdata,
    input command_tvalid,
    output reg command_tready,
    output reg spi_cs,
    output reg spi_mosi,
    output spi_rs,
    output spi_clk,
    input clk,
    input reset
);

reg data_locked = 0;
reg [8:0] command = 0;
reg [3:0] command_bit_counter = 7;

assign spi_clk = clk;
assign spi_rs = command[8];

always @(posedge clk) begin 
    if (!reset) begin 
        data_locked <= 0;
        command <= 0;
        command_tready <= 0;
        command_bit_counter <= 7;
        spi_cs <= 1;
    end else begin 
        if (!data_locked) begin: wait_for_transaction
            spi_cs <= 1; 
            if (command_tready && command_tvalid) begin 
                command <= command_tdata;
                command_tready <= 0;
                data_locked <= 1;
            end else begin 
                command_tready <= 1'b1;
            end 
        end else begin
            spi_cs <= 0; 
            spi_mosi <= command[command_bit_counter];
            if (!command_bit_counter) begin: send_spi_data 
                command_bit_counter <= 7;
                data_locked <= 0;
            end else begin
                command_bit_counter <= command_bit_counter - 1;
            end
        end
    end
end

endmodule
Command delay module detects if, after an outgoing command, the design needs to stall for "delay_val" cycles. If it does, then it forces tvalid and tready signals to logic level low.
module spi_4l_8b_cmd_delay 
#(parameter delay_val = 500000)(
    output [8:0]out_command_tdata,
    output out_command_tvalid,
    input out_command_tready,
    input [8:0]in_command_tdata,
    input in_command_tvalid,
    output in_command_tready,
    input clk,
    input reset
); 

reg [31:0] delay_counter = 0;
reg lock = 0;

assign out_command_tdata = in_command_tdata;
assign out_command_tvalid = in_command_tvalid && (!lock);
assign in_command_tready = out_command_tready && (!lock);

always @(posedge clk) begin 
    if (!reset) begin 
        delay_counter <=0;
        lock <= 0;
    end else begin
        if (
           !in_command_tdata[8] && //If incoming data is command
           (in_command_tdata[7:0] != 8'b0010_1100) && //If command is not screen write
           out_command_tready && //If AXI4 transaction
           in_command_tvalid && //passes
           !lock // and IP is not already locked.
        ) begin               
            lock <= 1;
            delay_counter <= delay_val;
        end else if (!delay_counter) begin
            lock <= 0;
        end
        if (delay_counter) begin: decrement_delay_counter 
            delay_counter <= delay_counter - 1;
        end    
    end
end
    
endmodule
The last module is FIFO/Memory. It has three tasks:
  • First, after reset, initialize LCD with default values.
  • Second, send memory write command to LCD after 240x320 pixels.
  • Third, parse the incoming 8-bit stream into 9-bit SPI commands.

`define ili_NOP             8'h00       // No Operation - NOP
`define ili_SWRESET         8'h01       // Software Reset - SWRESET
`define ili_SLPOUT          8'h11       // Sleep Out
`define ili_GAMSET          8'h26       // Gamma Set
`define ili_DISPOFF         8'h28       // Display OFF
`define ili_DISPON          8'h29       // Display ON
`define ili_CASET           8'h2A       // Column Address Set
`define ili_PASET           8'h2B       // Page (row) Address Set
`define ili_RAMWR           8'h2C       // Memory Write
`define ili_MADCTL          8'h36       // Memory Access Control
`define ili_IDMOFF          8'h38       // Idle Mode OFF
`define ili_IDMON           8'h39       // Idle Mode ON
`define ili_PIXSET          8'h3A       // COLMOD: Pixel Format Set
`define ili_RAMWRCont       8'h3C       // Write Memory Continue
`define ili_FRMCTR1         8'hB1       // Frame Rate Control (In Normal Mode/Full Colors)
`define ili_DISCTRL         8'hB6       // Display Function Control
`define ili_PWCTRL1         8'hC0       // Power Control 1
`define ili_PWCTRL2         8'hC1       // Power Control 2
`define ili_VMCTRL1         8'hC5       // VCOM Control 1
`define ili_VMCTRL2         8'hC7       // VCOM Control 2
`define ili_PGAMCTRL        8'hE0       // Positive Gamma Correction 
`define ili_NGAMCTRL        8'hE1       // Negative Gamma Correction
`define ili_PCA             8'hCB       // Power Control A
`define ili_PCB             8'hCF       // Power Control B
`define ili_DTCA_ic         8'hE8       // Driver Timming Control A
`define ili_DTCB            8'hEA       // Driver Timming Control B
`define ili_POSC            8'hED       // Power On Sequence Control
`define ili_E3G             8'hF2       // Enable 3G
`define ili_PRC             8'hF7       // Pump Ratio Control

module spi_4l_8b_fifo
#(parameter MEMORY_LIMIT = 96) // Nb of init commands
(
    input [7:0]in_command_tdata,
    input in_command_tvalid,
    output reg in_command_tready,
    output [8:0]command_tdata,
    output reg command_tvalid,
    input command_tready,
    output reg counter_ce,
    input clk,
    input reset
);
    
reg [8:0] memory[MEMORY_LIMIT - 1:0];
reg [18:0] memory_counter = 0;
reg [8:0] output_reg = 0;

localparam INIT = 0;
localparam NEXT_FRAME = 1;
localparam SEND_FRAME = 2;
localparam LCD_SIZE = 153600; //240x320*2 -> 2 transactions per pixel.
reg [1:0]state = INIT;

integer i;
task tft_write (input [7:0] cmd, input type);
    begin
        memory[i] = {type, cmd};
        i = i + 1;
    end
endtask

task write_data8 (input [7:0] cmd);
    begin 
        tft_write(cmd, 1'b1);
    end
endtask

task write_cmd (input [7:0] cmd);
    begin
        tft_write(cmd, 1'b0);
    end
endtask

task write_data16(input [7:0] cmd1, input [7:0] cmd2);
    begin 
        write_data8(cmd1);
        write_data8(cmd2);
    end
endtask

task delay();
    begin
        tft_write(8'h00, 1'b0);
    end
endtask

initial begin
   i = 0;
   
   write_cmd(`ili_SWRESET);
   write_cmd(`ili_NOP);
   // Power Control A
   write_cmd(`ili_PCA);
   write_data8(8'h39);
   write_data8(8'h2C);
   write_data8(8'h00);
   write_data8(8'h34);
   write_data8(8'h02);

   // Power Control B
   write_cmd(`ili_PCB);
   write_data8(8'h00);
   write_data8(8'hC1);
   write_data8(8'h30);

   // Driver Timming Control A
   write_cmd(`ili_DTCA_ic);
   write_data8(8'h85);
   write_data8(8'h00);
   write_data8(8'h78);

   // Driver Timming Control B
   write_cmd(`ili_DTCB);
   write_data8(8'h00);
   write_data8(8'h00);

   // Power On Sequence Control A
   write_cmd(`ili_POSC);
   write_data8(8'h64);
   write_data8(8'h03);
   write_data8(8'h12);
   write_data8(8'h81);

   // Pump Ratio Control
   write_cmd(`ili_PRC);
   write_data8(8'h20);

   // Power Control 1
   write_cmd(`ili_PWCTRL1);     
   write_data8(8'h23);

   // Power Control 2
   write_cmd(`ili_PWCTRL2);    
   write_data8(8'h10);

   // VCOM Control 1
   write_cmd(`ili_VMCTRL1);     
   write_data8(8'h3E);
   write_data8(8'h28);

   // VCOM Control 2
   write_cmd(`ili_VMCTRL2);     
   write_data8(8'h86);

   // Memory Access Control
   write_cmd(`ili_MADCTL);
   write_data8(8'hA8);
    
   // Pixel Format Set
   write_cmd(`ili_PIXSET);
   write_data8(8'h55);

   // Frame Rate Control
   write_cmd(`ili_FRMCTR1);     
   write_data8(8'h00);
   write_data8(8'h18);

   // Display Function Control
   write_cmd(`ili_DISCTRL);
   write_data8(8'h08);
   write_data8(8'h82);
   write_data8(8'h27);

   // Enable 3G
   write_cmd(`ili_E3G);  
   write_data8(8'h00);

   // Gamma Set
   write_cmd(`ili_GAMSET);
   write_data8(8'h01);

   // Positive Gamma Correction
   write_cmd(`ili_PGAMCTRL);
   write_data8(8'h0F);
   write_data8(8'h31);
   write_data8(8'h2B);
   write_data8(8'h0C);
   write_data8(8'h0E);
   write_data8(8'h08);
   write_data8(8'h4E);
   write_data8(8'hF1);
   write_data8(8'h37);
   write_data8(8'h07);
   write_data8(8'h10);
   write_data8(8'h03);
   write_data8(8'h0E);
   write_data8(8'h09);
   write_data8(8'h00);

   // Negative Gamma Correction
   write_cmd(`ili_NGAMCTRL);
   write_data8(8'h00);
   write_data8(8'h0E);
   write_data8(8'h14);
   write_data8(8'h03);
   write_data8(8'h11);
   write_data8(8'h07);
   write_data8(8'h31);
   write_data8(8'hC1);
   write_data8(8'h48);
   write_data8(8'h08);
   write_data8(8'h0F);
   write_data8(8'h0C);
   write_data8(8'h31);
   write_data8(8'h36);
   write_data8(8'h0F);
  
   // Sleep Out
   write_cmd(`ili_SLPOUT);
   write_cmd(`ili_NOP);

   //Display ON
   write_cmd(`ili_DISPON);
   write_cmd(`ili_NOP);
   
   write_cmd(`ili_CASET);
   write_data8(8'h00);
   write_data8(8'h00);
   write_data8(8'h01);
   write_data8(8'h40);
   
   write_cmd(`ili_PASET);
   write_data8(8'h00);
   write_data8(8'h00);
   write_data8(8'h00);
   write_data8(8'hEF);   
   //Init Done  
end

    always @(posedge clk) begin 
        if(!reset) begin 
            memory_counter <= 0;
            counter_ce <= 0;
            state <= INIT;
        end else begin
            case (state) 
            INIT:  begin
                counter_ce <= 0; 
                command_tvalid <= 1;
                if (command_tvalid && command_tready) begin 
                    memory_counter <= memory_counter + 1;
                end             
                if (memory_counter == MEMORY_LIMIT) begin
                    state <= NEXT_FRAME;
                    command_tvalid <= 0;
                end  
            end
            NEXT_FRAME: begin
                memory_counter <= 0;
                command_tvalid <= 1; 
                if (command_tvalid && command_tready) begin 
                    state <= SEND_FRAME;
                    command_tvalid <= 0; 
                    counter_ce <= 1;
                end
            end
            SEND_FRAME: begin
                counter_ce <= 0; 
                in_command_tready <= command_tready;
                command_tvalid <= in_command_tvalid;
                if (in_command_tready && in_command_tvalid) begin 
                    memory_counter <= memory_counter + 1;
                end
                if (memory_counter >= LCD_SIZE - 1) 
                    state <= NEXT_FRAME;            
            end
            default: begin 
                state <= INIT;
                memory_counter <= 0;
            end 
            endcase 
        end        
    end  
    
assign command_tdata = state == INIT ? memory[memory_counter] : state == NEXT_FRAME ? {1'b0, `ili_RAMWR} : {1'b1, in_command_tdata};
endmodule

With power of Vivado we have a possibility to define sequence of ili operations and use Verilog tasks in initial block like a software function that sends command to the LCD. Normally we should use $readmemh for sake of project portability as not all vendors support initial begin - task memory initialization.
Testbench
The testbench is really simple, as the FIFO needs to program LCD, it generates a known list of vectors that need to be serialized and displayed at SPI output. If input data is a command, we should see no operation in the simulation waveform for a while, run simulation by clicking "Run Simulation" in Vivado.
image
This should display to you a similar waveform:
image
`timescale 1ns / 1ns
module spi_4l_8b_tb;

wire [8:0]in_command_tdata;
wire in_command_tvalid;
wire in_command_tready;

wire [8:0]out_command_tdata;
wire out_command_tvalid;
wire out_command_tready;

wire spi_cs;
wire spi_mosi;
wire spi_rs;
wire spi_clk;
reg clk = 0;
reg reset = 0;
reg [7:0] acu = 8'hFF;
reg acu_vld = 1'b1;
wire sink;
wire counter_ce;

spi_4l_8b_fifo u0_spi_4l_8b_fifo(
    .in_command_tdata(acu),
    .in_command_tvalid(acu_vld),
    .in_command_tready(sink),
    .command_tdata(in_command_tdata),
    .command_tvalid(in_command_tvalid),
    .command_tready(in_command_tready),
    .counter_ce(counter_ce),
    .clk(clk),
    .reset(reset)
);

spi_4l_8b_cmd_delay #(.delay_val(20))
u0_spi_4l_8b_cmd_delay(
    .out_command_tdata(out_command_tdata),
    .out_command_tvalid(out_command_tvalid),
    .out_command_tready(out_command_tready),
    .in_command_tdata(in_command_tdata),
    .in_command_tvalid(in_command_tvalid),
    .in_command_tready(in_command_tready),
    .clk(clk),
    .reset(reset)
);

spi_4l_8b u0_spi_4l_8b (
    .command_tdata(out_command_tdata),
    .command_tvalid(out_command_tvalid),
    .command_tready(out_command_tready),
    .spi_cs(spi_cs),
    .spi_mosi(spi_mosi),
    .spi_rs(spi_rs),
    .spi_clk(spi_clk),
    .clk(clk),
    .reset(reset)
);

always begin #5 clk = !clk; end

initial begin 
    #20 reset = !reset;  
    $monitor("New data_in: %h %0t", u0_spi_4l_8b.command, $time);
end

endmodule
Constraints
Design seems to work correctly, now it is time to fill up constraints file with correct pinout.
#######################################################################
# Pmod #1
#######################################################################
set_property PACKAGE_PIN L14 [get_ports spi_rs]
set_property IOSTANDARD LVCMOS33 [get_ports spi_rs]

set_property PACKAGE_PIN K13 [get_ports spi_cs]
set_property IOSTANDARD LVCMOS33 [get_ports spi_cs]

set_property PACKAGE_PIN L13 [get_ports spi_mosi]
set_property IOSTANDARD LVCMOS33 [get_ports spi_mosi]

set_property PACKAGE_PIN N14 [get_ports spi_clk]
set_property IOSTANDARD LVCMOS33 [get_ports spi_clk]
Block design
The last step before implementing the design is to connect everything in block design. First, we need a clock source, for this add ZYNQ7 PS IP'core and run block automation.
{gallery}block design

image

image

image
Add SPI modules to block design, by dragging them from sources and other IPs for generating LCD signal and also a VIO for reset control. Minized PS IP FCLK_CLK0 is 50Mhz. LCD module maximum frequency is 10Mhz, so we need to use clocking wizard IP to lower the clock frequency.
{gallery}Block design Ips

image

image

image

image

image

image

image
Save block design, Vivado should automatically detect and set this block design as a top module. Generate bitstream and program Minized. If you have problems with VIO control, try to lower JTAG frequency. Use VIO to control the reset bit, as reset is active low, set this bit to high and look at the LCD.
{gallery}Program minized

image

image

image
For practice, I suggest adding AXI4 datawidth converter IP and widen binary counter to 16 bits for more LCD colors :)
image
If you have problems with not detecting VIO activity, that means that ZYNQ7 PS is not programmed. You can try to boot Minized from flash memory and then reprogram FPGA from Vivado, or export hardware project and program flash with Vitis using for example "Hello world" application.
You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image
Thank you for your time, wish you luck with future projects. I hope that somebody will find this tutorial useful :)
Take care!
  • Sign in to reply
element14 Community

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

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

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

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

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube