Thursday, June 21, 2018

ESP32 'Test Gadget' Menu System

I decided to go with a menu system very similar to what I had used in several of my other projects.  Now it uses the joystick vertical axis info to move through the menu items and a 'LongPress' on the button to make a selection.  It can be expanded to use the horizontal axis info and a 'ShortPress' if additional capabilities are desired.

Multiple menus can be created by defining different sets of definitions and variables for each menu, along with different versions of the two menu functions.   I use a naming convention of mnuName...   where Name  where NAME specifies which menu.
For the 'Test Gadget' there might be a mnuMain..., mnuVoltmeter...,
mnuSigGen... .

Each menu uses a block of global definitions and variables, with each member using the stated naming convention.  For my main menu they are.

#define  mnuMainNum    3       // number of menu items  0 referenced

#define  mnuMainX      20        // menu x and y position
#define  mnuMainY      20
#define  mnuMainTSize  3                      // text size
#define  mnuMainTCol   YELLOW       // text color
#define  mnuMainTBak   BLUE            // text background color
int    mnuMainSel   = 1 ;                        // selected menu item
// menu looks better if you pad items with spaces to make lengths equal

String   mnuMainItems[] = {" Meters   ", " Waveform ", " LCD      "," Sub Menu "};


For this test example I also have a sub menu that is called from the main menu.  Its global values are.

#define  mnuSubNum    2       // number of menu items  0 referenced

#define  mnuSubX      120     // menu x and y position
#define  mnuSubY     100
#define  mnuSubTSize  3                 // text size
#define  mnuSubTCol   BLUE        // text color
#define  mnuSubTBak   CYAN       // text background color
int    mnuSubSel   = 1 ;                    // selected menu item
// menu looks better if you pad items with spaces to make lengths equal

String   mnuSubItems[] = {" sub1 ", " sub2 ", " sub3 "};

I find it really easy to just do a copy and paste, and then using the Arduino IDE find and replace function to quickly make changes to the names.

Most of the work in the menu function is a function called
showNAMEMenuItems(), as with the definitions and variables you will have to change the name for each menu.  For the Main menu it looks like this. .

void showMainMenuItems() {

  tft.setTextSize(mnuMainTSize);
  for (int i = 0; i <= mnuMainNum ; i++) {
    if (i == mnuMainSel )
      tft.setTextColor( mnuMainTBak ,  mnuMainTCol );  // inverted colors
    else
      tft.setTextColor(mnuMainTCol, mnuMainTBak );     // normal colors
     // calculate proper x y position and set cursor
    tft.setCursor( mnuMainX  , mnuMainX + (i * 8 * mnuMainTSize));
    tft.println(mnuMainItems[i]);
  }

}
 It uses the global values and variables to show the menu items at the correct x,y position on the screen.  Each menu item is tested to see if it is the selected one, and the text/background colors are inverted from the non selected item.

The other function in the menu system is a function called
ShowMenuNAME() . It uses a local variable inMenu to control a while loop that reads the joystick and checks the vertical axis value to select the desired menu item.   It calls showNAMEMenuItems()
to update the menu after any change.  It also tests the joystick switch for a 'LongPress' which causes the while loop to end.

