Summary

This article updates the display system in my Tilt Hydrometer project to include the state machine apparatus to move between screens.

This series is broken up into the following 12 articles with a few additional possible articles. 

Tilt Hydrometer (Part 1) Overview & Out-of-Box

Tilt Hydrometer (Part 2) Architecture

Tilt Hydrometer (Part 3) Advertising Scanner

Tilt Hydrometer (Part 4) Advertising Packet Error?

Tilt Hydrometer (Part 5) Tilt Simulator & Multi Advertising iBeacons

Tilt Hydrometer (Part 6) Tilt Simulator an Upgrade

Tilt Hydrometer (Part 7) Advertising Database

Tilt Hydrometer (Part 8) Read the Database

Tilt Hydrometer (Part 9) An LCD Display

Tilt Hydrometer (Part 10) The Display State Machine

Tilt Hydrometer (Part 11) Draw the Display Screens

Tilt Hydrometer (Part 12) CapSense

Tilt Hydrometer: LittleFS & SPI Flash (Part ?)

Tilt Hydrometer: WiFi Introducer (Part ?)

Tilt Hydrometer: WebServer (Part ?)

Tilt Hydrometer: Amazon MQTT (Part ?)

Tilt Hydrometer: Printed Circuit Board (Part ?)

You can get the source code from git@github.com:iotexpert/Tilt2.git  This repository has tags for each of the articles which can be accessed with "git checkout part12"  You can find the Tilt Simulator at  git@github.com:iotexpert/TiltSimulator.git.

 

We will continue to edit the Display Manager in this article:

Story

Things are a little unfair as I already know the end of this story because I wrote this code a few months ago.  Recently, as I came back to write these articles I looked at the code, actually Monday.  The code was pretty complicated and I have been really busy so I set it aside as I wasn’t sure how to explain it.  Then I looked again on Tuesday and contemplated re-writing it… then again on Wednesday then … and finally Saturday when I decided that what I had done originally was correct.  That means I just need to explain it.

My system is going to look work like this:

  1. A splash screen with the IoT Expert Logo
  2. A table of data screen with one row per tilt
  3. A series of details screens, one per tilt
  4. The ability to “skip” detail screens if there is no data
  5. An automated UI that moved through the screens every 5000ms
  6. Support for a manual UI so that the ntshell and the CapSense interface could send it commands to move to specific screens

Here is the picture:

Add Color to the Tilt Data Manager

Before I jump into the GUI, I need to add some color information to the database of Tilts.  This is a little bit of a smearing the line between the database and the display systems, but I felt that having all of the information about the Tilts in one place was better than having a split.  In order for the display manager to get the color information I add a new function to the tiltDataManager.h to return the specific GUI_COLOR (which is an emWin thing) for that specific Tilt handle.

GUI_COLOR tdm_colorGUI(tdm_tiltHandle_t handle)

Then I need to update the tiltDataManager. c to have the color in the database.  Notice that the emWin library doesn’t have purpose or pink so I create those colors.

typedef struct  {
    char *colorName;
    GUI_COLOR color;
    uint8_t uuid[20];
    tdm_tiltData_t *data;
    int numDataPoints;
    int numDataSeen;
} tilt_t;


#define IBEACON_HEADER 0x4C,0x00,0x02,0x15
#define GUI_PINK GUI_MAKE_COLOR(0x00CCCCFF)
#define GUI_PURPLE GUI_MAKE_COLOR(0x00800080)

