Dot Matrix LCD (VSC)

Prerequisite Modules:

Introduction

Dot matrix LCDs allow a lot of freedom in how information is displayed in an embedded system. Each dot (pixel) in the LCD is addressable and can be turned on and off independently from the other pixels. The EiE dot matrix LCD is 128 pixels wide by 64 pixels high (see the full datasheet here). The LCD is, technically, mounted upside down on the EiE development board, but that doesn’t really matter as long as you remember the addressing. The pixel addresses are shown here:

The API also defines a standard 8-line text layout like this:

In general, dot matrix LCDs are written with various bitmaps or functions that can create basic geometric shapes like lines, boxes and circles. Characters are displayed in the same way – bitmaps for each letter are simply loaded into the LCD’s display RAM and in most cases the LCD has no idea it is displaying a letter vs. some random shape. To assist with programming images manually, an Excel spreadsheet was created to help. The spreadsheet should be in the Github repository (look in \firmware_dotmatrix\drivers) or you can access it here.

The driver to use the LCD can be quite involved and could include a lot of different features. It is fairly comprehensive and provides two fonts, bitmap functions, and string functions which will cover all of the basic needs of a system. We must be mindful of the resources required to update the LCD since an entire screen refresh requires 1024 bytes of data, and likewise storing a full-screen image also requires 1 kB of flash space. The main font is made up of equally-spaced characters 7 pixels high and 5 pixels wide and includes all of the “normal” printable characters. You can add any character or image you want as long as you carefully follow the process. All graphics are stored in the lcd_bitmaps.c file.

API Description

This API will seem far more cumbersome than the majority of other modules for the development board as the code remains fairly low-level. Further abstraction is of course possible, but would likely be more application-specific for how exactly the LCD was being used and thus not worth including anything in the base driver.

Type Definitions
To use the API, you need the following from the types defined in lcd_NHD-C12864LZ.h:

LcdFontType – enumerated type that selects the font to use when sending strings.
Values: {LCD_FONT_SMALL, LCD_FONT_BIG}

PixelAddressType – Pixel address struct
{
u16 u16PixelRowAddress;
u16 u16PixelColumnAddress;
}

PixelBlockType – Address struct to define a box of pixels
{
u16 u16RowStart; Address of top left pixel row
u16 u16ColumnStart; Address of top left pixel column
u16 u16RowSize; Number of rows in block
u16 u16ColumnSize; Number of columns in block
}
e.g. A twenty-five pixel high by thirty pixel wide block in the top-left corner would be initialized like this:
PixelBlockType sBoxExample = {0, 127, 25, 30};

The box is implemented by the firmware driver right-of (decreasing column addresses) and down-from (increasing row addresses) the top left pixel specified.

The lcd_NHD-C12864LZ.c file provides some globally defined PixelBlockType variables for clearing lines of text.


Public Functions
The driver API functions work with a copy of the LCD RAM in the processor’s RAM. This allows changes to be made quickly without worrying about updating the LCD until the new screen image is completely buffered. The driver automatically refreshes the LCD every LCD_REFRESH_TIME ms (currently 40 times per second). The following functions may be used by any application in the system.

  • void LcdSetPixel(PixelAddressType* sPixelAddress_) – Turn on one pixel in the LCD RAM.
  • void LcdClearPixel(PixelAddressType* sPixelAddress_) – Turn off one pixel in the LCD RAM.
  • void LcdClearPixels(PixelBlockType* sPixelsToClear_) – Clears the selected block of pixels. A custom area can be provided to this function, but several commonly used predefined areas are included.
  • void LcdClearScreen(void) – Clears all of the current pixel data.
  • void LcdLoadString(const unsigned char* pu8String_, LcdFontType eFont_, PixelAddressType* sStartPixel_) – Updates the local LCD memory with an ASCII string in the font specified. Any pixels that will not fit on the LCD are ignored. sStartPixel_ is the location where the top left pixel of the first character bitmap square is specified.
  • void LcdLoadBitmap(u8* aau8Bitmap_, PixelBlockType* sBitmapSize_) – Places a bitmap into the LCD RAM. Bit maps are 2D arrays of pixels; the size of the bitmap must be in SBitmapsSize).
  • bool LcdCommand(u8 u8Command_) – Sends a control command to the LCD. The commands relevant to tasks using the LCD are: LCD_DISPLAY_ON, LCD_DISPLAY_OFF, LCD_PIXEL_TEST_ON, LCD_PIXEL_TEST_OFF.

