I want to build on the design created in Workshop 4 of the Summer of FPGA series with the Ultra96-V2. That workshop created a design to use the UART click board and a PWM IP to drive the backlight on a LCD Mini Click board. The foundation of what follows is that design, and I'm assuming that if you are following along, you have that in place and you can fill in the simple blanks without me describing them in detail. I have additional useful posts associated with that workshop:
Summer of FPGA, Workshop 4: Elaborating on creating custom IP
Introduction
In Part One, I tested a design for emitting an SPI signal from the PL to the Low Speed Header that the Click Mezzanine sits in. In this part, I will incorporate that design into the existing solution and create the actual PS software that will interact with the LCD Mini Click.
Bryan Fletcher asked a good question in the comments for Part One: "Why not use the existing Mezzanine pins for this?" In the normal course of such a solution, the best approach would be to use those pins, which are connected to the PS, and use the Ultra96-V2 hardware as its designers envisaged. Certainly, to take this approach requires a bit of a bodge, by cross-wiring the PL pins on the LS Header to the PS pins on the LS Header, to interface the PL to the Click Mezzanine, but I wanted to use as much of the PL as possible in order to better understand how to design a hardware solution rather than a mostly software one. At this stage, it is still a mix of hardware and software as the actual SPI waveforms are generated through the PS using the AXI Quad SPI library provided by Xilinx. I may create a Post Three in which I update the hardware to actually undertaken the waveform generation.
Another "Fix"
The LCD Mini Click has a digipot that, by default, powers up at midrange (actually, it is a non-volatile potentiometer so it powers up to its last setting.) When the LCD operates in 2-line mode then the contrast is too low for the text to be visible at the default setting so the contrast must be adjusted. This is accessed via the "AN" pin on the MikroBus (or, as labelled on the Click Board, CS2) and unfortunately that pin is allocated as an input to the ADC on the Click Mezzanine so there is no direct way of addressing the digipot. A number of possible solutions were given in the comments on Post One of this series, but I've taken the approach of cross-wiring the CS pin on MikroBus 2 to the AN pin on MikroBus 1 using a temporary, non-soldered solution. In MikroBus 2 I have the I2c/USB UART click board and the CS pin is not connected so is free to use. By wrapping a thin wire around the board's CS and AN pins before engaging them into the MikroBus headers, the wire makes a connection and is held firmly in place between the bottom of the boards and the headers. A resistance check on my DMM ensures that there is no cross-connection of neighbouring pins when in place. The advantage is this is a temporary fix and doesn't require me to solder anything and risk damaging the boards.
I will need two SPI outputs and address CS1 on the LS header.
The I2C/USB Click board schematic
The Click Mezzanine schematic with the cross-wiring for the digipot Chip Select bodge:
Updating the Design in Vivado
See part one for the step-by-step process. I will be adding the AXI Quad SPI and Clocking Wizard IPs alongside the existing UART and PWM IPs. In addition, the AXI Quad SPI IP must be updated to use 2 slaves. Having done that, the block design looks like this:
Having added an extra SPI slave, an additional pin is required:
AXI QSPI Pin | Function | Click Mezzanine Header J5 Pin | Cross-Connect to J5 Pin | Low Speed Header | Bank | PL Pin |
---|---|---|---|---|---|---|
spi_ssio[0:0] | CS | 16 | 12 | HD_CPIO_9 | 26 | E6 |
spi_ssio[0:1] | CS1 | 9 | 26 | HD_GPIO_03 | 26 | G7 |
spi_sck_io | SCK | 22 | 8 | HD_GPIO_10 | 26 | D5 |
spi_io0_io | MOSI | 18 | 14 | HD_GPIO_11 | 26 | E5 |
spi_io1_io | MISO | 20 | 10 | HD_GPIO_12 | 26 | D6 |
The constraints file looks like this:
set_property PACKAGE_PIN F6 [get_ports ls_mezz_uart1_tx] set_property PACKAGE_PIN G5 [get_ports ls_mezz_uart1_rx] set_property PACKAGE_PIN A6 [get_ports {LS_Mezz_PWM_Out1[0]}] set_property IOSTANDARD LVCMOS18 [get_ports {LS_Mezz_PWM_Out1[0]}] set_property IOSTANDARD LVCMOS18 [get_ports ls_mezz_uart1_tx] set_property IOSTANDARD LVCMOS18 [get_ports ls_mezz_uart1_rx] set_property PACKAGE_PIN E6 [get_ports {spi_ss_io[0]}] set_property PACKAGE_PIN G7 [get_ports {spi_ss_io[1]}] set_property PACKAGE_PIN E5 [get_ports spi_io0_io] set_property PACKAGE_PIN D6 [get_ports spi_io1_io] set_property PACKAGE_PIN D5 [get_ports spi_sck_io] set_property IOSTANDARD LVCMOS18 [get_ports {spi_ss_io[0]}] set_property IOSTANDARD LVCMOS18 [get_ports {spi_ss_io[1]}] set_property IOSTANDARD LVCMOS18 [get_ports spi_io0_io] set_property IOSTANDARD LVCMOS18 [get_ports spi_io1_io] set_property IOSTANDARD LVCMOS18 [get_ports spi_sck_io]
I also turned off some of the PL functionality that I don't need for this solution so the Block Diagram looks like this:
I can then run synthesis, implementation and generate the bitstream before exporting the hardware XSA file.
Vitis
In Vitis, most of the work will be creating the software interface that sends commands to the LCD Mini Click, based on the actual LCD used. As mentioned in Part One, the LCD is a Topway LMB162XFW module and a readable data sheet is available here. It uses a Sitronix ST7066U LCD controller/Driver and a data sheet is available here (although see below for an update on this statement!) Note that there is a much clearer command table in the Sitronix datasheet on page 17 and an even clearer one in the data sheet for the HD44780 linked in the text further below.
Open the project created as part of Workshop 4: this already contains the code for operating the UART Click and PWM of the LCD Mini Click. You could also start from scratch if you're just going to use my code rather than write your own.
Update Hardware Specification
First things first: the HW design has changed so the project needs to be refreshed with the just exported XSA file:
It's worth re-building the platform project, which is now flagged as 'out of date' to save time later.
System and Application projects
Getting Ready
I could just build on these but I don't like the name which references the LCD backlight - I'm going to do more - and the location the projects are stored also reflects that name. I'll create a new Application Project called Ultra96v2_LCD_Mini_Click, using the Standalone on psu_cortexa53_0 domain and use an Empty C++ Application template. To make it simple, I can copy the Ultra96_v2_LCD_Backlight source files into the new project, renaming as necessary - I've called it lcd_example_app.c. I also imported the code from my SPI_Test application created in Part One so that I can use that for reference; later I deleted it as it was no longer required. My application src directory looks like this:
This is my coding jump-off point. There isn't a blow-by-blow account as I write the code but I will take my normal cyclic approach of code-test-fix. I also refactored the originally used code from the Workshop to make it easier for others to follow in the context of this application.
Development Notes
If you want to have a go at writing your own code, then the following will be useful and save you a bit of research time. Checking the schematic for the LCD Mini Click:
Port Expander
The Port Expander, MCP23S17, is using the Port B GPIOs and the LCD is only wired with 4 data pins thus operates in 4-bit mode; note that pins labelled RS and E are used in the writing of data to the LCD driver IC, HD44780U. The MCP23S17 address pins A0, A1 and A2 are connected to ground giving the driver an address of 0x00. This will need to be reflected in the code written.
To write to the MCP23S17, the master must send an OPCODE followed by REGISTER ADDRESS followed by one or more data bytes:
This application only writes to the SPI slave so the OPCODE will be 0b01000000.
The data sheet gives timing for SPI operations. These are mostly in nanosecond duration and, where not, in very low micro second duration. The code will (may) need to take account of this.
LCD and LCD Driver
The data sheet for the LCD refers to the driver IC as ST7066U which is a Hitachi HD44780U equivalent. Actually, the data sheet is a little confused as it also refers to a ST7066U or equivalent. During development, I found that the initialisation of the LCD was correctly achieved following the HD44780U datasheet, rather than the ST7066U data sheet, and thus that's the one I used going forward.
Writing to the LCD, or rather the LCD controller HD44780U, requires conformance to the following, in 4-bit mode:
For writing instruction data, the RS pin is kept low (Register Select, not Reset!). To actually write in valid data the E pin must be toggled on then off (and should start off). The high-order nibble is sent before the low-order nibble. I couldn't find timings for the pulses but the instruction table does give execution times - I just need to build in suitable delays. I'm not building a generic library for the LCD so I'm only implementing what is necessary for my own purposes.
The LCD is a 16x2 device but the driver is designed for 40x2. Therefore, each line can contain up to 40 characters although only 16 are shown on the display (to use all 80 you would need to amend the buffer size I use in the software.) To display off-screen characters, the display can be shifted left which will move off the left character and bring on a new right character. The display DRAM addresses are consecutive so writing 45 characters, for example, will display characters 0 to 15 on line 1; 16 to 39 will be off-display on line 1; 40 to 44 will be on line 2. Essentially, this can be viewed as 'wraparound'.
Digipot
The digipot is an MCP4161 IC - datasheet here - which is a single 10kOhm, 8-bit, non-volatile, potentiometer. It is factory set to power up at midscale (0x80). I just need to send it a suitable value between 0 and 256: a low value for 2-Line mode - I chose 30 which works well.
Result
The running application on the Cortex A53 displays a set of available commands in the terminal:
E.g.
- DC
- CP06
- PHello
- CP14
- PElement14
Will display the message in the image below.
As the point wasn't too create a fully functional LCD Mini Click library I haven't implemented all the possible features of the LCD, e.g. no display shift. Extending it with the extra few features available will be simple enough for anyone who wants to give it a go as the underlying software is well documented and modular:
- Class (H and CPP) that encapsulates the SPI functionality
- Class (H and CPP) that encapsulates the LCD functionality
- Application that drives the LCD class to do something useful.
And the proof:
The cross-wiring to enable the PL to drive the SPI bus isn't that clear on the photo I'm afraid but I detailed it above and I didn't bother cross-connecting MISO as it wasn't needed for my purposes.
To repeat what I said earlier, in a typical application you wouldn't get the PL to drive this as the SPI bus is readily accessible to the PS and is intended to be driven that way. However, even though there isn't a lot of HW programming involved in this stage, it was still more interesting to do it the way I have. The next step would be to remove the AXI_QUAD_SPI IP block and create my own waveforms to create a full HW solution. Best be learning some VHDL first!
I know it's not as sexy as some of the video imaging examples given on E14 and elsewhere, but I hope this is of use and interest to others who participated in the workshops and will allow them to get more out of their Click Starter Set.
Project Files
The attached ZIP contains the XSA file and source code for VITIS - everything else can be re-created yourself as I've outlined the steps taken.