Lesson 13.1. UI. Full manual



  • The purpose of this lesson

    Hi! Today we will get acquainted with such a tempting thing as the M5 UI. This is a library designed to create a user interface. Thanks to M5 UI you can connect all kinds of fields, buttons, sliders and switches with a couple of lines of code, create conditional layers. Although the process of connecting UI elements is very simple, you can also use the visual M5 UI Designer for Arduino IDE tool.

    Figure 1

    It is necessary to consider all the existing elements of the library M5 UI in practice, as well as get acquainted with the process of creating the interface in the application M5 UI Designer.

    Short help

    A graphical interface is a set of functional elements that are required for interaction with the user. As these elements appear in various fields of the input/output of text, buttons, checkboxes, sliders, radio buttons and many others. As an example of a graphical interface, let's look at figure 2.

    Figure 2. User interface element

    In order to set the focus to the next element (the select element) use alternate pressing the Fn and TAB

    Inputbox represents the input area of the text information on the screen with a fixed height of 50 px. The width can be set by the user, but cannot be less than 32 px. At the top is the inscription (for example: Enter user name), note that at the end will automatically be added the symbol ':'. At the bottom is a rectangular area in which the user can enter data from the keyboard. When you focus on this element-the bottom of the backlight. To change the value, use the letters and numbers keys on the keyboard.

    Textbox is an area where text information is displayed on the screen. Dimensions can be user-defined, but cannot be smaller than the size of a single character. This element consists of one part. The text fits over the entire area and does not go beyond. There is no focus on this element.

    Waitingbar is an area for displaying graphical information on a screen with a fixed height of 50 px. The width can be set by the user, but cannot be less than 12 px. At the top is the inscription (for example: Connection to Wi-Fi), note that at the end will automatically be added the symbol ':'. At the bottom is a rectangular area, which is painted periodically orange and black squares. There is no focus on this element.

    Progressbar is an area for displaying graphical information on a screen with a fixed height of 50 px. The width can be set by the user, but cannot be less than 12 px. At the top is the inscription (for example: Times of the check), note that at the end will automatically be added the symbol ':'. In the lower part is a rectangular area that is colored according to the value specified (10% red, 30% orange, 80% green and 100% blue color). There is no focus on this element.

    Selectbox represents the text information selection area on the screen with a fixed height of 50 px. The width can be set by the user, but cannot be less than 44 px. At the top is the inscription (for example: Mode), note that at the end will automatically be added the symbol ':'. At the bottom is a rectangular selection area where the user can select data from the keyboard. To change the value, press the Fn key, then K / M or similar up / down arrows.

    Checkbox represents the input area of a single value (true/false) on a screen with a fixed height of 32 px. The width can be set by the user, but cannot be less than 44 px. On the left side there is a flag (if it is colored, true, if not - false). On the right side is the inscription (for example: Remember password). To remove or set the flag, press the SPACE key.

    Button represents the call area of any user-defined function (with a void signature (String*)) with a fixed height of 32 px. The width can be set by the user, but cannot be less than 22 px. In the center is the inscription (for example: Launch). A distinctive feature of this element is the support of icons from the standard set (Fig. 3). To press the button, press the SPACE key.

    Figure 3. Standard icon codes 24 x 24 px

    If the icon is connected, the minimum width will be 51 px.

    Rangebox represents the selection of an integer value from a given range with a fixed height of 50 px. The width can be set by the user, but cannot be less than 32 px. At the top is the inscription (for example: Speed), note that at the end will automatically be added the symbol ':'. At the bottom is the area containing the strip and the adjacent slider. To change the value, press the Fn key, then N / $ or similar left / right arrows.

    List of components for the lesson:

    • PC/MAC;
    • M5STACK;
    • FACES;
    • FACES Keyboard;
      -USB-C cable from standard set;
    • colored wires from the standard set (type socket-plug);
    • prototype Board for soldering 5 x 7 cm;
    • soldering iron 40 or 60 W;
    • rosin soldering;
    • tin soldering;
    • scissors;
    • resistor 36 ohms (1 PC.);
    • 160K resistor (1 PC .);
    • chip 74HC595N (1 PC .);
    • resistor 220 Ohm (1 PC.);
    • LEDs: orange, green, yellow, red (4 PCs.);
    • powerful transistor BC337 (1 PC.);
    • resistor 100 kOhm (1 PC.);
    • DC motor (1 PC.).

    Let's start!

    Step 1. Install the M5 UI library

    Follow the link M5 UI for Arduino IDE in the section Downloads(at the bottom of this page) and download the archive with the library from GitHub (Fig. 3.1).

    Figure 3.1. Click the Clone or download button, Then download ZIP

    Run the Arduino IDE and add the downloaded archive (Fig. 3.2).

    Figure 3.2. Click Sketch, Include Library, Add .ZIP Library...

    After that, the library will be successfully added. That's all.

    Step 2. Installation and tools of M5 UI Designer

    Similarly, click the M5 UI Designer for Arduino IDE link in the Downloads section (at the bottom of this page) and download the library archive from GitHub to your desktop, then extract the content and open the index document.html using a browser (beta-version works only under browsers on Chrome engine) (Fig. 3.4)

    Figure 3.4

    This completes the installation of the tool (Fig. 3.5).

    Figure 3.5. M5 UI Designer

    Step 3. Enter key-special (⊙_⊙)

    Imagine this situation-the user has entered the necessary information in the same Inputbox and he needs to process it, for example: send somewhere. How do I tell M5 that the user has completed input? That's right-press Enter (as one of the options).
    Anywhere in the code, you can always bind a user-defined function to the Enter key, as long as the signature of the user - defined function is as follows void (String*).

    void userFunction1(String* rootVar) {
    	// reaction after pressing the Enter key
    }
    
    ...
    UIEnter = userFunction1;
    

    Step 4. Morse code, Inputbox and Textbox (^_^♪)

    Let's add our first element-Inputbox. In it we will enter the text and after pressing the Enter key will hear from the speaker Morse code (Fig. 4).

    Figure 4. Morse code

    Open M5 UI Designer, drag and drop Inputbox and Textbox, set width and headers. In Tools > User Functions, click the yellow lightning icon and enter the name of the new Morse user function. Then, in the Properties > Enter key section, select Morse (Fig. 4.1).

    Figure 4.1.

    Select all the text from the Source section and copy it to the Arduino IDE.

    /* User functions: */
    void Morse(String* rootVar) {
     int MorseCodes[] =
     {
      0,1,-1,-1, // A
      1,0,0,0, // B
      1,0,1,0, // C
      1,0,0,-1, // D
      0,-1,-1,-1, // E
      0,0,1,0,  //F
      1,1,0,-1, // G
      0,0,0,0,  // H
      0,0,-1,-1,  // I
      0,1,1,1, // J
      1,0,1,-1, // K
      0,1,0,0, // L
      1,1,-1,-1, // M
      1,0,-1,-1,  // N
      1,1,1,-1, // O
      0,1,1,0,  // P
      1,1,0,1,  // Q
      0,1,0,-1, // R
      0,0,0,-1, // S
      1,-1,-1,-1, // T
      0,0,1,-1, // U
      0,0,0,1,  // V
      0,1,1,-1, // W
      1,0,0,1,  // X
      1,0,1,1, // Y
      1,1,0,0 // Z
     };
     for (int i = 0; i < UIInputbox_v05700a.length(); i++) {
      char chr = UIInputbox_v05700a[i];
      if (chr == ' ')
      {
        M5.Speaker.mute();
        delay(350);
      }
      else
      {
        int chrNum = (chr - 'a') * 4;
        for (int j = chrNum; j < (chrNum + 4); j++)
        {
          M5.Speaker.tone(440);
          if (MorseCodes[j] == 0)
            delay(50);
          else if (MorseCodes[j] == 1)
            delay(200);
          M5.Speaker.mute();
          delay(150);
        }
      }
     }
    }
    

    As you can see - the entire code framework generated by M5 UI Designer remained absolutely unchanged in the standard form. The only thing we have changed is the custom Morse function. It's very simple :) (Fig. 4.2).

    Figure 4.2

    Step 5. Bruteforce and Waitingbar ≧ ( ◕ ‿ ‿ ◕ ) ≦

    Similarly, we add Waitingbar. We will generate an 8-bit random code and then select it. The progress bar will just Waitingbar. Once the code is matched Waitingbar will be hidden. The process will start after pressing the Enter key (Fig. 5).

    Figure 5.

    In this example, we modify the default layer function by adding void UIDiable(bool, String*) after enumerating the elements. This is to ensure that the Waitingbar is hidden for the duration of the inactivity.

    /* Function for layer default: */
    void LayerFunction_default(String* rootVar) {
     /* UI Elements */
     ...
     UIDisable(true, &UIWaitingbar_yksk2w8);
    }
    

    void UIDisable(bool, String*) or void UISet (String*, int) used to hide an element. Where bool - can be set to true/false i.e. hide/show element; String* - pointer to rootVar (root variable) element.

    Now add the contents of the void Brutforce(String) function*):

    /* User functions: */
    void Brutforce(String* rootVar) {
      /* make random original key */
      uint8_t okey = random(0, 256); 
      String okeyString = "";
      for (int i = 0; i < 8; i++) {
        okeyString += String((okey >> i) & 1);
      }
      UISet(&UITextbox_sxjzx0g, okeyString); // set value for Textbox
      
      UIDisable(false, &UIWaitingbar_yksk2w8); // show the Waitingbar
    
      uint8_t key;
      
      while (true) {
        key++;
        String keyString = String();
        for (int i = 0; i < 8; i++) {
          keyString += String((key >> i) & 1);
        }
        UISet(&UITextbox_eyar2x, keyString);
        Serial.print(key);
        Serial.print(" ");
        Serial.println(okey);
        if (key == okey) break;
        M5.Speaker.tone(800);
        delay(40);
        M5.Speaker.mute();
      }
      UIDisable(true, &UIWaitingbar_yksk2w8);
      M5.Speaker.tone(600);
      delay(75);
      M5.Speaker.mute();
      M5.Speaker.tone(800);
      delay(75);
      M5.Speaker.mute();
      M5.Speaker.tone(500);
      delay(75);
      M5.Speaker.mute();
    }
    

    To set the value for an element, use the void UISet(String*, String) or void UISet(String*, int) function. Where String* is a pointer to the rootVar (root variable) of the element;String or int is the new value.

    Now run and see what happened (Fig. 5.1).

    Figure 5.1

    Step 6. BatteryCheck and Progressbar Σ(O_O)

    Let's make a tester charge conventional finger batteries type A, AA, AAA. Indication will be carried out using the Progressbar in percentage terms, and in addition at the bottom using Textbox will display the voltage in mV (Fig. 6).

    Figure 6.

    Here we modify the layer function

    /* Function for layer default: */
    void LayerFunction_default(String* rootVar) {
     /* To open this layer use: */
     ...
     // BattaryCheck
     while (true) {
      int voltage = analogRead(35) * 3400 / 4096;
      int percentage = voltage * 100 / 1600;
      UISet(&UIProgressbar_1mlmhcu, percentage);
      UISet(&UITextbox_gtaetjh, voltage);
      delay(500);
     }
    }
    

    Constantly, every 500 MS, in the cycle we will take readings from the ADC port 0 (pin 35).
    Then we will calculate the voltage: 3400 mV is the reference voltage, 4096 is the resolution of the ADC. The switching circuit is shown in figure 6.1.

    Note-M5 Bottom is used instead of FACES

    Figure 6.1. Visual scheme of switching on the built-in ADC to the finger battery

    The device works great! Now you have a great tool to check the batteries (Fig. 6.2).

    Figure 6.2

    Step 7. LEDshift and Selectbox ( ◡ ‿ ◡ *)

    Take four LEDs (orange, green, red and yellow), connect them through the shift register 74HC595N to M5 according to the scheme in figure 7.

    Figure 7. Wiring the LEDs to the M5 via the shift register

    Create a graphical interface using M5 UI Designer, as in the previous examples, and copy the code (Fig. 7.1).

    Figure 7.1.

    Now let's modify the code.

    Add the pin numbers on the M5 for connecting the shift register 74HC595N after RootVar's:

    /* RootVar's for UI elements (note: not edit manually) */
    ...
    // Shift register pinout
    int SH_CP = 17;
    int ST_CP = 2;
    int DS = 5;
    

    Next, we add the contents of the void SelectColor(String) function*):

    /* User functions: */
    void SelectColor(String* rootVar) {
      int led = UIOptionValue(&UISelectbox_6foo6h).toInt();
      digitalWrite(ST_CP, LOW);
      shiftOut(DS, SH_CP, MSBFIRST, led);
      digitalWrite(ST_CP, HIGH);
      delay(100);
    }
    

    In order to get selected option from Selectbox using function String UIOptionValue(String*).

    Now you need to fill the Selectbox with options. to do this, we add 5 lines of code to the very beginning of the layer function:

    /* Function for layer default: */
    void LayerFunction_default(String* rootVar) {
     // add options to Selectbox
     UIOption("OFF", "0", &UISelectbox_6foo6h);
     UIOption("RED", "17", &UISelectbox_6foo6h);
     UIOption("YELLOW", "3", &UISelectbox_6foo6h);
     UIOption("GREEN", "5", &UISelectbox_6foo6h);
     UIOption("ORANGE", "9", &UISelectbox_6foo6h);
     ...
    }
    

    To add an option to the Selectbox, use the void UIOption(String, String, String*) function. Where the first String is the signature of the option that the user sees; the second String is the value of the option that is hidden from the user; String* is a pointer to the rootVar (root variable) of the element

    At the end, add three lines after the comment below. Thus, we will set up the M5 contacts to output:

    void setup() {
     ...
     /* Prepare user's I/O. For example pinMode(5, OUTPUT); */
     pinMode(SH_CP, OUTPUT);
     pinMode(ST_CP, OUTPUT);
     pinMode(DS, OUTPUT);
     ...
    }
    

    That's it! :) (fig. 7.2).

    Figure 7.2

    Step 8. SmartDrill and team from Rangebox, Checkbox, Button (。• ᵕ •。`)

    What can combine Rangebox, Checkbox and Button? Correct! - drilling machine. Take a DC motor (for example, from the cassette player), transistor (to the power came), resistor to block the current base, a few wires, breadboard and collect! Sometimes it is necessary to make several holes one by one, and sometimes it is necessary to make only one, so there is an idea to allocate some time to the drill, and then turn it off automatically: here we come to the aid of the Checkbox.

    Figure 8

    With Rangebox we will adjust the supply voltage on the motor. The button will start and stop the process (Fig. 8). Please note-the button with the icon ;)

    The Enter button here is not useful to us, so Enter key for all elements will be empty (zero).
    Instead, we will call the custom Drill function using The callback property for the button.

    What's code? After Rootvar's add bool startStatus, which will allow the program to understand whether the engine is running or not.

    /* RootVar's for UI elements (note: not edit manually) */
    ...
    // User's variables
    bool startStatus = false;
    

    Fill the user-defined function void Drill(String*):

    void Drill(String* rootVar) {
      startStatus = (startStatus) ? false : true;
      if (startStatus)
      {
        int power = UIRangebox_ztj619h.toInt() * 255 / 100;
        dac_out_voltage(DAC_CHANNEL_1, power);
        if (UICheckbox_1n9gs0b == "true")
        {
          UICaption("WAIT", &UIButton_enhu9fc);
          delay(25000);
          Drill(0);
          return;  
        }
        UICaption("STOP", &UIButton_enhu9fc);
      }
      else
      {
        dac_out_voltage(DAC_CHANNEL_1, 0);
        UICaption("START", &UIButton_enhu9fc);
      }
    }
    

    To set the value on the analog port, use the function dac_out_voltage (DAC_CHANNEL_1, int). Where DAC_CHANNEL_1 - channel number (pin 25), int - value.

    ;

    In order to change the title of any of the UI elements use the function UICaption(String, String)*. Where String - new header; String* - pointer to rootVar (root variable) of the element

    Hurray! Now you can try drilling (Fig. 8.1).

    Figure 8.1

    Step 9. Launch!

    In the section "Downloads" attached video demonstration. This completes the lesson.

    Downloads