Multi-Screen Interface Tutorial
Overview
Section titled “Overview”This tutorial explains how to design a multi-screen user interface on the TM4C1294XL LaunchPad with the BOOSTXL-EDUMKII display. You will learn how to:
- Create and manage multiple display screens.
- Switch between screens using hardware buttons (S1/S2).
- Use timers to refresh content at regular intervals.
- Organize code for scalable screen-based applications.
1. Concept
Section titled “1. Concept”Each screen in the system represents a self-contained visualization or module (e.g., Light sensor, Joystick, Microphone, Accelerometer). The screens share a common drawing context and are switched using navigation buttons.
enum ScreenID { SCREEN_LUX = 0, SCREEN_JOYSTICK, SCREEN_MIC, SCREEN_ACCEL, SCREEN_COUNT};
uint8_t currentScreen = SCREEN_LUX;Each screen has its own Draw_*() function that is responsible for updating the display content.
2. Navigation System
Section titled “2. Navigation System”Two physical buttons are used to navigate between screens:
- S1 (Left) → Previous screen.
- S2 (Right) → Next screen.
Each button has an event callback that updates the currentScreen index:
void OnLeftClick() { Buzzer_Beep(200, 40); // Feedback tone if (currentScreen > 0) currentScreen--; else currentScreen = SCREEN_COUNT - 1;}
void OnRightClick() { Buzzer_Beep(300, 40); // Feedback tone currentScreen = (currentScreen + 1) % SCREEN_COUNT;}These callbacks are attached to button objects using the attachClick() method:
btnLeft.attachClick(&OnLeftClick);btnRight.attachClick(&OnRightClick);3. Screen Update Timing
Section titled “3. Screen Update Timing”To avoid unnecessary redrawing, each screen is updated periodically using an elapsedMillis timer. The example below updates the current screen every 50 ms:
if (drawTimer >= 50) { switch (currentScreen) { case SCREEN_LUX: Draw_Lux(luxValue); break; case SCREEN_JOYSTICK: Draw_Joystick(js.x(), js.y()); break; case SCREEN_MIC: Draw_Mic(Mic_Level()); break; case SCREEN_ACCEL: Draw_Accelerometer(ax, ay); break; } drawTimer = 0;}4. Header Bar with Active Arrows
Section titled “4. Header Bar with Active Arrows”The header is common to all screens and shows navigation arrows that highlight when the corresponding button is pressed.
void DrawHeader(const char *title, bool leftActive, bool rightActive){ tRectangle header = {0, 0, 127, 20}; GrContextForegroundSet(&gContext, ClrDarkBlue); GrRectFill(&gContext, &header);
GrContextForegroundSet(&gContext, leftActive ? ClrYellow : ClrGray); GrStringDraw(&gContext, "<", -1, 4, 6, false);
GrContextForegroundSet(&gContext, rightActive ? ClrYellow : ClrGray); GrStringDraw(&gContext, ">", -1, 118, 6, false);
GrContextForegroundSet(&gContext, ClrWhite); GrStringDrawCentered(&gContext, title, -1, 64, 7, false);}Each Draw_*() function calls DrawHeader() to provide a consistent look across screens.
5. Example Screens
Section titled “5. Example Screens”Light Sensor Screen
Section titled “Light Sensor Screen”void Draw_Lux(float lux){ const float LUX_MAX = 1000.0f; int barWidth = (int)((lux / LUX_MAX) * 100); if (barWidth > 100) barWidth = 100;
tRectangle bg = {0, 21, 127, 127}; GrContextForegroundSet(&gContext, ClrBlack); GrRectFill(&gContext, &bg); DrawHeader("Ambient Light", btnLeft.isPressed(), btnRight.isPressed());
char buf[32]; snprintf(buf, sizeof(buf), "%.2f lx", lux); GrContextForegroundSet(&gContext, ClrYellow); GrStringDrawCentered(&gContext, buf, -1, 64, 50, false);
tRectangle fill = {14, 80, 14 + barWidth, 95}; GrContextForegroundSet(&gContext, ClrGreen); GrRectFill(&gContext, &fill); GrFlush(&gContext);}Joystick Screen
Section titled “Joystick Screen”void Draw_Joystick(float x, float y){ tRectangle bg = {0, 21, 127, 127}; GrContextForegroundSet(&gContext, ClrBlack); GrRectFill(&gContext, &bg); DrawHeader("Joystick", btnLeft.isPressed(), btnRight.isPressed());
int cx = 64, cy = 75, radius = 40; GrContextForegroundSet(&gContext, ClrWhite); GrCircleDraw(&gContext, cx, cy, radius);
int px = cx + (int)(x * radius); int py = cy - (int)(y * radius); GrContextForegroundSet(&gContext, ClrCyan); GrCircleFill(&gContext, px, py, 3); GrFlush(&gContext);}6. Adding a New Screen
Section titled “6. Adding a New Screen”To add a new screen, follow these steps:
- Add a new enum entry in
ScreenID(e.g.,SCREEN_TEMP). - Create a new
Draw_Temp()function with the desired layout. - Update the
switch(currentScreen)block in the main loop to call it. - Optionally, define a sensor read function or data variable.
This modular approach allows each screen to have its own drawing and sensor logic without affecting others.
7. Expected Behavior
Section titled “7. Expected Behavior”| Input | Action |
|---|---|
| S1 | Moves to the previous screen, plays a low beep |
| S2 | Moves to the next screen, plays a high beep |
| Display | Updates every 50 ms with live sensor data |
8. Summary
Section titled “8. Summary”This multi-screen architecture provides a foundation for interactive embedded GUIs. You can easily extend it with additional modules, such as temperature sensors, Wi‑Fi info, or system diagnostics, following the same design pattern.
Author: Edwin R. Version 1.0