static tilt_t tiltDB [] =
{
    {"Red",    GUI_RED,    {IBEACON_HEADER,0xA4,0x95,0xBB,0x10,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
    {"Green" , GUI_GREEN,  {IBEACON_HEADER,0xA4,0x95,0xBB,0x20,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
    {"Black" , GUI_GRAY,   {IBEACON_HEADER,0xA4,0x95,0xBB,0x30,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
    {"Purple", GUI_PURPLE, {IBEACON_HEADER,0xA4,0x95,0xBB,0x40,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
    {"Orange", GUI_ORANGE, {IBEACON_HEADER,0xA4,0x95,0xBB,0x50,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
    {"Blue"  , GUI_BLUE,   {IBEACON_HEADER,0xA4,0x95,0xBB,0x60,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
    {"Yellow", GUI_YELLOW, {IBEACON_HEADER,0xA4,0x95,0xBB,0x70,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
    {"Pink"  , GUI_PINK,   {IBEACON_HEADER,0xA4,0x95,0xBB,0x80,0xC5,0xB1,0x4B,0x44,0xB5,0x12,0x13,0x70,0xF0,0x2D,0x74,0xDE},0,0,0},
};
#define NUM_TILT (sizeof(tiltDB)/sizeof(tilt_t))

Then I add the actual function.

GUI_COLOR tdm_colorGUI(tdm_tiltHandle_t handle)
{
    return tiltDB[handle].color;
}

Display Manager State Machine

The picture above looks like a state machine, which I suppose makes sense as it is a state machine.  The parts of the state machine are

  1. Initialize the screen
  2. Update the data on the screen
  3. Go to the next screen
  4. Sequence the “subscreen” i.e. from the purple screen to the red screen
  5. Precheck (can you enter the screen)

To support this I created a structure of function pointers:

typedef struct {
    bool (*precheck)(void);   // return true if you can come to this screen
    void (*init)(void);       // draw the initial stuff
    void (*update)(void);     // update the data
    bool (*sequence)(void);   // sequence the data .. return true if you should go to the next screen
    dm_screenName_t next;
} dm_screenMgmt_t;

This means that each “screen” needs functions in a table that have:

  1. precheck – returns true if it is legal to come to that screen
  2. init – draw the initial data (like the table outline)
  3. update – to update the data on the screen
  4. sequence – up move to the next subscreen e.g. purple –> red

But what is the “next” member in the structure?  This is just an index into the state machine table that tells what is the next entry to go too.  I wish that there was a better way to do this in C as the enumerated value is critically mapped to the place in the array of screens (see below)

typedef enum {
    SPLASH,
    TABLE,
    SINGLE,
} dm_screenName_t;

With all of this setup I can now make a table to represent the states of my screens like this (more on the functions a bit later in this article).

dm_screenMgmt_t screenList[] = {
    {dm_displayScreenSplashPre, dm_displayScreenSplashInit, dm_displayScreenSplashUpdate, dm_displayScreenSplashSeq, TABLE},
    {dm_displayScreenTablePre , dm_displayScreenTableInit, dm_displayScreenTableUpdate, dm_displayScreenTableSeq, SINGLE},
    {dm_displaySinglePre, dm_displaySingleInit, dm_displaySingleUpdate, dm_displaySingleSeq, TABLE},
};

With that in place I can now create the code that actually runs the state machine, dm_nextScreen.  This function

  1. Runs the “sequence” function that will either move to the next subscreen OR it will return true (meaning go to the next screen)
  2. If the precheck returns true then it is legal to jump to this screen
  3. Jump to the next screen
  4. Finally update the data on the current screen
static void dm_nextScreen()
{
    if((*screenList[dm_currentScreen].sequence)())
    {
        if((*screenList[screenList[dm_currentScreen].next].precheck)())
        {
            dm_currentScreen = screenList[dm_currentScreen].next;
            (*screenList[dm_currentScreen].init)();
        }
    }
    (*screenList[dm_currentScreen].update)();
}

The Display Manager Task

The display manager task is now a bit tricky as well.  First of all I have a boolean variable called “autoRotate”.  When this variable is true it means that the screens should automatically switch to the next screen every 5 seconds.

The next part of the code initializes a command queue (so that other tasks can send a next screen or enable/disable of autorotate or a jump straight to the table.

The dm_currentScreen is global to this file and keep track of which screen you are currently on, which is what state the state machine is in.

The next part of the code waits for a message OR a timeout.  The timeout happens after 5000ms (5s) and tell the system to either go to the next screen, or update the data on the screen.

void dm_task(void *arg)
{
    dm_cmdMsg_t msg;
    bool autoRotate=true;
    dm_cmdQueue = xQueueCreate(10,sizeof(dm_cmdMsg_t));

    /* Initialize the display controller */
    mtb_st7789v_init8(&tft_pins);
    GUI_Init();

    dm_currentScreen = SPLASH;

    dm_displayScreenSplashInit();
    dm_displayScreenSplashUpdate();

    for(;;)
    {
        if(xQueueReceive(dm_cmdQueue,&msg,5000) == pdPASS) // Got a command
        {
            switch(msg.cmd)
            {
                case SCREEN_NEXT:
                dm_nextScreen();
                break;
                case SCREEN_AUTO:
                autoRotate = ! autoRotate;
                printf("AutoRotate =%s\n",autoRotate?"True":"False");
                break;
                case SCREEN_TABLE:
                dm_currentScreen = TABLE;
                (*screenList[dm_currentScreen].init)();
                (*screenList[dm_currentScreen].update)();
                break;

            }

        }
        else // otherwise just update the screen
        {
            if(autoRotate)
                dm_nextScreen();
            else
                (*screenList[dm_currentScreen].update)();
        }
    }
}

With the infrastructure in place, in the next article I actually draw the screens.

Recommended Posts

No comment yet, add your voice below!


Add a Comment

Your email address will not be published.