

Considerations for C Programming in Embedded Systems – Screen Operations
The problem to be solved now is that often in embedded systems, a complete Chinese character library is not used; instead, it is often necessary to provide a limited number of Chinese characters for essential display functions.
Chinese Character Processing
The problem to be solved now is that often in embedded systems, a complete Chinese character library is not used; instead, it is often necessary to provide a limited number of Chinese characters for essential display functions. For example, an LCD on a microwave oven does not need to display the function of “email”; an LCD on an air conditioner that provides Chinese character display does not need to show a “short message”, and so on. However, a mobile phone or a small smart device usually needs to include a more complete Chinese character library.
If the included Chinese character library is relatively complete, then calculating the offset of the Chinese character model in the library from the internal code is quite simple: the Chinese character library is arranged in the order of area number, with the first byte being the area number of the Chinese character and the second byte being the position number of that character. Each area records 94 Chinese characters, and the position number indicates the position of that character within the area. Therefore, the specific position calculation formula for a Chinese character in the library is: 94 * (area number – 1) + position number – 1. The subtraction of 1 is because arrays start at 0 while area and position numbers start at 1. Just multiply by the number of bytes occupied by a Chinese character model, that is: (94 * (area number – 1) + position number – 1) * bytes occupied by a Chinese character model. For a 16×16 dot matrix font library, the calculation formula would be: (94 * (area number – 1) + (position number – 1)) * 32. The 32 bytes of information from that position in the library record the model information of that character.
For systems with a relatively complete Chinese character library, we can calculate the position of the character model using the above rules. But what if we only need to provide a small number of Chinese characters? For example, dozens to hundreds? The best approach is:
Define Macros:
# define EX_FONT_CHAR() # define EX_FONT_UNICODE_VAL() (), # define EX_FONT_ANSI_VAL() (),
typedef struct _wide_unicode_font16x16 { WORD; /* Internal Code */ BYTE data[32]; /* Character Model Dot Matrix */ } Unicode; #define CHINESE_CHAR_NUM ... /* Number of Chinese Characters */
Unicode chinese[CHINESE_CHAR_NUM] = { { EX_FONT_CHAR("业") EX_FONT_UNICODE_VAL(0x4e1a) {0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24, 0x48, 0x14, 0x50, 0x1c, 0x50, 0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00} }, { EX_FONT_CHAR("中") EX_FONT_UNICODE_VAL(0x4e2d) {0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00} }, { EX_FONT_CHAR("云") EX_FONT_UNICODE_VAL(0x4e91) {0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00, 0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00} }, { EX_FONT_CHAR("件") EX_FONT_UNICODE_VAL(0x4ef6) {0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40} } };
To display a specific Chinese character, simply find the internal code that matches the required character’s internal code in the array to obtain the character model. If the previous Chinese characters are arranged in ascending order by internal code, then we can use a binary search method to efficiently find the character model.
This is a very effective way to organize a small Chinese character library, ensuring that the program has a good structure.
System Time Display
The system time can be read from NVRAM, and the system generally reads the current time once every second using the second interrupt generated by NVRAM and displays it on the LCD. Regarding time display, there is an efficiency issue. Since time has its special nature, where a minute only changes once every 60 seconds, and an hour only changes once every 60 minutes, if we completely refresh the displayed time on the screen every time, it wastes a lot of system time.
A better approach is to use static variables in the time display function to store hours, minutes, and seconds separately, and only update the display when their contents change.
extern void DisplayTime(...) { static BYTE byHour, byMinute, bySecond; BYTE byNewHour, byNewMinute, byNewSecond; byNewHour = GetSysHour(); byNewMinute = GetSysMinute(); byNewSecond = GetSysSecond(); if (byNewHour != byHour) { ... /* Display hour */ byHour = byNewHour; } if (byNewMinute != byMinute) { ... /* Display minute */ byMinute = byNewMinute; } if (byNewSecond != bySecond) { ... /* Display second */ bySecond = byNewSecond; } }
This example can also serve as a testament to the powerful capabilities of the static keyword in C. Of course, in C++, the static keyword has even more powerful capabilities, allowing certain data and functions to be detached from “objects” and become part of “classes”, which is precisely this characteristic that has led to countless excellent software designs.
Animation Display
Animation is neither about existence nor non-existence; a still image becomes an animation as it moves more. Thus, to display animation on an embedded system’s LCD, a timer must be utilized. A world without hardware or software timers is unimaginable:
(1) Without a timer, an operating system cannot perform time-slicing, thus cannot schedule multitasking, and will no longer be a multitasking operating system;
(2) Without a timer, a multimedia playback software cannot operate because it does not know when to switch to the next frame;
(3) Without a timer, a network protocol cannot function because it does not know when a packet transmission times out and needs to be retransmitted, nor can it complete specific tasks within a certain timeframe.
Therefore, lacking a timer means lacking an operating system, lacking a network, lacking multimedia; what a dark world that would be! Hence, the reasonable and flexible use of various timers is the most basic requirement for a software engineer!
In an embedded system with an 80186 as the main chip, we need to use hardware timer interrupts as software timers, changing the display content after the interrupt occurs. To alternate the colon in the time display “xx:xx”, ShowDot needs to be called each time a second interrupt occurs:
void ShowDot() { static BOOL bShowDot = TRUE; /* Once again, witness the power of the static keyword */ if (bShowDot) { showChar(':', xPos, yPos); } else { showChar(' ', xPos, yPos); } bShowDot = !bShowDot; }
Menu Operations
The countless problems that have puzzled many finally emerge. In this section, we will see how even a tiny bit of object-oriented thinking in C can greatly improve software structure!
It is required to switch the menu focus using the “← →” keys on the keyboard. When the user is on a menu focus, pressing the OK or CANCEL keys will call the corresponding processing function for that focused menu. I once naively did it this way:
/* Press OK key */ void onOkKey() { /* Determine which focused menu the OK key was pressed on and call the corresponding processing function */ switch (currentFocus) { case MENU1: menu1OnOk(); break; case MENU2: menu2OnOk(); break; ... } } /* Press Cancel key */ void onCancelKey() { /* Determine which focused menu the Cancel key was pressed on and call the corresponding processing function */ switch (currentFocus) { case MENU1: menu1OnCancel(); break; case MENU2: menu2OnCancel(); break; ... } } One day, I finally did it this way: /* Encapsulate the menu's attributes and operations together */ typedef struct tagSysMenu { char *text; /* Menu text */ BYTE xPos; /* Menu's x-coordinate on the LCD */ BYTE yPos; /* Menu's y-coordinate on the LCD */ void (*onOkFun)(); /* Pointer to the processing function for pressing the ok key on this menu */ void (*onCancelFun)(); /* Pointer to the processing function for pressing the cancel key on this menu */ } SysMenu, *LPSysMenu; When I define the menu, I only need to do this: static SysMenu menu[MENU_NUM] = { { "menu1", 0, 48, menu1OnOk, menu1OnCancel }, { "menu2", 7, 48, menu2OnOk, menu2OnCancel }, { "menu3", 7, 48, menu3OnOk, menu3OnCancel }, { "menu4", 7, 48, menu4OnOk, menu4OnCancel }, ... }; The processing for the OK and CANCEL keys becomes: /* Press OK key */ void onOkKey() { menu[currentFocusMenu].onOkFun(); } /* Press Cancel key */ void onCancelKey() { menu[currentFocusMenu].onCancelFun(); }
The program has been greatly simplified and now has excellent extensibility! By merely utilizing the encapsulation concept from object-oriented programming, the program structure has become clear, resulting in the ability to add more menus to the system without modifying the program, while the key processing functions remain unchanged.
Object-oriented programming is truly amazing!
Simulating the MessageBox Function
The MessageBox function, a super tool in Windows programming, is the first function many beginners encounter. Do you remember the novelty of using MessageBox to output “Hello, World!” dialog in Windows for the first time? It is impossible to count how many programmers began their journey into Windows programming with MessageBox(“Hello, World!”, …). At my undergraduate school, a term widely circulated is “‘Hello, World’ level programmer”, referring to entry-level programmers, but it seems that this term is more amusing and vivid.
Embedded systems do not provide us with a MessageBox, but given its powerful function, we need to simulate it. A simulated MessageBox function is:
/****************************************** /* Function Name: MessageBox /* Function Description: Popup dialog box to display information to remind the user /* Parameter Description: lpStr --- String output information to remind the user /* TYPE --- Output format (ID_OK = 0, ID_OKCANCEL = 1) /* Return Value: Returns the key value received by the dialog box, only two types KEY_OK, KEY_CANCEL /****************************************** typedef enum TYPE { ID_OK, ID_OKCANCEL } MSG_TYPE; extern BYTE MessageBox(LPBYTE lpStr, BYTE TYPE) { BYTE key = -1; ClearScreen(); /* Clear the screen */ DisplayString(xPos, yPos, lpStr, TRUE); /* Display string */ /* Decide whether to display OK and Cancel based on dialog type */ switch (TYPE) { case ID_OK: DisplayString(13, yPos + High + 1, " OK ", 0); break; case ID_OKCANCEL: DisplayString(8, yPos + High + 1, " OK ", 0); DisplayString(17, yPos + High + 1, " Cancel ", 0); break; default: break; } DrawRect(0, 0, 239, yPos + High + 16 + 4); /* Draw outer frame */ /* MessageBox is a modal dialog box, blocking operation, waiting for key press */ while ((key != KEY_OK) || (key != KEY_CANCEL)) { key = getSysKey(); } /* Return the key type */ if (key == KEY_OK) { return ID_OK; } else { return ID_CANCEL; }}
The above function is remarkably similar to the MessageBox we usually use in VC++ and other environments. Implementing this function will reveal its endless usefulness in embedded systems.
Conclusion
This article is the most technically deep one in this series, providing some clever handling methods for screen display in embedded systems. By using them flexibly, we will no longer be troubled by chaotic display content on the LCD.
The screen is an important auxiliary for the survival of embedded systems; a hideous display will drive users away. Poor handling of screen programming will result in the most disorganized and chaotic part of the software.
43 lectures of STM32 system courses (including course materials and source code)
Access Method: Reply [806] in the background

1
《[Practical] Important Considerations for C Programming in Embedded Systems – Software Architecture》
2
《Important Considerations for C Programming in Embedded Systems – [Memory Operations] (Includes Benefits)》
3
《Rare Good Article! The Real Situation You Must Know in Embedded Systems…》

01
02
03
04
05


