867 lines
31 KiB
Markdown
867 lines
31 KiB
Markdown
|
|
[tocstart]: # (toc start)
|
|
|
|
* [Introduction](#introduction)
|
|
* [MUI](#mui)
|
|
* [Graphical User Interface](#graphical-user-interface)
|
|
* [Minimal Example](#minimal-example)
|
|
* [Content (Minimal Example)](#content-minimal-example)
|
|
* [MUIMinimal.ino](#muiminimalino)
|
|
* [Includes and Constructors](#includes-and-constructors)
|
|
* [MUIF](#muif)
|
|
* [ Form Definition String (FDS)](#-form-definition-string-fds)
|
|
* [Arduino Setup](#arduino-setup)
|
|
* [Arduino Loop: Draw User Interface](#arduino-loop-draw-user-interface)
|
|
* [Arduino Loop: Map and Execute Events](#arduino-loop-map-and-execute-events)
|
|
* [Arduino Loop: Inactive User Interface](#arduino-loop-inactive-user-interface)
|
|
* [Minimal Example Summary](#minimal-example-summary)
|
|
* [MUICountDown Example](#muicountdown-example)
|
|
* [Content (Count Down)](#content-count-down)
|
|
* [MUICountDown.ino](#muicountdownino)
|
|
* [Event Mapping and Action Execution](#event-mapping-and-action-execution)
|
|
* [Field Processing Functions (MUIF)](#field-processing-functions-muif)
|
|
* [Form Definition String (FDS)](#form-definition-string-fds)
|
|
* [Leave and Enter the Menu System](#leave-and-enter-the-menu-system)
|
|
* [Stopwatch Example](#stopwatch-example)
|
|
* [Content (Stopwatch)](#content-stopwatch)
|
|
* [MUIStopwatch.ino](#muistopwatchino)
|
|
* [FDS](#fds)
|
|
* [Waveform Generator Example](#waveform-generator-example)
|
|
* [Content](#content)
|
|
* [Code ](#code-)
|
|
* [Array Editor for the Waveform](#array-editor-for-the-waveform)
|
|
* [Conclusion](#conclusion)
|
|
|
|
[tocend]: # (toc end)
|
|
|
|
# Introduction
|
|
|
|
## MUI
|
|
|
|
The monochrome minimal graphical user interface (MUI) is a small addon for u8g2 library.
|
|
The purpose is to create interactive user interfaces with u8g2.
|
|
|
|

|
|
|
|
|
|
Key features:
|
|
|
|
* Event Processing
|
|
* User interface drawing
|
|
* Many predefined user elements
|
|
* User interface definition in PROGMEM area for AVR systems
|
|
* Static menu definitions
|
|
* Small code size (4 to 8 KB)
|
|
* Low RAM (<0.5 KB)
|
|
* Optimized for monochrome displays
|
|
* MUI Reference: https://github.com/olikraus/u8g2/wiki/muiref
|
|
|
|
|
|
## Graphical User Interface
|
|
|
|
### Generic Approach
|
|
|
|
Steps to implement a graphical user interface::
|
|
|
|
1. Draw the user interface on a display.
|
|
2. Detect user input events (like a button press)
|
|
3. Map the user input event to a user interface action (like moving the focus or changing a value)
|
|
4. Execute the user interface action (and continue with 1.)
|
|
|
|
### User Interface vs. other Information
|
|
|
|
The user interface and your own display procedures
|
|
need to coexist. Both parts may share the same display:
|
|
|
|
1. Check if the user interface is active or inactive
|
|
2. If user interface is active: Draw UI, handle events, execute actions (see above)
|
|
3. If user interface is inactive: Draw custom code, check whether user wants to return to the menu system
|
|
|
|
It is also possible to place custom graphics into the user interface.
|
|
The [Waveform Generator](#waveform-generator-example) will show more details how user interface and custom graphics can be used together.
|
|
|
|
### Definitions
|
|
|
|
Definitions for some terms, used in this documentation (see also the more detailed definitions [here](muiref#definitions)).
|
|
|
|
* **Form**: A form contains multiple fields and is described in FDS.
|
|
* **Field**: A field is placed at a certain position on the form in FDS. Each field must have a corresponding MUIF entry in the MUIF list.
|
|
* **MUIF** The MUI Function declares properties of a field. A field on the form referes to a MUIF. All MUIF must be part of a MUIF list.
|
|
* **FDS** The "Form Definition String" defines all the forms with their fields.
|
|
|
|
# Minimal Example
|
|
|
|
## Content (Minimal Example)
|
|
* MUI setup for U8g2
|
|
* Preconditions for the user interface: MUIF list and FDS
|
|
* How to draw the menu
|
|
* How to process events
|
|
* How to deactivate the user interface
|
|
|
|
## MUIMinimal.ino
|
|
|
|
This is a minimal example for MUI together with U8g2.
|
|
The code will assume the dogs102 shield (http://shieldlist.org/controlconnection/dogs102).
|
|
|
|
[Full Arduino code of for this example](https://github.com/olikraus/u8g2/blob/master/sys/arduino/u8g2_page_buffer/MUIMinimal/MUIMinimal.ino)
|
|
|
|

|
|
|
|
|
|
``` C++
|
|
#include <Arduino.h>
|
|
#include <U8g2lib.h>
|
|
#include <MUIU8g2.h>
|
|
|
|
U8G2_UC1701_EA_DOGS102_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
|
|
MUIU8G2 mui;
|
|
|
|
muif_t muif_list[] = {
|
|
MUIF_VARIABLE("BN", NULL, mui_u8g2_btn_exit_wm_fi)
|
|
};
|
|
|
|
fds_t fds_data[] =
|
|
MUI_FORM(1)
|
|
MUI_XYT("BN", 64, 30, " Select Me ")
|
|
;
|
|
|
|
void setup(void) {
|
|
u8g2.begin(/* menu_select_pin= */ 5, /* menu_next_pin= */ 4, /* menu_prev_pin= */ 2, /* menu_up_pin= */ U8X8_PIN_NONE, /* menu_down_pin= */ U8X8_PIN_NONE, /* menu_home_pin= */ 3);
|
|
mui.begin(u8g2, fds_data, muif_list, sizeof(muif_list)/sizeof(muif_t));
|
|
mui.gotoForm(/* form_id= */ 1, /* initial_cursor_position= */ 0);
|
|
}
|
|
|
|
uint8_t is_redraw = 1;
|
|
void loop(void) {
|
|
u8g2.setFont(u8g2_font_helvR08_tr);
|
|
if ( mui.isFormActive() )
|
|
{
|
|
/* menu is active: draw the menu */
|
|
if ( is_redraw ) {
|
|
u8g2.firstPage();
|
|
do {
|
|
mui.draw();
|
|
} while( u8g2.nextPage() );
|
|
is_redraw = 0;
|
|
}
|
|
/* handle events */
|
|
switch(u8g2.getMenuEvent()) {
|
|
case U8X8_MSG_GPIO_MENU_SELECT:
|
|
mui.sendSelect();
|
|
is_redraw = 1;
|
|
break;
|
|
}
|
|
} else {
|
|
/* menu not active: show something else */
|
|
u8g2.firstPage();
|
|
do {
|
|
u8g2.setCursor(0,20);
|
|
u8g2.print(millis());
|
|
} while( u8g2.nextPage() );
|
|
} /* mui.isFormActive() */
|
|
} /* loop */
|
|
|
|
```
|
|
|
|
The following sections will discuss the code step by step.
|
|
|
|
## Includes and Constructors
|
|
|
|
``` C++
|
|
#include <Arduino.h>
|
|
#include <U8g2lib.h>
|
|
#include <MUIU8g2.h>
|
|
|
|
U8G2_UC1701_EA_DOGS102_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
|
|
MUIU8G2 mui;
|
|
```
|
|
|
|
After the usual includes create the u8g2 object and the new `mui` object.
|
|
The `mui` object will not receive any arguments. This is done later with the `.begin` function.
|
|
|
|
## MUIF
|
|
|
|
|
|
``` C++
|
|
muif_t muif_list[] = {
|
|
MUIF_VARIABLE("BN", NULL, mui_u8g2_btn_exit_wm_fi)
|
|
};
|
|
```
|
|
|
|
This is a list of all the MUIFs (field processing functions), which are required for the menu.
|
|
In this case there is only one MUIF,
|
|
because there will be only one element in the menu system.
|
|
|
|
Available MUIFs are listed in the MUIF [reference](muiref).
|
|
From left to right,
|
|
the arguments for the MUIF are:
|
|
|
|
* `"BN"`: A unique identifier. This MUST be a string with exactly two chars. This identifier
|
|
is later used in the field definitions (FDS).
|
|
* `NULL`: This is a pointer to a data value. `NULL` means, that there is no variable assigned
|
|
to this field.
|
|
* `mui_u8g2_btn_exit_wm_fi`: This is the callback function
|
|
for the field. It will render the button on the display and handle the select event
|
|
for the corresponding field. The name usually reflects the behaviour ("exit" = leave menu system)
|
|
and the visual representation ("fi" = "with frame, inverted text").
|
|
The [MUIF reference](muiref) will include all predefined callback functions.
|
|
|
|
Details for this MUIF can be found [here](https://github.com/olikraus/u8g2/wiki/muiref#exit-button).
|
|
|
|
It is possible to define custom MUIFs. Usually a new custom MUIF extends the functionality of an existing MUIFs.
|
|
The [Stopwatch Example](#stopwatch-example) and the [Waveform Generator Example](#waveform-generator-example) will discuss this in more detail.
|
|
|
|
|
|
## Form Definition String (FDS)
|
|
|
|
``` C++
|
|
fds_t fds_data[] =
|
|
MUI_FORM(1)
|
|
MUI_XYT("BN", 64, 30, " Select Me ")
|
|
;
|
|
```
|
|
|
|
The form definition string (FDS) defines all forms along with their fields. Each form must have a number between 1 and 255.
|
|
In this
|
|
case there is only one form with the number `1`. A form always starts with the
|
|
macro `MUI_FORM`.
|
|
|
|
The field is defined with the macro `MUI_XYT`, which requires the following
|
|
arguments:
|
|
* `"BN"`: The MUIF identifier for the MUIF callback which should render and handle this field. A MUIF with the "BN" keyword must exist in the MUIF list.
|
|
* `64`: The X reference position
|
|
* `30`: The Y reference position
|
|
* `" Select Me "`: A UTF8 string, which is rendered as button text on the display.
|
|
|
|
FDS and MUIF are connected with each other:
|
|
Each identifier in FDS (like `"BN"`) must also appear in the MUIF list. If the identifier is
|
|
used in FDS, but not found in the MUIF list, then the field will not appear on the form.
|
|
|
|
Note: The FDS internally is a string, so there must be exactly only one ';' at the end.
|
|
|
|
## Arduino Setup
|
|
|
|
``` C++
|
|
void setup(void) {
|
|
u8g2.begin(/* menu_select_pin= */ 5, /* menu_next_pin= */ 4, /* menu_prev_pin= */ 2, /* menu_up_pin= */ U8X8_PIN_NONE, /* menu_down_pin= */ U8X8_PIN_NONE, /* menu_home_pin= */ 3);
|
|
mui.begin(u8g2, fds_data, muif_list, sizeof(muif_list)/sizeof(muif_t));
|
|
mui.gotoForm(/* form_id= */ 1, /* initial_cursor_position= */ 0);
|
|
}
|
|
```
|
|
|
|
In this case U8g2 is configured to check button events from the DOGS102 shield.
|
|
However any other button library can also be used.
|
|
|
|
In the second line, the mui object is connected to u8g2 object, FDS list and MUIF list. The last
|
|
argument of the mui begin statement is the number of entries in the MUIF list.
|
|
This is calculated by the compiler with help of the `sizeof` operator: `sizeof(muif_list)/sizeof(muif_t))`.
|
|
|
|
The last statement (`mui.gotoForm`) will setup the menu system and
|
|
jump to form `1` as initial menu form. Obviously the form with the number `1` should exist in FDS (`MUI_FORM(1)`)
|
|
otherwise `mui.gotoForm` will fail and the menu is not activated.
|
|
|
|
The focus cursor will be placed on the first interactive field (`initial_cursor_position`).
|
|
|
|
## Arduino Loop: Draw User Interface
|
|
|
|
``` C++
|
|
uint8_t is_redraw = 1; // indicates whether the menu should be drawn
|
|
void loop(void) {
|
|
u8g2.setFont(u8g2_font_helvR08_tr);
|
|
if ( mui.isFormActive() )
|
|
{
|
|
/* menu is active: draw the menu */
|
|
if ( is_redraw ) { // is any redraw of the menu required?
|
|
u8g2.firstPage();
|
|
do {
|
|
mui.draw();
|
|
} while( u8g2.nextPage() );
|
|
is_redraw = 0; // menu is now up to date, no redraw required at the moment
|
|
}
|
|
```
|
|
|
|
The `is_redraw` variable will be used to avoid unrequired redraws. Drawing the menu
|
|
will happen only if `is_redraw` is not 0 (which is the initally the case, because `1` is
|
|
assigned as default value)
|
|
|
|
The menu system is also active because of the previous `mui.gotoForm` statement:
|
|
`mui.isFormActive()` will return true.
|
|
|
|
`mui.draw()` is called to draw the complete current state of the user interface and finally
|
|
the `is_redraw` is cleared to ensure that the menu is not rendered again without change
|
|
in the user interface.
|
|
|
|
## Arduino Loop: Map and Execute Events
|
|
|
|
``` C++
|
|
/* handle events */
|
|
switch(u8g2.getMenuEvent()) {
|
|
case U8X8_MSG_GPIO_MENU_SELECT:
|
|
mui.sendSelect();
|
|
is_redraw = 1; // The select statement will change something: Enforce a redraw
|
|
break;
|
|
}
|
|
```
|
|
|
|
After drawing the user interface the code will check for any user button press events.
|
|
If the select button is pressed, then the select action (`mui.sendSelect()`) will be executed.
|
|
|
|
Usually a user interface has to check and act on multiple events, but for this
|
|
minimal example only one event is sufficient.
|
|
|
|
Once the event has been detected, the `is_redraw` flag is set to enforce a redraw of the
|
|
user interface.
|
|
|
|
## Arduino Loop: Inactive User Interface
|
|
|
|
``` C++
|
|
} else {
|
|
/* menu not active: show something else */
|
|
u8g2.firstPage();
|
|
do {
|
|
u8g2.setCursor(0,20);
|
|
u8g2.print(millis());
|
|
} while( u8g2.nextPage() );
|
|
} /* mui.isFormActive() */
|
|
```
|
|
|
|
The `Select Me` button will call the `mui_u8g2_btn_exit_wm_fi`
|
|
function. If `mui_u8g2_btn_exit_wm_fi` receives a select
|
|
action, then the user interface menu will be deactivated. `isFormActive` will return
|
|
false and the program will execute the above code.
|
|
|
|
This code will just display the current `millis()` forever (until next reset).
|
|
|
|
## Minimal Example Summary
|
|
|
|
This completes the minimal MUI example. The following topics had been covered:
|
|
* How to define a MUIF (field function) list
|
|
* How to define a form with a single field
|
|
* How to connect MUI to U8g2
|
|
* How to display an initial form
|
|
* How to draw the form (user interface) and how to handle user input events
|
|
|
|
The following topics are not covered or may require more details:
|
|
* How to handle sufficient user input events for a usefull user interface
|
|
* How to build a more complex menu system
|
|
* How to connect and exchange data between the user interface and the rest of your code
|
|
* How to define a custom callback function for a MUIF entry
|
|
|
|
|
|
# MUICountDown Example
|
|
|
|
## Content (Count Down)
|
|
* More details on user interface events
|
|
* Using the style feature
|
|
* More MUIF examples
|
|
* How to re-enter the menu system
|
|
|
|
## MUICountDown.ino
|
|
|
|
The code examples in the following sections are taken from the "MUICountDown" example.
|
|
|
|
[Full Arduino code for this example](https://github.com/olikraus/u8g2/blob/master/sys/arduino/u8g2_page_buffer/MUICountDown/MUICountDown.ino)
|
|
|
|

|
|
|
|
|
|
## Event Mapping and Action Execution
|
|
|
|
``` C++
|
|
/* handle events */
|
|
switch(u8g2.getMenuEvent()) {
|
|
case U8X8_MSG_GPIO_MENU_SELECT:
|
|
mui.sendSelect();
|
|
is_redraw = 1;
|
|
break;
|
|
case U8X8_MSG_GPIO_MENU_NEXT:
|
|
mui.nextField();
|
|
is_redraw = 1;
|
|
break;
|
|
case U8X8_MSG_GPIO_MENU_PREV:
|
|
mui.prevField();
|
|
is_redraw = 1;
|
|
break;
|
|
}
|
|
```
|
|
|
|
The user can jump from one interactive field to the next/previous interactive field with
|
|
the `mui.nextField()`/`mui.prevField()` function. An interactive field is activated by
|
|
sending the select action (`mui.sendSelect()`).
|
|
|
|
Additional buttons could be used to jump to a specific form
|
|
by using `mui.gotoForm()`.
|
|
|
|
## Field Processing Functions (MUIF)
|
|
|
|
``` C++
|
|
muif_t muif_list[] MUI_PROGMEM = {
|
|
/* normal text style */
|
|
MUIF_U8G2_FONT_STYLE(0, u8g2_font_helvR08_tr),
|
|
|
|
/* Leave the menu system */
|
|
MUIF_VARIABLE("LV",&exit_code,mui_u8g2_btn_exit_wm_fi),
|
|
|
|
/* input for a number between 0 to 9 */
|
|
MUIF_U8G2_U8_MIN_MAX("IN", &number_input, 0, 9, mui_u8g2_u8_min_max_wm_mse_pi),
|
|
|
|
/* MUI_LABEL is used to place fixed text on the screeen */
|
|
MUIF_LABEL(mui_u8g2_draw_text)
|
|
};
|
|
```
|
|
|
|
The MUICountDown example contains several MUIF entries.
|
|
MUIF entries are connected with a field macro inside FDS if both entries share the same identifier (two-char or number, see table).
|
|
|
|
| MUIF | FDS Macro | Identifier |
|
|
|---|---|---|
|
|
| MUIF_U8G2_FONT_STYLE | MUI_STYLE | single number, no double quotes |
|
|
| MUIF_VARIABLE | MUI_XYT | two chars in double quotes |
|
|
| MUIF_U8G2_U8_MIN_MAX | MUI_XY | two chars in double quotes |
|
|
| MUIF_LABEL | MUI_LABEL | no identifier required |
|
|
|
|
|
|
## Form Definition String (FDS)
|
|
|
|
``` C++
|
|
fds_t fds_data[] MUI_PROGMEM =
|
|
MUI_FORM(1)
|
|
MUI_STYLE(0) // select "u8g2_font_helvR08_tr" (see MUIF above)
|
|
MUI_LABEL(5,12, "Countdown Time")
|
|
MUI_LABEL(5,30, "Seconds:")
|
|
MUI_XY("IN",60, 30)
|
|
MUI_XYT("LV",64, 59, " OK ")
|
|
;
|
|
```
|
|
The form definition string defines all the forms. One form describes the position of each field on the display:
|
|
|
|

|
|
|
|
* A form always starts with the `MUI_FORM()` command. Each form must have a unique number (here: 1).
|
|
* Each form should be followed by `MUI_STYLE()` command, which defines a suitable font for the following commands.
|
|
* The `MUI_LABEL` will place a read only text at the specified postion.
|
|
|
|
## Leave and Enter the Menu System
|
|
|
|
The function `mui.isFormActive()` checks whether there is an active form (menu). The arduino loop should look like this:
|
|
|
|
``` C++
|
|
void loop(void) {
|
|
if ( mui.isFormActive() ) {
|
|
// draw the menu and handle user events
|
|
} else {
|
|
// display your own content, do something else
|
|
}
|
|
```
|
|
|
|
The function `mui.leaveForm` will close the current form.
|
|
The "LV" button (`mui_u8g2_btn_exit_wm_fi` MUIF) will call `mui.leaveForm` to close the current form and the complete menu system.
|
|
As a result, `mui.isFormActive()` will return false.
|
|
However: The content on the display is not modified and probably still displays the last form. It is the responsibility of the user code to clear the display.
|
|
This could be done by checking whether the form is inactive after handling all the user events:
|
|
|
|
``` C++
|
|
void loop(void) {
|
|
if ( mui.isFormActive() ) {
|
|
// draw the menu and handle user events
|
|
if ( mui.isFormActive() == false ) {
|
|
// clear display
|
|
}
|
|
} else {
|
|
// display your own content, do something else
|
|
}
|
|
```
|
|
|
|
Jumping back to the menu system is simple: Just call `mui.gotoForm()`. `mui.isFormActive()` will return true after successful call to `mui.gotoForm()`.
|
|
|
|
``` C++
|
|
void loop(void) {
|
|
if ( mui.isFormActive() ) {
|
|
// draw the menu and handle user events
|
|
if ( mui.isFormActive() == false ) {
|
|
// clear display
|
|
}
|
|
} else {
|
|
// display your own content, do something else
|
|
if ( all_is_done )
|
|
mui.gotoForm(/* form_id= */ 1, /* initial_cursor_position= */ 0);
|
|
}
|
|
```
|
|
|
|
# Stopwatch Example
|
|
|
|
## Content (Stopwatch)
|
|
* How to display custom data on a form
|
|
* Form change events
|
|
* Custom MUI functions
|
|
|
|
## MUIStopwatch.ino
|
|
|
|
The code examples in the following sections are taken from the "MUIStopwatch" example.
|
|
|
|
[Full Arduino code for this example](https://github.com/olikraus/u8g2/blob/master/sys/arduino/u8g2_page_buffer/MUIStopwatch/MUIStopwatch.ino)
|
|
|
|
Implementation of the Stopwatch:
|
|
* Two forms: Form 1 for the stopped state of the timer and form 2 for the running timer
|
|
* Transition from form 1 to 2: Start the timer
|
|
* Transition from form 2 to 1: Stop the timer
|
|
|
|
## FDS
|
|
|
|
```
|
|
fds_t fds_data[] =
|
|
MUI_FORM(1)
|
|
MUI_AUX("SO") // this will stop the stop watch time once this form is entered
|
|
MUI_STYLE(0)
|
|
MUI_LABEL(5,12, "Stopwatch")
|
|
MUI_XY("CT", 5, 24)
|
|
MUI_XYAT("GO",20, 36, 2, " Start ") // jump to the second form to start the timer
|
|
|
|
MUI_FORM(2)
|
|
MUI_AUX("ST") // this will start the stop watch time once this form is entered
|
|
MUI_STYLE(0)
|
|
MUI_LABEL(5,12, "Stopwatch")
|
|
MUI_XY("CT", 5, 24)
|
|
MUI_XYAT("GO",20, 36, 1, " Stop ") // jump to the first form to stop the timer
|
|
;
|
|
```
|
|
|
|
There are three custom fields to implement the stopwatch:
|
|
* `MUI_XY("CT", 5, 24)`: A field which will draw the current stopwatch timer.
|
|
* `MUI_AUX("SO")`: Invisible field which will receive a form change event to stop the timer.
|
|
* `MUI_AUX("ST")`: Invisible field which will receive a form change event to start the timer.
|
|
|
|
### MUIF
|
|
|
|
The MUIF callback will receive several messages. A custom code can react on each message.
|
|
|
|
The most important messages are:
|
|
| Message | Description | Return Value |
|
|
|---|---|---|
|
|
| `MUIF_MSG_DRAW` | The field content should be drawn on the screen | 0 |
|
|
| `MUIF_MSG_FORM_START` | A new form is entered. | 0 |
|
|
| `MUIF_MSG_FORM_END` | A current form is closed. | 0 |
|
|
| `MUIF_MSG_CURSOR_SELECT` | Active fields only: "Select" button was pressed by the user. | 0 |
|
|
|
|
Here is the code, which does the timer start, stop and visualization:
|
|
|
|
``` C++
|
|
long stop_watch_timer = 0; // stop watch timer 1/100 seconds
|
|
long stop_watch_millis = 0; // millis() value, when the stop watch was started
|
|
uint8_t is_stop_watch_running = 1; // defines the current state of the stop watch: running or not running
|
|
|
|
/* draw the current stop watch value */
|
|
uint8_t mui_draw_current_timer(mui_t *ui, uint8_t msg) {
|
|
if ( msg == MUIF_MSG_DRAW ) {
|
|
u8g2.setCursor(mui_get_x(ui), mui_get_y(ui));
|
|
u8g2.print(stop_watch_timer/1000);
|
|
u8g2.print(".");
|
|
u8g2.print((stop_watch_timer/10)%100);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* start the stop watch */
|
|
uint8_t mui_start_current_timer(mui_t *ui, uint8_t msg) {
|
|
if ( msg == MUIF_MSG_FORM_START ) {
|
|
is_stop_watch_running = 1;
|
|
stop_watch_millis = millis();
|
|
stop_watch_timer = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* stop the stop watch timer */
|
|
uint8_t mui_stop_current_timer(mui_t *ui, uint8_t msg) {
|
|
if ( msg == MUIF_MSG_FORM_START )
|
|
is_stop_watch_running = 0;
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
The above custom callbacks are then registered in the MUIF table:
|
|
|
|
```
|
|
muif_t muif_list[] = {
|
|
/* normal text style */
|
|
MUIF_U8G2_FONT_STYLE(0, u8g2_font_helvR08_tr),
|
|
|
|
/* custom MUIF callback to draw the timer value */
|
|
MUIF_RO("CT", mui_draw_current_timer),
|
|
|
|
/* custom MUIF callback to start the stop watch timer */
|
|
MUIF_RO("ST", mui_start_current_timer),
|
|
|
|
/* custom MUIF callback to end the stop watch timer */
|
|
MUIF_RO("SO", mui_stop_current_timer),
|
|
|
|
/* a button for the menu... */
|
|
MUIF_BUTTON("GO", mui_u8g2_btn_goto_wm_fi),
|
|
|
|
/* MUI_LABEL is used to place fixed text on the screeen */
|
|
MUIF_LABEL(mui_u8g2_draw_text)
|
|
};
|
|
```
|
|
|
|
`MUIF_RO` (RO=read only) registers a simple function, which does not get any further data, but receives all the messages.
|
|
The above registered functions `mui_start_current_timer` and `mui_stop_current_timer` are placed as hidden fields on the form with `MUI_AUX()`.
|
|
`MUI_XY` is used for `mui_draw_current_timer` to provide the `x` and `y` values for `mui_draw_current_timer`.
|
|
|
|
The main loop for this example is a little bit unusual: It will force a permanent screen update once the stopwatch timer runs.
|
|
|
|
``` C++
|
|
void loop(void) {
|
|
|
|
/* check whether the menu is active */
|
|
if ( mui.isFormActive() ) {
|
|
/* if so, then draw the menu */
|
|
if ( is_redraw ) {
|
|
u8g2.firstPage();
|
|
do {
|
|
mui.draw();
|
|
} while( u8g2.nextPage() );
|
|
is_redraw = 0;
|
|
}
|
|
|
|
/* handle events */
|
|
switch(u8g2.getMenuEvent()) {
|
|
...
|
|
}
|
|
|
|
/* update the stop watch timer */
|
|
if ( is_stop_watch_running != 0 ) {
|
|
stop_watch_timer = millis() - stop_watch_millis;
|
|
is_redraw = 1; // always redraw the screen so that the timer gets updated
|
|
}
|
|
|
|
} else {
|
|
/* the menu should never become inactive, but if so, then restart the menu system */
|
|
mui.gotoForm(/* form_id= */ 1, /* initial_cursor_position= */ 0);
|
|
}
|
|
}
|
|
```
|
|
|
|
# Waveform Generator Example
|
|
|
|
## Content
|
|
* How to update a data array
|
|
* Custom MUIF for drawing custom data
|
|
* Custom MUIF to control the current array element
|
|
|
|
## Code
|
|
|
|

|
|
|
|
* [Full Arduino code MUIWaveformSimpleRotary.ino](https://github.com/olikraus/u8g2/blob/master/sys/arduino/u8g2_page_buffer/MUIWaveformSimpleRotary/MUIWaveformSimpleRotary.ino)
|
|
* [Full Arduino code MUIWaveformBounce2.ino](https://github.com/olikraus/u8g2/blob/master/sys/arduino/u8g2_page_buffer/MUIWaveformBounce2/MUIWaveformBounce2.ino)
|
|
|
|
### Waveform Array
|
|
|
|
The signal waveform has four parts. Each part has a duration and a signal height:
|
|
``` C++
|
|
struct array_element_struct
|
|
{
|
|
uint8_t value;
|
|
uint8_t time;
|
|
};
|
|
|
|
/* array list */
|
|
|
|
#define WAVEFORM_ELEMENT_CNT 4
|
|
struct array_element_struct waveform_array[WAVEFORM_ELEMENT_CNT];
|
|
```
|
|
|
|
MUI does not offer a scrollable table which would allow a 2x4 matrix like editing.
|
|
Instead the idea is to introduce a "current-element-position" (`array_edit_pos`) and allow the user to modify the current element (see the animation above).
|
|
For the user update, the current element is copied from the array into a local editable element (`array_edit_element`). After the edit operation, the content of the
|
|
edit element (`array_edit_element`) is copied back into the array.
|
|
``` C++
|
|
/* array editable local copy */
|
|
|
|
volatile uint8_t array_edit_pos = 0; // "volatile" might not be required, but still; array_edit_pos is modified by MUI callbacks and used in the extended MUIF
|
|
struct array_element_struct array_edit_element;
|
|
```
|
|
|
|
### Waveform Output
|
|
|
|
This example can output the waveform to a LED. If the LED is connected to a PWM output, then the brightness of the LED will change according to the defined waveform:
|
|
``` C++
|
|
if ( is_redraw == 0 && mui.getCurrentFormId() == 2 )
|
|
{
|
|
...
|
|
analogWrite(LED_PIN, current_value*15); // This will only work, if the output pin for the LED supports PWM
|
|
delay(10);
|
|
is_redraw = 1; /* keep doing a redraw, because we want some animation */
|
|
}
|
|
```
|
|
|
|
There is also a custom MUIF, which will show the waveform during the waveform output to the LED:
|
|
``` C++
|
|
uint8_t muif_waveform_show(mui_t *ui, uint8_t msg)
|
|
{
|
|
switch(msg)
|
|
{
|
|
case MUIF_MSG_DRAW:
|
|
current_value = waveform_array_draw(mui_get_x(ui), mui_get_y(ui), WAVEFORM_ELEMENT_CNT, 255, current_time);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
```
|
|
The above code will place the waveform at the position defined with the FDS (`mui_get_x(ui)` and `mui_get_y(ui)`).
|
|
|
|
This MUIF is very simple and will only process the `MUIF_MSG_DRAW` event. It is connected to the keyword "WS" with a simple "read-only" macro in the MUIF list:
|
|
``` C++
|
|
MUIF_RO("WS", muif_waveform_show)
|
|
```
|
|
|
|
The waveform is now available with key "WS" and is placed at position 54/62 in form 2:
|
|
|
|
``` C++
|
|
MUI_FORM(2)
|
|
MUI_STYLE(0)
|
|
|
|
MUI_XYAT("GO", 63, 28, 1, "Stop")
|
|
|
|
MUI_XY("HL", 0, 45)
|
|
MUI_LABEL(3,60, "Waveform")
|
|
MUI_XY("WS", 54, 62)
|
|
```
|
|
|
|
## Array Editor for the Waveform
|
|
|
|
### Update a single Array Element
|
|
|
|
As mentioned above, there is an extra object to hold the data for the user update.
|
|
This data is copied from the array and later stored back. The connection of that extra object to MUI is simple:
|
|
|
|
``` C++
|
|
/* array editable local copy */
|
|
|
|
struct array_element_struct array_edit_element;
|
|
...
|
|
|
|
muif_t muif_list[] MUI_PROGMEM = {
|
|
...
|
|
MUIF_U8G2_U8_MIN_MAX("AV", &array_edit_element.value, 0, ARRAY_VALUE_MAX, mui_u8g2_u8_min_max_wm_mud_pi),
|
|
MUIF_U8G2_U8_MIN_MAX("AT", &array_edit_element.time, 0, ARRAY_TIME_MAX, mui_u8g2_u8_min_max_wm_mud_pi),
|
|
...
|
|
}
|
|
```
|
|
|
|
In this case the standard MUI macros for U8 are good enough for the update of the element member values.
|
|
The editbale fields can now be placed at any position inside the FDS by using the keywords "AV" and "AT".
|
|
|
|
### Update the current Array Position
|
|
|
|
The current array position is just a U8 (from 0 to 3). It can be put into the MUIF list with the `MUIF_U8G2_U8_MIN_MAX` macro.
|
|
However the desired field function (`mui_u8g2_u8_min_max_wm_mud_pi` in this example) has to be extended to support the copying of the data between array and local editable extra object:
|
|
* When the form is entered: Copy the data from the current array element to the extra object.
|
|
* When the array position is about to change: Copy the data from the extra object back to the array.
|
|
* When the array position has been changed: Copy the data from the new array position to the extra object.
|
|
* When the form is closed: Copy the data from the extra object back to the array.
|
|
|
|
The following messages will be checked:
|
|
| Message | Description | Return Value |
|
|
|---|---|---|
|
|
| `MUIF_MSG_FORM_START` | A new form is entered. | 0 |
|
|
| `MUIF_MSG_FORM_END` | A current form is closed. | 0 |
|
|
| `MUIF_MSG_CURSOR_SELECT` | Active fields only: "Select" button was pressed by the user. | 0 |
|
|
| `MUIF_MSG_EVENT_NEXT` | Rotary Encoder right rotation | depends on mud state |
|
|
| `MUIF_MSG_EVENT_PREV` | Rotary Encoder left rotation | depends on mud state|
|
|
|
|
Checking for `MUIF_MSG_EVENT_NEXT` and `MUIF_MSG_EVENT_PREV` is required only for MUD mode (which is usually used together with a rotary encoder).
|
|
The below example code for the rotary encoder will indeed use the MUD version of the U8 field, so we need to check for the EVENT messages:
|
|
``` C++
|
|
uint8_t muif_array_edit_pos(mui_t *ui, uint8_t msg)
|
|
{
|
|
uint8_t return_value = 0;
|
|
switch(msg)
|
|
{
|
|
case MUIF_MSG_FORM_START:
|
|
array_edit_element = waveform_array[array_edit_pos]; // copy array element to the local editable copy
|
|
return_value = mui_u8g2_u8_min_max_wm_mud_pi(ui, msg); // call the original MUIF
|
|
break;
|
|
case MUIF_MSG_FORM_END:
|
|
return_value = mui_u8g2_u8_min_max_wm_mud_pi(ui, msg); // finalise the form
|
|
waveform_array[array_edit_pos] = array_edit_element ; // store the current elements in the array before leaving the form
|
|
break;
|
|
case MUIF_MSG_CURSOR_SELECT: // for mse mode
|
|
case MUIF_MSG_EVENT_NEXT: // for mud mode
|
|
case MUIF_MSG_EVENT_PREV: // for mud mode
|
|
waveform_array[array_edit_pos] = array_edit_element; // store the modified local copy back to the array
|
|
return_value = mui_u8g2_u8_min_max_wm_mud_pi(ui, msg); // let MUI modify the current array position
|
|
array_edit_element = waveform_array[array_edit_pos] ; // load the new element from the array to the local editable copy
|
|
break;
|
|
default:
|
|
return_value = mui_u8g2_u8_min_max_wm_mud_pi(ui, msg); // for any other messages, just call the original MUIF
|
|
}
|
|
return return_value;
|
|
}
|
|
```
|
|
|
|
The return value for this custom MUIF is just taken from the calls to `mui_u8g2_u8_min_max_wm_mud_pi()`.
|
|
The code of the MUIF should be almost clear:
|
|
When entering the new form, the element is taken from the array:
|
|
``` C++
|
|
array_edit_element = waveform_array[array_edit_pos]; // copy local array element to the local editable copy
|
|
```
|
|
When leaving the form, the data is stored back into the array:
|
|
``` C++
|
|
waveform_array[array_edit_pos] = array_edit_element; // store the local editable copy into the array
|
|
```
|
|
Whenever the user changes the current array position: Store and load the data:
|
|
``` C++
|
|
waveform_array[array_edit_pos] = array_edit_element; // store the modified local copy back to the array
|
|
return_value = mui_u8g2_u8_min_max_wm_mud_pi(ui, msg); // let MUI modify the current array position
|
|
array_edit_element = waveform_array[array_edit_pos] ; // load the new element from the array to the local editable copy
|
|
```
|
|
|
|
This custom function can be now placed in the MUIF list ("AP" keyword):
|
|
``` C++
|
|
muif_t muif_list[] MUI_PROGMEM = {
|
|
MUIF_U8G2_LABEL(),
|
|
MUIF_U8G2_FONT_STYLE(0, u8g2_font_helvR08_tr),
|
|
|
|
MUIF_BUTTON("GO", mui_u8g2_btn_goto_wm_fi),
|
|
MUIF_RO("WS", muif_waveform_show),
|
|
MUIF_RO("HL", mui_hline),
|
|
|
|
/* waveform editor */
|
|
|
|
MUIF_RO("DE", muif_waveform_editor_decoration),
|
|
MUIF_RO("AD", muif_waveform_draw),
|
|
MUIF_U8G2_U8_MIN_MAX("AP", (uint8_t *)&array_edit_pos, 0, WAVEFORM_ELEMENT_CNT-1, muif_array_edit_pos), // the pointer cast avoids warning because of the volatile keyword
|
|
MUIF_U8G2_U8_MIN_MAX("AV", &array_edit_element.value, 0, ARRAY_VALUE_MAX, mui_u8g2_u8_min_max_wm_mud_pi),
|
|
MUIF_U8G2_U8_MIN_MAX("AT", &array_edit_element.time, 0, ARRAY_TIME_MAX, mui_u8g2_u8_min_max_wm_mud_pi),
|
|
MUIF_BUTTON("AG", mui_u8g2_btn_back_wm_fi)
|
|
};
|
|
```
|
|
|
|
The final form will include some additional decoration and also has a "Done" button:
|
|
|
|
``` C++
|
|
MUI_FORM(20)
|
|
MUI_STYLE(0)
|
|
MUI_AUX("DE")
|
|
MUI_LABEL(3,15, "Pos:")
|
|
MUI_XY("AP", 30, 15)
|
|
|
|
MUI_LABEL(54,10, "Value:")
|
|
MUI_XY("AV", 100, 10)
|
|
|
|
MUI_LABEL(54,20, "Time:")
|
|
MUI_XY("AT", 100, 20)
|
|
|
|
MUI_LABEL(3,39, "Waveform")
|
|
MUI_XY("AD", 54, 41)
|
|
|
|
MUI_XYAT("AG", 63, 59, 1, "Done")
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
* There is no scrollable list in MUI, instead use a "current position" and let the user modify each element of the list.
|
|
* MUIFs can be extended with extra functionality to copy data from and to a list.
|
|
|