Close

Displaying the Time: LCD Operations

A project log for NTP Clock Based on STM32H735 Discovery Kit

This is an SNTP clock based on the STM32H735 Discovery Kit.

dmoisandmoisan 04/04/2021 at 16:570 Comments

The STM32H735G Discovery Kit has an LCD touchscreen display of 480x272 pixels, smaller than VGA, but bigger than the displays on most other project boards.  I bought the board for the display.  Here's the code.

For the user, the LCD code starts in main.c, in function BSP_Config:

static void BSP_Config(void)
{

  /* Initialize the LCD */
  BSP_LCD_Init(0, LCD_ORIENTATION_LANDSCAPE);
  UTIL_LCD_SetFuncDriver(&LCD_Driver);
  UTIL_LCD_Clear(UTIL_LCD_COLOR_BLACK);
  UTIL_LCD_SetBackColor(UTIL_LCD_COLOR_BLACK);
  UTIL_LCD_SetTextColor(UTIL_LCD_COLOR_WHITE);
//  UTIL_LCD_SetFont(&DroidSansMono20);
//  UTIL_LCD_DisplayStringAt(10,60, (uint8_t *)"Initializing network...", CENTER_MODE);
}

This code first sets the orientation of the LCD to landscape, setting certain internal counters and position references, and then it references a list of driver function primitives--there are eleven basic functions that must be implemented.  Then it clears the LCD screen, sets the background color (black) and the text color (white).

I've commented out the next lines, which would have displayed a message on the screen.  More on those as we get further into the code.

These are the eleven primitive LCD functions:

/** @defgroup STM32H735G_DISCO_LCD_Private_TypesDefinitions LCD Private TypesDefinitions
  * @{
  */
const LCD_UTILS_Drv_t LCD_Driver =
{
  BSP_LCD_DrawBitmap,
  BSP_LCD_FillRGBRect,
  BSP_LCD_DrawHLine,
  BSP_LCD_DrawVLine,
  BSP_LCD_FillRect,
  BSP_LCD_ReadPixel,
  BSP_LCD_WritePixel,
  BSP_LCD_GetXSize,
  BSP_LCD_GetYSize,
  BSP_LCD_SetActiveLayer,
  BSP_LCD_GetPixelFormat
};

All other drawing functions are derived from these.  The graphics library supports two drawing layers, but I only use one of them.

We convert the time data in hours, minutes and seconds, to glyphs to display on the screen.  I use macro definitions for the X and Y coordinates so I can easily tweak the position of the elements.

    // Display code

    //
    // Text positioning definitions
    //
#define LINE1XBASELINE    140
#define LINE1YBASELINE    70

    //
    // Clear time display
    //

    UTIL_LCD_SetFont(&DroidSansMono28);
//    UTIL_LCD_SetFont(&DroidSansMono72);
    UTIL_LCD_ClearStringLine(LINE1YBASELINE);

    // Line 1 Hours, Minutes, Seconds
    static char textstring[25];

    TimeToStr(textstring, hour);
    UTIL_LCD_DisplayStringAt(LINE1XBASELINE,LINE1YBASELINE, (uint8_t *)textstring, LEFT_MODE);
    UTIL_LCD_DisplayStringAt(LINE1XBASELINE+42,LINE1YBASELINE, (uint8_t *)":", LEFT_MODE);

    TimeToStr(textstring, minute);
    UTIL_LCD_DisplayStringAt(LINE1XBASELINE+64,LINE1YBASELINE,(uint8_t *)textstring, LEFT_MODE);
//    UTIL_LCD_DisplayStringAt(LINE1XBASELINE+120,LINE1YBASELINE,(uint8_t *)":", LEFT_MODE); -- Don't display colon

    TimeToStr(textstring, second);
    UTIL_LCD_SetFont(&DroidSansMono20);
    UTIL_LCD_DisplayStringAt(LINE1XBASELINE+116,LINE1YBASELINE,(uint8_t *)textstring, LEFT_MODE);
// original offset = 220
    UTIL_LCD_DisplayStringAt(LINE1XBASELINE+162,LINE1YBASELINE,(uint8_t *)TimeZoneName, LEFT_MODE);

I use the open source font Droid Sans Mono for display.  I've found that the biggest size I can use is 28;  the lower-level LCD HAL routines (what the LCD primitive functions call) display garbage on larger bitmap sizes.  There's obviously a limit on the size of a bitmap.   TimeToStr is a homemade function that converts time values (hours, minutes and seconds) to bitmaps.  I don't like using the standard sprintf function in this routine, because the latency of string handling is unpredictable.  TimeToStr will take the same time and the same resources to execute, which it must do, 60 times a minute.  (There are sprintf functions designed specifically for embedded systems, which I hope to try out later on.)