void ShowMenuMain() {
  boolean inMenu = true;
  showMainMenuItems();
  delay(200);
  while (inMenu) {
    ReadJoyStick();
    delay(20);
    if (jstick) {
      if (jstickV ) {
        mnuMainSel += jstickV ;
        if ( mnuMainSel < 0)  mnuMainSel = 0;
        if (mnuMainSel >= mnuMainNum )  mnuMainSel = mnuMainNum;
        showMainMenuItems();
      }
      if (jstickSW == LongPress)
        inMenu = false;         // long press gets you out of the menu loop
    }
    jstick = false;
  }

After the while(inMenu) loop ends you can use either a series of if statements , or a switch- case function to select what is done for each menu item.  For this test example I used a series of if statements , mainly because I was just copying existing code for testing.  If I had already written specific functions for each menu item, I would probably have gone with a switch-case instead.

if (mnuMainSel == 0) {
    tft.fillScreen(BLACK);
    drawMeter(MTR1_X, MTR1_Y, MTR1_W , MTR1_H, WHITE, WHITE, BLACK,
     "  1 2 3 4 5 6 7 8 9 10");
    drawMeter(MTR2_X, MTR2_Y, MTR2_W , MTR2_H, WHITE, BLUE, YELLOW,
    "  1 2 3 4 5 6 7 8 9 10");
    plotNeedle(MTR1_X, MTR1_Y, MTR1_W , MTR1_H, 150, 300,  BLACK);
    plotNeedle(MTR2_X, MTR2_Y, MTR2_W , MTR2_H, 250, 400, BLUE);
  }
  if (mnuMainSel == 1) {
    tft.fillScreen(BLACK);
    DrawWaveformScreen(BLUE, CYAN, 10);
    DrawYScale(0 , 10, 10);
  }
  if (mnuMainSel == 2) {
    tft.fillScreen(BLACK);
    lcd_begin(10, 60, 16, 4, 3,  BLUE,  YELLOW) ; // text and background color
    tft.setCursor(0, 0);
    tft.setTextColor(lcdTxtColor);
    tft.setTextSize(lcdTxtSize);
    for (int i = 0; i < lcdRow; i++) {
      lcd_setCursor(0, i);
      lcd_print("12345");
      lcd_setCursor(5, i);
      lcd_printNum(6, 0);
      lcd_print("7890123456");
    }
  }
  if (mnuMainSel == 3) {
    ShowMenuSub();
  }

}


This test code this shows a simple menu, which lets you select one of the test graphic display types from the last blog entry.  The other option is a sub menu that only exits when you do a 'LongPress'.
In each case the display stays around for 5 seconds then clears the screen and shows the main menu again.The two functions for the sub menu are basically the same as the main menu code with the names changed.

void ShowMenuSub() {
  boolean inMenu = true;
  showSubMenuItems();
  delay(200);
  while (inMenu) {
    ReadJoyStick();
    delay(20);
    if (jstick) {
      if (jstickV ) {
        mnuSubSel += jstickV ;
        if ( mnuSubSel < 0)  mnuSubSel = 0;
        if (mnuSubSel >= mnuSubNum )  mnuSubSel = mnuSubNum;
        showSubMenuItems();
      }
      if (jstickSW == LongPress)
        inMenu = false;         // long press gets you out of the menu loop
    }
    jstick = false;
  }
  // out of menu item selection now do selected item
  // you can do as multiple if statements
  // or a switch  -  case
  //  FOR THIS DEMO JUST EXIT
}

void showSubMenuItems() {
  tft.setTextSize(mnuSubTSize);
  for (int i = 0; i <= mnuSubNum ; i++) {
    if (i == mnuSubSel )
      tft.setTextColor( mnuSubTBak ,  mnuSubTCol );  // inverted colors
    else
      tft.setTextColor(mnuSubTCol, mnuSubTBak );     // normal colors
    tft.setCursor( mnuSubX  , mnuSubX + (i * 8 * mnuSubTSize));
    tft.println(mnuSubItems[i]);
  }
}


Here is a picture of what the menu looks 
like after the sub menu was selected.  
The same code can be easily modified to use a rotary encoder with switch instead of the joystick.  I have found this to be a relatively simple menu system to implement.  It may use more program space than some of the complete menu management sytems, but most of my applications have only need one or two menu selections and in that case may be smaller.


Sunday, June 17, 2018

ESP32 'Test Gadget' starting on software Update 6/19/18

I got back from visiting with family at a wedding, and finally getting time to work on the software for the 'Test Gadget'
After getting the drivers for the TFT display and the Adafruit graphics library working along with the Joystick, I started porting some of my existing software over to the ESP32.  It has turned out to be nearly a direct drop-in from the code I have for the Nano and Pro-mini I have used before.

For some of the test functions a digital read out of values is OK, but when you are making adjustments I prefer some sort of a analog display.  In my early projects I used a bar-graph as a analog display, but recently I came up with a very small set of functions that simulate a analog meter.  With the speed of the processor and memory available, I could probably make one that is more accurate in appearance, but this simple one is adequate for what I want.


