Wio Terminal GUI Menu Example

After spending a fair bit of time going through examples, I’ve assembled a very basic example that may save someone else some trouble. This is somewhat tailored to my specific application but could surely be adapted. Looking forward to additional examples including full functionality with ArduPy…espeically with interrupts.

 * TODO: 
 * How to store values or struct (look at code example from Seeed? (Add Save Button for parameters)
 * Change font? Not sure this is compatible with ArduinoMenu
 * Uses most of the buttons on the WioTerminal and maps them to specific functions. 
 * Requires the ArduinoMenu library https://github.com/neu-rah/ArduinoMenu
 * And optionally the SAMD51_InterruptTimer.h library. The latter is for a specific
 * function designed for this project.  Feel free to comment out if needed. 

#include <Arduino.h>

#include "SAMD51_InterruptTimer.h"

#include <TFT_eSPI.h>
#include <SPI.h>//ultimately used for interface to AD9850 (code not included).

#include <menu.h>
#include <menuIO/serialIO.h>
#include <menuIO/TFT_eSPIOut.h>
#include <menuIO/altKeyIn.h>
#include <menuIO/chainStream.h>
using namespace Menu;

TFT_eSPI gfx;

result doAlert(eventMask e, prompt &item);

int test=55; //not used...legacy
int pwmFreq = 2000;

//Values in microseconds
long delayMin = 0;
long delayMax = 10000000;
long startDelay = 0;
long endDelay = 100000;
long delayStepLarge = 1000;
long delayStepSmall = 1;
long delayShift = 50;

int ledCtrl=LOW;

#define BTN_SEL WIO_5S_PRESS  // Select button
#define BTN_UP WIO_5S_UP // Up
#define BTN_DOWN WIO_5S_DOWN // Down

// the following variables are unsigned longs because the time, measured in
// microseconds, will quickly become a bigger number than can be stored in an int.
volatile unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 500000;    // the debounce time; increase if the output flickers

volatile bool toggleOut = true;
volatile bool outState = true;
int outPin = D7;//output pin for ISR

void delayISR(){
  toggleOut = not(toggleOut);
  outState = not(outState);

result myLedOn() {
  return proceed;
result myLedOff() {
  return proceed;

result updateAction(eventMask e,navNode& nav, prompt &item) {
  Serial.print("updateAction event: ");
  Serial.println(", proceed menu");
  gfx.drawEllipse(280, 200, 10, 10, random(0xFFFF));
  return proceed;

TOGGLE(ledCtrl,setLed,"Led: ",doNothing,noEvent,noStyle//,doExit,enterEvent,noStyle

int clockToggle=0;
SELECT(clockToggle,clockMenu,"Clock Out:",doNothing,noEvent,noStyle
//  ,VALUE("Two",2,doNothing,noEvent)

int selTest=0;
SELECT(selTest,selMenu,"Delay Shift",doNothing,noEvent,noStyle
//  ,VALUE("Two",2,doNothing,noEvent)

int chooseTest=-1;//Not used but left as an example.

//Not used but left as an example.
//custom field print
//implementing a customized menu component
//this numeric field prints formatted number with leading zeros
template<typename T>
class leadsField:public menuField<T> {
  using menuField<T>::menuField;
  Used printTo(navRoot &root,bool sel,menuOut& out, idx_t idx,idx_t len,idx_t panelNr=0) override {
    bool ed=this==root.navFocus;
    out.print((root.navFocus==this&&sel)?(menuField<T>::tunning?'>':':'):' ');
    char buffer[]="      ";
    sprintf(buffer, "%03d", menuField<T>::reflex);
    return len;

//Not used but left as an example.
//customizing a prompt look!
//by extending the prompt class
class altPrompt:public prompt {
  altPrompt(constMEM promptShadow& p):prompt(p) {}
  Used printTo(navRoot &root,bool sel,menuOut& out, idx_t idx,idx_t len,idx_t) override {
    return out.printRaw(F("special prompt!"),len);;

//Not used but left as an example.
const char* constMEM hexDigit MEMMODE="0123456789ABCDEF";
const char* constMEM hexNr[] MEMMODE={"0","x",hexDigit,hexDigit};
char buf1[]="0x11";

result confirmFreq() {
  gfx.fillCircle(200, 25, 5, TFT_CYAN);
  Serial.println("Freq Confirm");

//Not used but left as an example.
MENU(ledMenu,"LED Menu",doNothing,noEvent,noStyle
  ,OP("LED On",myLedOn,enterEvent)
  ,OP("LED Off",myLedOff,enterEvent)

MENU(subMenu,"Timing Delays",doNothing,noEvent,noStyle
  ,FIELD(startDelay,"Start Delay: "," us",delayMin, delayMax, delayStepLarge, delayStepSmall,doNothing,exitEvent,wrapStyle)
  ,FIELD(endDelay,"End Delay: "," us", delayMin, delayMax, delayStepLarge, delayStepSmall,doNothing,exitEvent,wrapStyle)
  ,FIELD(delayShift,"Delay Step: "," us", delayMin, delayMax,10, delayStepSmall,doNothing,exitEvent,wrapStyle)

//Main Menu
MENU(mainMenu,"Timing Control",doNothing,noEvent,wrapStyle
  ,FIELD(pwmFreq,"Freq: "," Hz",25,20000,1000,25,updateAction,exitEvent,wrapStyle)//once things have been entered and we exit from that menu item, call handler
//  ,SUBMENU(setLed)
//  ,SUBMENU(ledMenu)
//  ,OP("Reset",doNothing,noEvent)
  ,EXIT("Pause Menu")

// define menu colors --------------------------------------------------------
//  {{disabled normal,disabled selected},{enabled normal,enabled selected, enabled editing}}
//monochromatic color table

#define Black RGB565(0,0,0)
#define Red  RGB565(255,0,0)
#define Green RGB565(0,255,0)
#define Blue RGB565(0,0,255)
#define Gray RGB565(128,128,128)
#define LighterRed RGB565(255,150,150)
#define LighterGreen RGB565(150,255,150)
#define LighterBlue RGB565(150,150,255)
#define DarkerRed RGB565(150,0,0)
#define DarkerGreen RGB565(0,150,0)
#define DarkerBlue RGB565(0,0,150)
#define Cyan RGB565(0,255,255)
#define Magenta RGB565(255,0,255)
#define Yellow RGB565(255,255,0)
#define White RGB565(255,255,255)

const colorDef<uint16_t> colors[6] MEMMODE={
  {{(uint16_t)Black,(uint16_t)Black}, {(uint16_t)Black, (uint16_t)LighterBlue,  (uint16_t)Blue}},//bgColor
  {{(uint16_t)Gray, (uint16_t)Gray},  {(uint16_t)White, (uint16_t)White, (uint16_t)White}},//fgColor
  {{(uint16_t)White,(uint16_t)Black}, {(uint16_t)Yellow,(uint16_t)Yellow,(uint16_t)Red}},//valColor
  {{(uint16_t)White,(uint16_t)Black}, {(uint16_t)White, (uint16_t)Yellow,(uint16_t)Yellow}},//unitColor
  {{(uint16_t)White,(uint16_t)Gray},  {(uint16_t)Black, (uint16_t)Blue,  (uint16_t)White}},//cursorColor
  {{(uint16_t)White,(uint16_t)Yellow},{(uint16_t)Black,  (uint16_t)Red,   (uint16_t)Red}},//titleColor

#define MAX_DEPTH 4

//Notice button order is reversed. Oddly for some menu selecitons the +/- directions appear reversed. 
//Not sure how to fix but functional.

keyMap joystickBtn_map[]={
 {BTN_SEL, defaultNavCodes[enterCmd].ch,INPUT_PULLUP} ,
 {BTN_UP, defaultNavCodes[downCmd].ch,INPUT_PULLUP} ,
 {BTN_DOWN, defaultNavCodes[upCmd].ch,INPUT_PULLUP}  ,
keyIn<3> joystickBtns(joystickBtn_map);

serialIn serial(Serial);
menuIn* inputsList[]={&joystickBtns,&serial};
chainStream<2> in(inputsList);//3 is the number of inputs

//MENU_INPUTS(in,&serial); //When there are not buttons input is single, no need to `chainStream`

//define serial output device
idx_t serialTops[MAX_DEPTH]={0};
serialOut outSerial(Serial,serialTops);

//Screen Size
#define GFX_WIDTH 320
#define GFX_HEIGHT 240
#define fontW 16
#define fontH 20

//Shift over so all components can be seen clearly
const panel panels[] MEMMODE = {{1, 1, GFX_WIDTH / fontW, GFX_HEIGHT / fontH}};
navNode* nodes[sizeof(panels) / sizeof(panel)]; //navNodes to store navigation status
panelsList pList(panels, nodes, 1); //a list of panels and nodes
idx_t eSpiTops[MAX_DEPTH]={0};
TFT_eSPIOut eSpiOut(gfx,colors,eSpiTops,pList,fontW,fontH+1);
menuOut* constMEM outputs[] MEMMODE={&outSerial,&eSpiOut};//list of output devices
outputsList out(outputs,sizeof(outputs)/sizeof(menuOut*));//outputs list controller

//menuIn* inputsList[]={&joystickBtns,&serial};
//chainStream<2> in(inputsList);//3 is the number of inputs


//when menu is suspended
result idle(menuOut& o,idleEvent e) {
  if (e==idling) {
    o.println(F("press [select]"));
    o.println(F("to continue"));
  return proceed;

void topbutton3(){
  long currentTime = micros();
  if ((currentTime - lastDebounceTime) > debounceDelay){
    lastDebounceTime = currentTime;
    Serial.println("Top Button 3");
    //Control Block for Menu Selection
    clockToggle = not(clockToggle);
    //Not necessary but left for example. Simply changing the variable allows the manu to be updated.
//    nav.reset();//go back to home and execute commands as follows.
//    nav.doNav(navCmd(idxCmd,1));
//    if (selTest == 0){
//      nav.doNav(navCmd(downCmd));
//    }
//    else{
//      nav.doNav(navCmd(upCmd));
//    }
//    nav.doNav(navCmd(enterCmd));

void topbutton2(){
  long currentTime = micros();
  if ((currentTime - lastDebounceTime) > debounceDelay){
    lastDebounceTime = currentTime;
    Serial.println("Top Button 2");

void topbutton1(){
  long currentTime = micros();
  if ((currentTime - lastDebounceTime) > debounceDelay){
    lastDebounceTime = currentTime;
    Serial.println("Top Button 1");
    //Control Block for Menu Selection
    selTest = not(selTest);


void setup() {

  pinMode(outPin, OUTPUT);
//  while(!Serial);//Start right away when commented out.
  Serial.println("Timing Delay Module");
  nav.idleTask=idle;//point a function to be used when menu is suspended
//  mainMenu[1].disable();//disable a specific menu item
  //nav.showTitle=false;//show menu title?

  gfx.setTextSize(2);//test scalling

//  pinMode(WIO_5S_LEFT, INPUT_PULLUP);//not used
//  pinMode(WIO_5S_RIGHT, INPUT_PULLUP);//not used

  attachInterrupt(digitalPinToInterrupt(WIO_KEY_A), topbutton3, CHANGE);
  attachInterrupt(digitalPinToInterrupt(WIO_KEY_B), topbutton2, CHANGE);
  attachInterrupt(digitalPinToInterrupt(WIO_KEY_C), topbutton1, CHANGE);


  //Timer Setup //Not sure which timer is being used...
   *void startTimer(unsigned long period, void (*f)());
    void stopTimer();
    void restartTimer(unsigned long period);
    void setPeriod(unsigned long period);

  TC.startTimer(50126, delayISR); // 5,000,000 usec or 5 s

void loop() {
  nav.poll();//this device only draws when needed
  if (toggleOut){
    gfx.fillEllipse(280, 100, 10, 10, Red);
    gfx.fillEllipse(280, 100, 10, 10, Black);
  delay(100);//simulate a delay when other tasks are done

Are there any pictures or videos that are running? @bhclowers

For those interested, here’s a link to the basic functionality.


That’s great. Thanks for sharing.

Excuse sir. Can I adjust text size for menu list ? or Can I adjust each menu list ?

Hi, yes you can adjust the text size and the menu list.
You can find the code gfx.setTextSize(2);//test scalling, SELECT(clockToggle,clockMenu,"Clock Out:",doNothing,noEvent,noStyle and change it

Thank you. And if I want each line of letters to be different in size and set the position of the x-y axis, what do I do?

Like this