Examples

Turn on a pixel at the center of the screen:
PixelAddressType sTargetPixel = {32, 64};
LcdSetPixel(&sTargetPixel);

Turn off a pixel in the center of the screen:
PixelAddressType sTargetPixel = {32, 64};
LcdClearPixel(&sTargetPixel);

Clear a 25 x 30 block of pixels in the top-left corner of the LCD:
PixelBlockType sPixelsToClear = {0, 127, 25, 30}
sPixelsToClear.u16RowStart = 0;
sPixelsToClear.u16ColumnStart = 0;
sPixelsToClear.u16RowSize = 25;
sPixelsToClear.u16ColumnSize = 30;
LcdClearPixels(sPixelsToClear);

Clear a 25 x 30 block of pixels in the bottom-right corner of the LCD:
PixelBlockType sPixelsToClear;
sPixelsToClear.u16RowSize = 25;
sPixelsToClear.u16ColumnSize = 30;
sPixelsToClear.u16RowStart = LCD_BOTTOM_MOST_ROW – sPixelsToClear.u16RowSize;
sPixelsToClear.u16ColumnStart = LCD_RIGHT_MOST_COLUMN – sPixelsToClear.u16ColumnSize;
LcdClearPixels(sPixelsToClear);

Load a string on the bottom text line left justified.
PixelAddressType sTestStringLocation = {LCD_SMALL_FONT_LINE3, LCD_LEFT_MOST_COLUMN);
u8 au8TestString[] = “Testing”;
LcdLoadSting(au8TestString, LCD_FONT_SMALL, &sTestStringLocation);

Load the test screen image referenced to the top left corner of the screen
PixelBlockType sTestImage;
sTestImage.u16RowStart = 0;
sTestImage.u16ColumnStart = 0;
sTestImage.u16RowSize = 50;
sTestImage.u16ColumnSize = 50;
LcdLoadBitmap(&aau8TestPosition[0][0], sTestImage);


Next you will write your name on the screen and move it down when BUTTON0 i is pressed and up when BUTTON1 is pressed.

  1. Pull the latest Master code from Github.
  2. Add UserApp1_au8Name[] in the global variable section that is accessible to all functions in UserApp1.
  3. Use LcdClearScreen() to clear the LCD in UserAppInitialize.
  4. Write your name in UserAppInitialize. The starting pixel address is LCD_TOP_MOST_ROW and LCD_LEFT_MOST_COLUMN.

Build and run the code to ensure that it does what you expect.

The algorithm to move your name around should have the following features:

  • If BUTTON0 was pressed and there is an empty row below the current row, erase the current row and re-write the name string to the new row; if the name is already at the bottom, do nothing (or alternatively, wrap it back to the top row).
  • If BUTTON1 was pressed and there is an empty row above the current row, erase the current row and re-write the name string to the new row; if the name is already at the top do nothing (or alternatively, wrap it back to the bottom row).

Implement the code as follows:

  1. Add static u8 u8CurrentRow = 0 to UserAppSM_Idle.
  2. Check if BUTTON0 was pressed.
  3. If BUTTON0, check that u8CurrentRow is not currently = LCD_SMALL_FONT_ROWS.
  4. If there is room, use LcdClearPixels to clear the name from the current row.
  5. Increment u8CurrentRow and write your name to the new row.
  6. Build and test that the code will move your name all the way to the bottom of the screen.
  7. Copy the code and make adjustemnts to move your name back up the screen with BUTTON1.

Exercise

The challenge exercise for this module is to make a custom logo to represent yourself. The first step is to add your logo into the LCD worksheet. Make the logo 25×25 pixels – this means that you can copy and paste one of the existing 25×25 bitmaps. Rename it MyLogo25x25 and then draw whatever you would like. Enter “1” to turn on a pixel, or “0” to turn off a pixel. The pixel will shade automatically, and the code will write itself.

Once complete, copy your logo into the LCD bitmaps file and add the required definitions.

The next task is to show your logo when the development board boots up. Find the code that is currently running during initialization and replace it with code that loads your static image to the center of the screen. You could animate it in some way, but beware of the time required!

When the code is running in the main loop, move the image when BUTTON 0 is pressed. Ideally you should clear just the logo area of the screen, then re-draw the logo in the new location. Figure out a way to pull a random starting pixel based on the current 1ms timestamp.

[LAST UPDATE: 2024-11-26]