As a test I drew two meters on the screen, and have room for four if I would ever need them.  With this size, there is plenty of room on the screen to display multiple digital values, such as instantaneous reading, and peak readings.  I could also make one  large meter that would cover the entire display area.

Some of the other test instruments I plan on making with the 'Test Gadget' will have frequency scans shown as a wave-form.  I ported over my basic waveform screen with programmable grid sizes, and Y-axis scales.  They look very nice on this size display, and can redraw with very little flicker.

I have just a few more of these routines to port over in the next few days.
Then I have to decide if I want to incorporate a full menu management system, or just use the simple menus I have used in the past.
Thats all for now  check back for updates.

UPDATE 6/19/18

Thinking about what it will take to port some of the CWTD.org 'TestGadget' programs to the ESP32 version, I thought it would be nice to emulate the 2 X 16 LCD display used with their Nano version.

From the 'Gadget Rack' code I found the description of the the basic lcd commands.
Gadget codes have access to the 2 row by 16 character LCD.  The LCD is initialized in
*   the main setup code.  The LCD is accessed with the following functions:

*   void lcd.clear(void)  // clears the lcd display and set cursor to 0,0
*   void lcd.setCursor(int column, int row)
*   int  lcd.print(char *text)
*   int  lcd.print(type n, BASE)  // n is a number (char, byte, int, long, or string,
*                                 // BASE (optional) is BIN, DEC, OCT, HEX or an integer
*                                 // specifying the number of digits to print following
*                                 // the decimal point.


 I found there was also an lcd.begin(int column, int row) function that is called in setup() to initialize the lcd.  With just a little modification to these functions, I could emulate a LCD from a 2x16 to a 4x20 display. Some of these are dependent on the text size used. The first thing I did was define some global variables that are initialized in the begin function. For these I decided on

int lcdX; int lcdY; int lcdCol; int lcdRow;       // position on screen and size of lcd to simulate

int lcdTxtSize; int lcdTxtColor; int lcdBakColor;   // text size color and background color of                                                                                                  //simulated lcd
Since I didn't want to bother rewriting anything from the lcd library, I just wrote some simple functions to translate the LCD functions to something understood by the tft library I use.  To keep the format similar I changed the name from lcd.function to lcd_function. This should make it easier to rewrite any existing code. 
First and most import function to write was the lcd_begin().
The original function only requires it be passed the number of columns and rows in the LCD display.  For my version I need to also pass the X and Y location of the display on the screen, the text size to use, and the text color and background color for the simulated LCD.  The lcd_begin()  first copies the passed values to the global variables, and from those values define and draw a filled rectangle that is the emulated LCD display. The next function I wrote was the lcd_clear() which just uses the global variables set in lcd_begin() to compute and redraw the filled rectangle.

The lcd_setCursor(column,row) translates the LCD column and row information to X and Y coordinates on the tft display and calls the tft.setCursor(x,y) function.  I had to make two different versions of the lcd.print() function, one for text and another for numbers.
The first is used as lcd_print( "text") to print text at the current cursor position on the LCD. The next is lcd_printNum(6,2) which in this case prints the number 6 followed by ".00" .
Here are a couple pictures of what the emulated LCD looks like
first a 16 x 4 display using this code

 lcd_begin(10, 60, 16, 4, 3,  BLUE,  YELLOW) ; // text and background color

  tft.setTextColor(lcdTxtColor);
  tft.setTextSize(lcdTxtSize);
  for (int i = 0; i < lcdRow; i++) {
    lcd_setCursor(0, i);
    lcd_print("12345");
    lcd_setCursor(5, i);
    lcd_print("7890123456");
  }

 The second picture shows the use of the lcd_setCursor(),
lcd_print() and lcd_printNum() functions using this code.

 lcd_begin(10, 80, 16, 2, 3,  BLUE,  YELLOW) ;

  lcd_setCursor(0, 0);
  lcd_print("12345");
  lcd_setCursor(5, 1);
  lcd_printNum(6, 2);




Hopefully these simple functions might make any porting of the CWTD 'TestGadget' or any other code written for a LCD display to the tft display I am using for my ESP32 version a little easier.