This is how it looks so far:

Now to display the month, day and year in North American English format.

    //
    // Text position definitions for Line 2
    //

#define LINE2XBASELINE    80
#define LINE2YBASELINE    190

    //
// Display the day of week string
#define DISPLAYDAYOFWEEKBASELINE    18

    // Part 2
    UTIL_LCD_SetFont(&DroidSansMono20);
    UTIL_LCD_ClearStringLine(LINE2YBASELINE);

// Display the day of week string
    switch (day_of_week) {
        case 0:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Sun, ", LEFT_MODE);
            break;
        case 1:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Mon, ", LEFT_MODE);
            break;
        case 2:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Tue, ", LEFT_MODE);
            break;
        case 3:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Wed, ", LEFT_MODE);
            break;
        case 4:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Thu, ", LEFT_MODE);
            break;
        case 5:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Fri, ", LEFT_MODE);
            break;
        case 6:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Sat, ", LEFT_MODE);
            break;
        default:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYDAYOFWEEKBASELINE,LINE2YBASELINE, (uint8_t *)"Err, ", LEFT_MODE);
            break;
    }

    // Display month string
#define DISPLAYMONTHBASELINE    90
    switch (month) {
        case 1:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Jan ", LEFT_MODE);
            break;
        case 2:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Feb ", LEFT_MODE);
            break;
        case 3:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Mar ", LEFT_MODE);
            break;
        case 4:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Apr ", LEFT_MODE);
            break;
        case 5:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"May ", LEFT_MODE);
            break;
        case 6:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Jun ", LEFT_MODE);
            break;
        case 7:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Jul ", LEFT_MODE);
            break;
        case 8:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Aug ", LEFT_MODE);
            break;
        case 9:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Sep ", LEFT_MODE);
            break;
        case 10:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Oct ", LEFT_MODE);
            break;
        case 11:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Nov ", LEFT_MODE);
            break;
        case 12:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Dec ", LEFT_MODE);
            break;
        default:
            UTIL_LCD_DisplayStringAt(LINE2XBASELINE+DISPLAYMONTHBASELINE,LINE2YBASELINE, (uint8_t *)"Err ", LEFT_MODE);
            break;
    }

    //
    // Line 3  Day nnn (of the year)
    //

#define LINE3XBASELINE    184
#define LINE3YBASELINE    150

    UTIL_LCD_ClearStringLine(LINE3YBASELINE);
    memset(&textstring,0,sizeof(textstring));
    snprintf(textstring, 10, " %u",day);
    UTIL_LCD_DisplayStringAt(LINE2XBASELINE+160,LINE2YBASELINE, (uint8_t *)textstring, LEFT_MODE);;
    UTIL_LCD_DisplayStringAt(LINE2XBASELINE+222,LINE2YBASELINE, (uint8_t *)", ", LEFT_MODE);
    memset(&textstring,0,sizeof(textstring));
    snprintf(textstring, 10, "%u", (year));
    UTIL_LCD_DisplayStringAt(LINE2XBASELINE+240,LINE2YBASELINE, (uint8_t *)textstring, LEFT_MODE);

    memset(&textstring,0,sizeof(textstring));
    snprintf(textstring, 20, "Day %u",days_since_first_of_year);
    UTIL_LCD_DisplayStringAt(LINE3XBASELINE,LINE3YBASELINE, (uint8_t *)textstring, LEFT_MODE);
}

This goes through several long switch statements because C string arrays (or not), of course.  I tried writing my own routtine to replace sprintf, but wasn't successful. UTIL_LCD_DrawStringLine is a function I've never seen in a library before.  Apparently, it will erase a string that was previously drawn on the screen at a certain Y position.  It does this by redrawing the string, that was previously drawn, in the background color!  Unusual. UTIL_LCD_DisplayStringAt is a conventional function that makes a character string into a bitmap and displays it.  The day and month string uses Droid Sans Mono 20.

This is what it looks like.  The alignment is really off, and that's on me.  I've had the clock running for almost three months and one doesn't notice flaws until one takes a fresh look at it.

There's a question you may be asking:  How did you get the font in your code?

The next project log will answer that.  Until next time.  It's Easter in North America as I write this, so if you celebrate it, have a Happy Easter!

Discussions