Signal Generator

Dependencies:   IniManager RA8875 Watchdog mbed-rtos mbed

Fork of speaker_demo_Analog by jim hamblen

SignalGenDisplay.cpp

Committer:
WiredHome
Date:
2017-05-20
Revision:
6:1f48212fbaf9
Parent:
5:49dd0c647a40

File content as of revision 6:1f48212fbaf9:

// 
// Signal Generator Control System
//
//
#include "SignalGenDisplay.h"
#include "rtos.h"
#include "IniManager.h"
#include "BPG_Arial08x08.h"


// ##### Main Page #############################################################
//
// +---------------------------------------------------------------------------+
// | +--- Scope Area ---------------------------+   Progam Name and version    |
// | |                                          |   Manufacturer name          |
// | | +---- Wave Outline - -                   |                              |
// | | |                                        |   [Start/Stop    ]  [      ] |
// | | |                                        |   [Text Entry Box]  [ Back ] |
// | |                                          |   +-- Keypad Area ---------+ |
// | |                                          |   |                        | |
// | |                                     |    |   |                        | |
// | |                                     |    |   |                        | |
// | |                                  ---+    |   |                        | |
// | |                                          |   |                        | |
// | +------------------------------------------+   |                        | |
// |                                                |                        | |
// | [duty cycle]  [frequency]     [amplitude]      |                        | |
// |                                                |                        | |
// | [    ...   ]  [period   ]     [offset   ]      |                        | |
// |                                                |                        | |
// | [     ] [      ] [        ] [        ] [    ]  |                        | |
// | [Sine ] [Square] [Triangle] [Sawtooth] [User]  +------------------------+ |
// +---------------------------------------------------------------------------+

// Object Colors
#define UI_BackColor                RGB(8,8,8)
#define UI_ScopeBackColor           RGB(0,0,0)
#define UI_ScopeFrameColor          RGB(255,255,255)
#define WaveOutlineColor            RGB(16,16,32)
#define UI_DutyColor                Magenta
#define UI_FreqColor                BrightRed
#define UI_VP2PColor                Yellow
#define UI_VOffsetColor             Green
#define UI_BUTTON_FACE_UP           White
#define UI_BUTTON_FACE_DN           RGB(255,92,92)
#define UI_BUTTON_SHADOW            RGB(128,0,0)
#define UI_BUTTON_FACE_DISABLED     RGB(24,24,24)
#define UI_BUTTON_SHADOW_DISABLED   RGB(32,0,0)
#define UI_ProductNameColor         UI_BUTTON_FACE_DN

#define SC_LEFT_MARGIN              10       // Scope left margin
#define SC_TOP_MARGIN               10
#define SC_RIGHT_MARGIN             30
#define SC_BOT_MARGIN               20
#define BTN_W       54          // Button width
#define BTN_H       32          // Button height
#define BTN_S       5           // Button white-space

#define BTN_MODE_X  2           // Mode Buttons left edge
#define BTN_MODE_Y  233         // Mode Buttons top edge

#define BTN_KEYP_X  300         // Keypad left edge
#define BTN_KEYP_Y  53          // Keypad top edge

// Rectangular Zones
static const rect_t UI_START_STOP      = {BTN_KEYP_X,BTN_KEYP_Y, BTN_KEYP_X+2*BTN_W+BTN_S,BTN_KEYP_Y+BTN_H};
static const char * UI_StartLabels[3]  = { "Start", "Stop", "Pulse" };
static const char StartStopKeys[]      = { 'G', 'O', 'P' };
static const rect_t UI_DATA_ENTRY      = {BTN_KEYP_X,BTN_KEYP_Y, BTN_KEYP_X+2*BTN_W+BTN_S,BTN_KEYP_Y+BTN_H-2};
static const rect_t UI_SCOPE_RECT      = {4,5, 290,160};

static const rect_t UI_WAVE_RECT       = {4+SC_LEFT_MARGIN,5+SC_TOP_MARGIN, 290-SC_RIGHT_MARGIN,160-SC_BOT_MARGIN};

static const rect_t Parameters[] = {
    {4,170, 60,190},            // 'd'uty cycle
    {90,170, 186,190},          // 'f'requency
    {90,200, 186,220},          // 'p'eriod
    {230,170, 290,190},         // 'v'oltage
    {230,200, 290,220}          // 'o'ffset
};
static const int ParameterCount = sizeof(Parameters)/sizeof(Parameters[0]);
static const char ParameterKeys[] =    { 'd', 'f', 'p', 'v', 'o' };

static const rect_t UI_PROD_RECT =     {  298,3, 479,51 };
static const rect_t NavToSettings =    {  4,200, 60,220 };

// Mode Buttons
static const rect_t ModeButtons[] = {
    { BTN_MODE_X+0*(BTN_W+BTN_S),BTN_MODE_Y,  BTN_MODE_X+0*(BTN_W+BTN_S)+BTN_W,BTN_MODE_Y+BTN_H },
    { BTN_MODE_X+1*(BTN_W+BTN_S),BTN_MODE_Y,  BTN_MODE_X+1*(BTN_W+BTN_S)+BTN_W,BTN_MODE_Y+BTN_H },
    { BTN_MODE_X+2*(BTN_W+BTN_S),BTN_MODE_Y,  BTN_MODE_X+2*(BTN_W+BTN_S)+BTN_W,BTN_MODE_Y+BTN_H },
    { BTN_MODE_X+3*(BTN_W+BTN_S),BTN_MODE_Y,  BTN_MODE_X+3*(BTN_W+BTN_S)+BTN_W,BTN_MODE_Y+BTN_H },
    { BTN_MODE_X+4*(BTN_W+BTN_S),BTN_MODE_Y,  BTN_MODE_X+4*(BTN_W+BTN_S)+BTN_W,BTN_MODE_Y+BTN_H },
};
static const int ModeCount = sizeof(ModeButtons)/sizeof(ModeButtons[0]);
static const SG_Waveform UI_ModeList[] = {
    SG_SINE,
    SG_SQUARE,
    SG_TRIANGLE,
    SG_SAWTOOTH,
    SG_USER,
};
static const char ModeKeys[] = { 'S','Q','T','W','U' };
static const char *ModeNames[] = {
    "Sine",
    "Square",
    "Triangle",
    "Sawtooth",
    "User",
};

static const rect_t UI_Keypad[] = {
    {BTN_KEYP_X+2*(BTN_W+BTN_S),BTN_KEYP_Y+0*(BTN_H+BTN_S), BTN_KEYP_X+2*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+0*(BTN_H+BTN_S)+BTN_H },    // backspace
    {BTN_KEYP_X+0*(BTN_W+BTN_S),BTN_KEYP_Y+1*(BTN_H+BTN_S), BTN_KEYP_X+0*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+1*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+1*(BTN_W+BTN_S),BTN_KEYP_Y+1*(BTN_H+BTN_S), BTN_KEYP_X+1*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+1*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+2*(BTN_W+BTN_S),BTN_KEYP_Y+1*(BTN_H+BTN_S), BTN_KEYP_X+2*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+1*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+0*(BTN_W+BTN_S),BTN_KEYP_Y+2*(BTN_H+BTN_S), BTN_KEYP_X+0*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+2*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+1*(BTN_W+BTN_S),BTN_KEYP_Y+2*(BTN_H+BTN_S), BTN_KEYP_X+1*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+2*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+2*(BTN_W+BTN_S),BTN_KEYP_Y+2*(BTN_H+BTN_S), BTN_KEYP_X+2*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+2*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+0*(BTN_W+BTN_S),BTN_KEYP_Y+3*(BTN_H+BTN_S), BTN_KEYP_X+0*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+3*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+1*(BTN_W+BTN_S),BTN_KEYP_Y+3*(BTN_H+BTN_S), BTN_KEYP_X+1*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+3*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+2*(BTN_W+BTN_S),BTN_KEYP_Y+3*(BTN_H+BTN_S), BTN_KEYP_X+2*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+3*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+0*(BTN_W+BTN_S),BTN_KEYP_Y+4*(BTN_H+BTN_S), BTN_KEYP_X+0*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+4*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+1*(BTN_W+BTN_S),BTN_KEYP_Y+4*(BTN_H+BTN_S), BTN_KEYP_X+1*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+4*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+2*(BTN_W+BTN_S),BTN_KEYP_Y+4*(BTN_H+BTN_S), BTN_KEYP_X+2*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+4*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+0*(BTN_W+BTN_S),BTN_KEYP_Y+5*(BTN_H+BTN_S), BTN_KEYP_X+0*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+5*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+1*(BTN_W+BTN_S),BTN_KEYP_Y+5*(BTN_H+BTN_S), BTN_KEYP_X+1*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+5*(BTN_H+BTN_S)+BTN_H },
    {BTN_KEYP_X+2*(BTN_W+BTN_S),BTN_KEYP_Y+5*(BTN_H+BTN_S), BTN_KEYP_X+2*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+5*(BTN_H+BTN_S)+BTN_H },
};
static const int KeypadCount = sizeof(UI_Keypad)/sizeof(UI_Keypad[0]);
static const char UI_KeyLabels[] = {
              '\x1B',
    '7', '8', '9',
    '4', '5', '6',
    '1', '2', '3',
    '0', '.', '-',
    '\x19', '\x18', '\xB6',
};
static const char KeyPadKeys[] = { 
              '\x08', 
    '7', '8', '9', 
    '4', '5', '6', 
    '1', '2', '3',
    '0', '.', '-', 
    '<', '>', '\n' };


// ##### Settings  #############################################################
//
// +---------------------------------------------------------------------------+
// |                                                Progam Name and version    |
// |                                                Manufacturer name          |
// |                                                Build Date                 |
// | Signal Generator Mode                                                     |
// |                                                                  \ | /    |
// |   ( * ) Continuous                                               = O =    |
// |                                                                  / | \    |
// |   (   ) One-Shot                                               +--------+ |
// |                                                                |        | |
// |                                                                |        | |
// |                                                                |        | |
// |                                                                |        | |
// |                                                                |        | |
// |                                                                |--------| |
// |                                                                |        | |
// | [    ...   ]                                                   |        | |
// |                                                                |        | |
// |                                                                |        | |
// |                                                                +--------+ |
// +---------------------------------------------------------------------------+

static const point_t suncenter = { 450,85 };
static const rect_t sunray[] = {
    { 450-2, 85-25, 450+2, 85+25 },
    { 450-25,85-2,  450+25,85+2 }
};
static const rect_t sungraph = { 450-18,120+0, 450+18,265+0 };
static const rect_t inrgraph = { 450-16,120+2, 450+16,265-2 };

static const rect_t SignalMode =
    { 20,50, 20+140,70 };
static const char * SignalModeLabel = "Signal Mode";

static const rect_t radio_Cycles[] = { 
    { 40, 80, 40+120,100 },
    { 40,110, 40+120,130 }
};
static const int radio_CyclesCount = sizeof(radio_Cycles)/sizeof(radio_Cycles[0]);
static const char * PulseModeLabels[] = {
    "Continuous",
    "One-Shot"
};
#define UI_CyclesColor              Green
#define UI_CyclesBackColor          RGB(0,0,0)

// rect_t radio_Cycles[], radio_CyclesCount, char * PulseModeLabels[]

#define PI 3.1415       // Handy value

template <typename T> int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}


// #############################################################################

SignalGenDisplay::SignalGenDisplay(RA8875 * _lcd, SignalGenDAC * _signal, const char * _Path,
    const char * _ProgName, const char * _Manuf, const char * _Ver, const char * _Build) :
    lcd(_lcd), signal(_signal), Path(_Path), ProgName(_ProgName), Manuf(_Manuf), Ver(_Ver), Build(_Build) {
    char buf[50];
    
    snprintf(buf, sizeof(buf), "%s/SigGen.ini", Path);
    ini.SetFile(buf, 2);
    needsInit = true;
}


SignalGenDisplay::~SignalGenDisplay() {
}




char SignalGenDisplay::GetTouchEvent(void) {
    TouchCode_t touch;
    
    touch = lcd->TouchPanelReadable();                      // any touch to report?
    if (touch == no_touch) {
        timerForceTSCal.stop();
        timerForceTSCal.reset();
    } else {
        uint8_t id = lcd->TouchID(0);                       // 'id' tracks the individual touches
        TouchCode_t ev = lcd->TouchCode(0);                 // 'ev'ent indicates no_touch, touch, held, release, ...
        point_t point = lcd->TouchCoordinates(0);           // and of course the (x,y) coordinates
        if (ev == touch) {
            timerRepeat.start();
            timerRepeat.reset();
            timerForceTSCal.start();
            timerForceTSCal.reset();
        } else if (ev == held && timerForceTSCal.read() > 10.0) {
            printf("Forcing T.S. Cal\r\n");
            timerForceTSCal.stop();
            timerForceTSCal.reset();
            lcd->cls();
            CalibrateTS();
            Refresh();
        }
        if ((ev == release) || (ev == held && timerRepeat.read_ms() > 250)) {
            timerRepeat.reset();
            switch (vis) {
                case VS_MainScreen:
                    // Start/Stop/Pulse
                    if (textLen == 0 && ev == release) {
                        if (lcd->Intersect(UI_START_STOP, point)) {
                            printf("Start/Stop/Pulse %d - %d : %c\r\n", pulseMode, signal->isRunning(), 
                                StartStopKeys[pulseMode ? 2 : signal->isRunning()]);
                            return StartStopKeys[pulseMode ? 2 : signal->isRunning()];
                        }
                    }
                    // Mode Keys touch
                    if (ev == release) {
                        for (int i=0; i<ModeCount; i++) {
                            if (lcd->Intersect(ModeButtons[i], point)) {
                                return ModeKeys[i];
                            }
                        }
                    }
                    // Parameters
                    if (ev == release) {
                        for (int i=0; i<ParameterCount; i++) {
                            if (lcd->Intersect(Parameters[i], point)) {
                                return ParameterKeys[i];
                            }
                        }
                    }
                    // Keypad
                    if (1 || ev == release) {
                        for (int i=0; i<KeypadCount; i++) {
                            if (lcd->Intersect(UI_Keypad[i], point)) {
                                return KeyPadKeys[i];
                            }
                        }
                    }
                    
                    if (ev == release) {
                        if (lcd->Intersect(NavToSettings, point)) {
                            vis = VS_Settings;
                            Refresh();
                            while (lcd->TouchPanelReadable())
                                ;
                            Thread::wait(100);
                        }
                    }
                    break;

                case VS_Settings:
                    Thread::wait(20);
                    if (lcd->Intersect(sungraph, point)) {
                        float bl = (float)(sungraph.p2.y - point.y)/(sungraph.p2.y - sungraph.p1.y);
                        lcd->Backlight(rangelimit(bl, 0.1, 1.0));
                        SaveSettings(OM_BACKL);
                        ShowBrightnessSetting();
                    }
                    if (ev == release) {
                        if (lcd->Intersect(NavToSettings, point)) {
                            // Switch to main screen
                            vis = VS_MainScreen;
                            Refresh();
                            while (lcd->TouchPanelReadable())
                                ;
                            Thread::wait(100);
                            ShowMenu();
                        }
                    }
                    
                    if (ev == release) {
                        for (int i=0; i<radio_CyclesCount; i++) {
                            if (lcd->Intersect(radio_Cycles[i], point)) {
                                pulseMode = i;
                                SaveSettings(OM_PULSE);
                                signal->Stop();
                                ShowCyclesControl();
                            }
                        }
                    }
                    break;
            }
        }
    }
    return 0;
}


void SignalGenDisplay::Refresh() {
    if (needsInit) {
        char buf[100];

        needsInit = false;
        vis = VS_MainScreen;    // always start on main screen
        lcd->TouchPanelInit();
        InitializeTS();

        // Default the backlight
        ini.ReadString("Settings", "Backlight", buf, sizeof(buf), "60");
        lcd->Backlight_u8(atoi(buf));
    
        ini.ReadString("Signal", "Waveform", buf, sizeof(buf), ModeNames[0]);
        for (int i=0; i<ModeCount; i++) {
            if (strcmp(ModeNames[i], buf) == 0) {
                mode = (SG_Waveform)i;
                printf("Read ini mode is %d\r\n", mode);
                break;
            }
        }
        ini.ReadString("Signal", "Duty Cycle", buf, sizeof(buf), "50");
        dutycycle = atof(buf);
    
        ini.ReadString("Signal", "Frequency", buf, sizeof(buf), "1000");
        frequency = atof(buf);
    
        ini.ReadString("Signal", "Voltage", buf, sizeof(buf), "3.0");
        voltage = atof(buf);
    
        ini.ReadString("Signal", "Offset", buf, sizeof(buf), "1.5");
        offset = atof(buf);
        
        ini.ReadString("Signal", "Pulse Mode", buf, sizeof(buf), PulseModeLabels[0]);
        for (int i=0; i<radio_CyclesCount; i++) {
            if (strcmp(PulseModeLabels[i], buf) == 0) {
                pulseMode = i;
                break;
            }
        }
        ShowMenu();
    }
    switch (vis) {
        case VS_MainScreen:
            lcd->background(UI_BackColor);
            lcd->cls(1);
            lcd->SelectDrawingLayer(0);
            // Clear Screen
            lcd->SetLayerMode(RA8875::ShowLayer0);
            
            // Product Info
            lcd->foreground(UI_ProductNameColor);
            ShowProductInfo();
            
            ClearScope();
            resetDataEntry();
            DrawNavGadget();
            DrawModeButtons();
            break;
            
        case VS_Settings:
            lcd->background(UI_BackColor);
            lcd->cls(2);
            lcd->SelectDrawingLayer(1);
            lcd->SetLayerMode(RA8875::ShowLayer1);
            lcd->foreground(UI_ProductNameColor);
            ShowProductInfo(true);
            ShowCyclesControl();
            ShowBrightnessSetting();
            DrawNavGadget();
            break;
    }
}

// rect_t radio_Cycles[], radio_CyclesCount, char * PulseModeLabels[]

void SignalGenDisplay::ShowCyclesControl(void) {
    lcd->fillrect(SignalMode, UI_CyclesBackColor);
    lcd->foreground(UI_CyclesColor);
    lcd->background(UI_CyclesBackColor);
    lcd->SetTextCursor(SignalMode.p1.x+1, SignalMode.p1.y+1);
    lcd->printf("%s", SignalModeLabel);
    for (int x=0; x<radio_CyclesCount; x++) {
        lcd->fillrect(radio_Cycles[x], UI_CyclesBackColor); 
        lcd->foreground(UI_CyclesColor);
        lcd->background(UI_CyclesBackColor);
        lcd->SetTextCursor(radio_Cycles[x].p1.x+1,radio_Cycles[x].p1.y+1);
        lcd->printf("%c %s", (pulseMode == x) ? '\x07' : '\x09', PulseModeLabels[x]);
    }
}


void SignalGenDisplay::DrawModeButtons(void) {
    for (int i=0; i<ModeCount; i++) {
        DrawButton(ModeButtons[i], (UI_ModeList[i] == mode) ? true : false, UI_ModeList[i], true);
    }
    UpdateScope();
}

void SignalGenDisplay::DrawNavGadget(void) {
    lcd->fillrect(NavToSettings, Black);
    lcd->SetTextCursor(NavToSettings.p1.x+1, NavToSettings.p1.y+1);
    lcd->foreground(White);
    lcd->background(Black);
    lcd->puts("  ...");
}


void SignalGenDisplay::ShowProductInfo(bool builddate) {
    rect_t r = UI_PROD_RECT;
    lcd->window(r);
    lcd->SetTextCursor(r.p1.x, r.p1.y);
    lcd->printf("%s v%s", ProgName, Ver);
    lcd->SetTextCursor(r.p1.x, r.p1.y+16);
    lcd->printf("by %s", Manuf);
    if (builddate) {
        lcd->SetTextCursor(r.p1.x, r.p1.y+32);
        lcd->printf("%s", Build);
    }
    lcd->window();
}

void SignalGenDisplay::ShowBrightnessSetting(void) {
    int i;
    // Sunbeam
    lcd->fillrect(sunray[0], White);
    lcd->fillrect(sunray[1], White);
    for (i=-2; i<=+2; i++) {
        lcd->line(
            (sunray[0].p1.x + sunray[1].p1.x)/2 - 5 + i, (sunray[0].p1.y + sunray[1].p1.y)/2 - 5 - i, 
            (sunray[0].p2.x + sunray[1].p2.x)/2 + 5 + i, (sunray[0].p2.y + sunray[1].p2.y)/2 + 5 - i, 
            White);
        lcd->line(
            (sunray[0].p2.x + sunray[1].p1.x)/2 - 5 + i, (sunray[0].p2.y + sunray[1].p1.y)/2 + 5 + i, 
            (sunray[0].p1.x + sunray[1].p2.x)/2 + 5 + i, (sunray[0].p1.y + sunray[1].p2.y)/2 - 5 + i, 
            White);
    }
    lcd->fillcircle(suncenter, 18, UI_BackColor);
    lcd->fillcircle(suncenter, 12, White);
    lcd->rect(sungraph, Blue);
    float bl = lcd->GetBacklight();
    lcd->fillrect(inrgraph, UI_BackColor);
    lcd->fillrect(inrgraph.p1.x,inrgraph.p2.y, inrgraph.p2.x, inrgraph.p2.y - bl * (inrgraph.p2.y - inrgraph.p1.y), White);
}

void SignalGenDisplay::ShowMenu(void) {
    if (Manuf) {
        printf("\r\n%s v%s by %s build %s\r\n\r\n", ProgName, Ver, Manuf, Build);
    }
    printf(" Select:                  Signal:\r\n");
    printf("      S:  Sine Wave            d:  Duty Cycle\r\n");
    printf("      Q:  Square Wave          f:  Frequency\r\n");
    printf("      T:  Triangle Wave        p:  Period\r\n");
    printf("      W:  Sawtooth Wave        v:  Voltage\r\n");
    printf("      U:  User Wave            o:  Offset\r\n");
    printf("                                   \r\n");
    printf("                        0-9 . - :  Numeric entry\r\n");
    printf("                            < > :  Modify selected signal\r\n");
    printf("                            <bs>:  Backspace entry\r\n");
    printf("      ?: This help          <cr>:  Save number\r\n");
    printf("      #: Dump RA8875       <esc>:  Exit number entry\r\n");
    //printf("  4:  Reverse sawtoothSignal\r\n");
    
    printf(" Settings:\r\n");
    printf("    Mode: %d - %s\r\n", mode, ModeNames[mode]);
    printf("    Freq: %f\r\n", frequency);
    printf("    Duty: %f\r\n", dutycycle);
    printf("    Volt: %f\r\n", voltage);
    printf("    Offs: %f\r\n", offset);
    printf("    Puls: %d - %s\r\n", pulseMode, PulseModeLabels[pulseMode]);
}

SignalGenDisplay::OM_Changes SignalGenDisplay::Poll(char c) {
    OM_Changes ret = OM_NONE;
    
    if (needsInit)
        Refresh();      // If Poll was the first API call, we need to init
    SaveSettings();
    if (!c) {
        c = GetTouchEvent();
    }
    if (c) {
        printf("%02X: EntryMd: %d, textLen: %d [%s] VIS: %d\r\n", c, EntryMd, textLen, textBuffer, vis);
    }
    /// 01234567890-. #?SQTW dfpvo < > <bs> <enter> <esc>
    switch (c) {
        case '#':
            printf("DumpRegisters for RA8875 unsupported\r\n");
            //lcd->DumpRegisters();
            break;
        case '?':
            ShowMenu();
            break;
        case 'G':       // Go is 'Start'
            signal->Start(false);
            ShowStartStop(true);
            break;
        case 'O':       // Off
            signal->Stop();
            ShowStartStop(true);
            break;
        case 'P':       // 'P'ulse
            signal->Start(true);
            ShowStartStop(true);
            break;
        case 'S':
            if (mode != SG_SINE)
                SaveSettings(OM_MODE);
            SetWaveformMode(SG_SINE);
            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            //ret = SG_SINE;
            break;
        case 'Q':
            if (mode != SG_SQUARE)
                SaveSettings(OM_MODE);
            SetWaveformMode(SG_SQUARE);
            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            //ret = SG_SQUARE;
            break;
        case 'T':
            if (mode != SG_TRIANGLE)
                SaveSettings(OM_MODE);
            SetWaveformMode(SG_TRIANGLE);
            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            //ret = SG_TRIANGLE;
            break;
        case 'W':
            if (mode != SG_SAWTOOTH)
                SaveSettings(OM_MODE);
            SetWaveformMode(SG_SAWTOOTH);
            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            //ret = SG_SAWTOOTH;
            break;
        case 'U':
            if (mode != SG_USER)
                SaveSettings(OM_MODE);
            SetWaveformMode(SG_USER);
            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            //ret = SG_USER;
            break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '.':
        case '-':
            if (EntryMd) {
                if (textLen<8) {
                    textBuffer[textLen++] = c;
                    textBuffer[textLen] = '\0';
                    updateTextWindow();
                }
            }
            break;
        case '\x08':
            if (EntryMd) {
                if (textLen) {
                    textLen--;
                    textBuffer[textLen] = '\0';
                    updateTextWindow();
                }
                if (textLen == 0)
                    clearTextWindow();
            }
            break;
        case '\x1B':
            textBuffer[0] = '\0';
            textLen = 0;
            resetDataEntry();
            break;
        case '\r':
        case '\n':
            if (EntryMd) {
                if (strlen(textBuffer)) {
                    switch (EntryMd) {
                        case OM_DUTY:
                            SetDutyCycle(atof(textBuffer));
                            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                            SaveSettings(OM_DUTY);
                            break;
                        case OM_FREQ:
                            SetFrequency(atof(textBuffer));
                            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                            SaveSettings(OM_FREQ);
                            break;
                        case OM_PERI:
                            SetPeriod(atof(textBuffer));
                            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                            SaveSettings(OM_FREQ);
                            break;
                        case OM_VOLT:
                            SetVoltagePeakToPeak(atof(textBuffer));
                            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                            SaveSettings(OM_VOLT);
                            break;
                        case OM_OFFS:
                            SetVoltageOffset(atof(textBuffer));
                            signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                            SaveSettings(OM_OFFS);
                            break;
                        default:
                            break;
                    }
                }
                resetDataEntry(OM_NONE, true);
            }
            break;
        case '>':
            switch (EntryMd) {
                case OM_DUTY:
                    SetDutyCycle(dutycycle + 1.0);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_DUTY);
                    break;
                case OM_FREQ:
                    SetFrequency(frequency + 1.0);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_FREQ);
                    break;
                case OM_PERI:
                    SetPeriod(1/frequency + 0.000001);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_FREQ);
                    break;
                case OM_VOLT:
                    SetVoltagePeakToPeak(voltage + 0.1);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_VOLT);
                    break;
                case OM_OFFS:
                    SetVoltageOffset(offset + 0.1);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_OFFS);
                    break;
                default:
                    break;
            }
            break;
        case '<':
            switch (EntryMd) {
                case OM_DUTY:
                    SetDutyCycle(dutycycle - 1.0);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_DUTY);
                    break;
                case OM_FREQ:
                    SetFrequency(frequency - 1.0);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_FREQ);
                    break;
                case OM_PERI:
                    SetPeriod(1/frequency - 0.000001);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_FREQ);
                    break;
                case OM_VOLT:
                    SetVoltagePeakToPeak(voltage - 0.1);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_VOLT);
                    break;
                case OM_OFFS:
                    SetVoltageOffset(offset - 0.1);
                    signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
                    SaveSettings(OM_OFFS);
                    break;
                default:
                    break;
            }
            break;
        case 'd':
            if (EntryMd != OM_DUTY) {
                SaveSettings(EntryMd);
                resetDataEntry(OM_DUTY, true);
                updateDutyCycle();
                signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            } else {
                resetDataEntry(OM_NONE, true);
            }
            break;
        case 'f':
            if (EntryMd != OM_FREQ) {
                SaveSettings(EntryMd);
                resetDataEntry(OM_FREQ, true);
                updateFrequency();
                signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            } else {
                resetDataEntry(OM_NONE, true);
            }
            break;
        case 'p':
            if (EntryMd != OM_PERI) {
                SaveSettings(EntryMd);
                resetDataEntry(OM_PERI, true);
                updatePeriod();
                signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            } else {
                resetDataEntry(OM_NONE, true);
            }
            break;
        case 'v':
            if (EntryMd != OM_VOLT) {
                SaveSettings(EntryMd);
                resetDataEntry(OM_VOLT, true);
                updateVoltage();
                signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            } else {
                resetDataEntry(OM_NONE, true);
            }
            break;
        case 'o':
            if (EntryMd != OM_OFFS) {
                SaveSettings(EntryMd);
                resetDataEntry(OM_OFFS, true);
                updateOffset();
                signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
            } else {
                resetDataEntry(OM_NONE, true);
            }
            break;
        default:
            break;
    }
    return ret;
}

bool SignalGenDisplay::SetWaveformMode(SG_Waveform _mode, bool force) {
    if (/* _mode >= SG_SINE && */ _mode <= SG_USER) {
        mode = _mode;
        printf("mode is %d\r\n", mode);
        DrawModeButtons();
        return true;
    } else {
        return false;
    }
}

bool SignalGenDisplay::SetDutyCycle(float _dutyCycle) {
    if (_dutyCycle >= 5 && _dutyCycle <= 95) {
        dutycycle = _dutyCycle;
        updateDutyCycle();
        UpdateScope();
        return true;
    } else {
        return false;
    }
}

bool SignalGenDisplay::SetFrequency(float _frequency) {
    if (_frequency >= 1.0 && _frequency <= 1.0E6) {
        frequency = _frequency;
        updateFrequency();
        updatePeriod();
        UpdateScope();
        return true;
    } else {
        return false;
    }
}

bool SignalGenDisplay::SetPeriod(float _period) {
    if (_period >= 1.0E-6 && _period <= 1.0) {
        frequency = 1/_period;
        updatePeriod();
        updateFrequency();
        UpdateScope();
        return true;
    } else {
        return false;
    }
}

bool SignalGenDisplay::SetVoltagePeakToPeak(float _voltage) {
    if (_voltage >= 0.0 && _voltage <= 3.3) {
        voltage = _voltage;
        updateVoltage();
        UpdateScope();
        return true;
    } else {
        return false;
    }
}

bool SignalGenDisplay::SetVoltageOffset(float _voltage) {
    if (_voltage > -SG_MAX_V && _voltage < SG_MAX_V) {
        if (abs(_voltage) < 0.008)     // if binary precision slips it, fix it
            _voltage = 0.0;
        offset = _voltage;
        updateOffset();
        UpdateScope();
        return true;
    } else {
        return false;
    }
}

// ########################   Private Methods past here #######################

void SignalGenDisplay::UpdateScope(void) {
    ClearScope();
    rect_t r = UI_WAVE_RECT;

    float vPeakPos, vPeakNeg;
    dim_t waveHeight = (UI_WAVE_RECT.p2.y - UI_WAVE_RECT.p1.y);
    vPeakPos = rangelimit(offset + voltage/2, SG_MIN_V, SG_MAX_V);
    vPeakNeg = rangelimit(offset - voltage/2, SG_MIN_V, SG_MAX_V);
    loc_t markerPos_y = UI_WAVE_RECT.p2.y - vPeakPos/(SG_MAX_V-SG_MIN_V) * waveHeight;
    loc_t markerNeg_y = UI_WAVE_RECT.p2.y - vPeakNeg/(SG_MAX_V-SG_MIN_V) * waveHeight;
    loc_t df = rangelimit(offset, SG_MIN_V, SG_MAX_V) / SG_MAX_V * (r.p2.y - r.p1.y);
    loc_t y;

    lcd->SelectUserFont(BPG_Arial08x08);
    lcd->background(UI_ScopeBackColor);
    // Draw the Waveform rectangle
    lcd->rect(UI_WAVE_RECT, WaveOutlineColor);
    
    // Draw the Peak to Peak markers
    lcd->line(UI_WAVE_RECT.p1.x-3,markerPos_y, UI_WAVE_RECT.p2.x+3*SC_RIGHT_MARGIN/4,markerPos_y,   UI_VP2PColor);
    lcd->line(UI_WAVE_RECT.p1.x-3,markerNeg_y, UI_WAVE_RECT.p2.x+3*SC_RIGHT_MARGIN/4,markerNeg_y,   UI_VP2PColor);
    lcd->line(r.p2.x+3*SC_RIGHT_MARGIN/4-3,markerPos_y, r.p2.x+3*SC_RIGHT_MARGIN/4-3,markerNeg_y, UI_VP2PColor);  // vert
    lcd->filltriangle(                              // top arrowhead
        r.p2.x+3*SC_RIGHT_MARGIN/4-3,  markerPos_y,
        r.p2.x+3*SC_RIGHT_MARGIN/4-3+2,markerPos_y+3,
        r.p2.x+3*SC_RIGHT_MARGIN/4-3-2,markerPos_y+3,
        UI_VP2PColor);
    lcd->filltriangle(                              // bottom arrowhead
        r.p2.x+3*SC_RIGHT_MARGIN/4-3,  markerNeg_y,
        r.p2.x+3*SC_RIGHT_MARGIN/4-3+2,markerNeg_y-3,
        r.p2.x+3*SC_RIGHT_MARGIN/4-3-2,markerNeg_y-3,
        UI_VP2PColor);
    lcd->SetTextCursor(r.p2.x+3*SC_RIGHT_MARGIN/4-3 - 10,  markerPos_y - 9);
    lcd->printf("%3.2f", vPeakPos);
    lcd->SetTextCursor(r.p2.x+3*SC_RIGHT_MARGIN/4-3 - 10,  markerNeg_y + 3);
    lcd->printf("%3.2f", vPeakNeg);

    // Draw the offset voltage markers
    y = r.p2.y - df;
    dim_t w = (r.p2.x + SC_RIGHT_MARGIN/3 - r.p1.x) / 35;
    for (int i=0; i<=35+1; i++) {       // dashed line
        if ((i & 1) == 0) {
            lcd->line(r.p1.x + i * w,y, r.p1.x + (i+1) * w, y, UI_VOffsetColor);
        }
    }
    switch (sgn(offset)) {
        default:
        case 0:
            break;
        case -1:
        case 1:
            lcd->line(r.p2.x+SC_RIGHT_MARGIN/3-3,r.p2.y, r.p2.x+SC_RIGHT_MARGIN/3-3,y, UI_VOffsetColor); // vert
            lcd->filltriangle(
                r.p2.x+SC_RIGHT_MARGIN/3-3,y, 
                r.p2.x+SC_RIGHT_MARGIN/3-3+2,y+sgn(offset)*3, 
                r.p2.x+SC_RIGHT_MARGIN/3-3-2,y+sgn(offset)*3, 
                UI_VOffsetColor);
            lcd->line(r.p2.x,r.p2.y, r.p2.x+SC_RIGHT_MARGIN/3,r.p2.y, UI_VOffsetColor);   // horz
            break;
    }
    lcd->SetTextCursor(r.p2.x+SC_RIGHT_MARGIN/3-3 - 8,  y - 10);
    lcd->printf("%3.2f", offset);

    // Draw the Frequency marker
    w = r.p2.x - r.p1.x;
    dim_t dc = dutycycle/100.0 * 1*w/2;
    lcd->line(r.p1.x,r.p1.y-3, r.p1.x,r.p2.y+3*SC_BOT_MARGIN/4, UI_FreqColor);
    lcd->line(r.p1.x+1*w/2,r.p1.y-3, r.p1.x+1*w/2,r.p2.y+3*SC_BOT_MARGIN/4, UI_FreqColor);
    lcd->line(r.p1.x,r.p2.y+3*SC_BOT_MARGIN/4-3, r.p1.x+1*w/2,r.p2.y+3*SC_BOT_MARGIN/4-3, UI_FreqColor);
    lcd->filltriangle(
        r.p1.x+0,r.p2.y+3*SC_BOT_MARGIN/4-3,
        r.p1.x+3,r.p2.y+3*SC_BOT_MARGIN/4-3-2,
        r.p1.x+3,r.p2.y+3*SC_BOT_MARGIN/4-3+2,
        UI_FreqColor);
    lcd->filltriangle(
        r.p1.x+1*w/2-0,r.p2.y+3*SC_BOT_MARGIN/4-3,
        r.p1.x+1*w/2-3,r.p2.y+3*SC_BOT_MARGIN/4-3-2,
        r.p1.x+1*w/2-3,r.p2.y+3*SC_BOT_MARGIN/4-3+2,
        UI_FreqColor);
    
    // Draw the Duty Cycle markers
    lcd->line(r.p1.x,r.p1.y-3, r.p1.x,r.p2.y+2*SC_BOT_MARGIN/4, UI_DutyColor);
    lcd->line(r.p1.x + dc,r.p1.y-3, r.p1.x + dc,r.p2.y+2*SC_BOT_MARGIN/4, UI_DutyColor);
    point_t p;
    p.x = r.p1.x;
    p.y = r.p2.y+2*SC_BOT_MARGIN/4-3;
    lcd->line(p.x,p.y, p.x+dc,p.y, UI_DutyColor);
    lcd->filltriangle(
        p.x,p.y,
        p.x+3,p.y-2,
        p.x+3,p.y+2,
        UI_DutyColor);
    p.x = r.p1.x + dc;
    lcd->filltriangle(
        p.x,p.y,
        p.x-3,p.y-2,
        p.x-3,p.y+2,
        UI_DutyColor);
    lcd->SetTextCursor(p.x + 3,  p.y-4);
    float period = dutycycle/100*1/frequency; 
    if (period < 0.001)
        lcd->printf("%8.3f uS", period * 1000000);
    else
        lcd->printf("%8.3f mS", period * 1000);

    lcd->SelectUserFont();        // restore
    DrawWaveform(r, mode, White);
}


//       ++           +----+            +               +
//      .  .          |    |           / \            / |
//     .    +         |    |    |     /   +   /     +   |
//           .             |    |          \ /     /    |
//            ++           +----+           +     +     +
//
void SignalGenDisplay::DrawWaveform(rect_t r, SG_Waveform mode, color_t color, bool drawPure) {
    loc_t x,y;
    loc_t y0 = (r.p1.y + r.p2.y)/2;
    dim_t w = r.p2.x - r.p1.x;
    dim_t h = r.p2.y - r.p1.y;
    dim_t privDutyCycleX;
    dim_t a = (r.p2.y - r.p1.y)/2;
    float privVoltage = voltage;
    float privOffset = offset;
    float privDutyCycle = dutycycle;
    float vRange = SG_MAX_V - SG_MIN_V;
    float v;

    if (drawPure) {
        privVoltage = vRange;
        privOffset = vRange/2;
        privDutyCycle = 50;
    }
    privDutyCycleX = privDutyCycle/100.0 * 1*w/2;
    int cyclesToShow = (pulseMode) ? 1 : 2;
    switch (mode) {
        case SG_SINE:
            for (int cycle=0; cycle<cyclesToShow; cycle++) {
                for (x=0; x<=privDutyCycleX; x++) {
                    v = privOffset + privVoltage/2 * sin(x * 1 * PI / privDutyCycleX);
                    v = rangelimit(v, SG_MIN_V, SG_MAX_V);
                    y = r.p2.y - 2 * a * v / vRange;
                    lcd->pixel(r.p1.x + cycle * w/2 + x, y, color);
                }
                for (x=0; x<=(w/2-privDutyCycleX); x++) {
                    v = privOffset - privVoltage/2 * sin(x * 1 * PI / (w/2-privDutyCycleX));
                    v = rangelimit(v, SG_MIN_V, SG_MAX_V);
                    y = r.p2.y - 2 * a * v / vRange;
                    lcd->pixel(r.p1.x + cycle * w/2 + privDutyCycleX + x, y, color);
                }
            }
            break;
        case SG_SQUARE:
            for (int cycle=0; cycle<cyclesToShow; cycle++) {
                loc_t mid = r.p2.y - rangelimit(privOffset, SG_MIN_V, SG_MAX_V) / vRange * h;
                loc_t upp = r.p2.y - rangelimit(privOffset + privVoltage/2, SG_MIN_V, SG_MAX_V) / vRange * h;
                loc_t low = r.p2.y - rangelimit(privOffset - privVoltage/2, SG_MIN_V, SG_MAX_V) / vRange * h;
                lcd->line(r.p1.x+cycle*w/2+0*w/8, mid, r.p1.x+cycle*w/2+0*w/8, upp, color);   // rise
                lcd->line(r.p1.x+cycle*w/2+0*w/8, upp, r.p1.x+cycle*w/2+privDutyCycleX, upp, color);      // horz
                lcd->line(r.p1.x+cycle*w/2+privDutyCycleX, upp, r.p1.x+cycle*w/2+privDutyCycleX, low, color);         // fall
                lcd->line(r.p1.x+cycle*w/2+privDutyCycleX, low, r.p1.x+cycle*w/2+4*w/8, low, color);      // horz
                lcd->line(r.p1.x+cycle*w/2+4*w/8, low, r.p1.x+cycle*w/2+4*w/8, mid, color);   // rise
            }
            break;
        case SG_TRIANGLE:
            for (int cycle=0; cycle<cyclesToShow; cycle++) {
                for (x=0; x<=privDutyCycleX; x++) {
                    v = privVoltage * (float)x/privDutyCycleX;
                    if (x < privDutyCycleX/2)
                        v += privOffset;
                    else
                        v = privVoltage - (v - privOffset);
                    y = r.p2.y - rangelimit(v, SG_MIN_V, SG_MAX_V) / vRange * h;
                    lcd->pixel(r.p1.x + cycle * w/2 + x, y, color);
                }
                dim_t phaseWidth = (w/2 - privDutyCycleX);
                for (x=0; x<phaseWidth; x++) {
                    v = privVoltage * (float)x/phaseWidth;
                    if (x < phaseWidth/2)
                        v = privOffset - v;
                    else
                        v = v + privOffset - privVoltage;
                    y = r.p2.y - rangelimit(v, SG_MIN_V, SG_MAX_V) / vRange * h;
                    lcd->pixel(r.p1.x + cycle * w/2 + privDutyCycleX + x, y, color);
                }
            }
            break;
        case SG_SAWTOOTH:
            for (int cycle=0; cycle<cyclesToShow; cycle++) {
                for (x=0; x<=privDutyCycleX; x++) {
                    v = privVoltage/2 * (float)x/privDutyCycleX - privVoltage/2 + privOffset;
                    y = r.p2.y - rangelimit(v, SG_MIN_V, SG_MAX_V) / vRange * h;
                    lcd->pixel(r.p1.x + cycle * w/2 + x, y, color);
                }
                dim_t phaseWidth = (w/2 - privDutyCycleX);
                for (x=0; x<phaseWidth; x++) {
                    v = privVoltage/2 * (float)x/phaseWidth + privOffset;
                    y = r.p2.y - rangelimit(v, SG_MIN_V, SG_MAX_V) / vRange * h;
                    lcd->pixel(r.p1.x + cycle * w/2 + privDutyCycleX + x, y, color);
                }
                loc_t y2 = r.p2.y - rangelimit(-privVoltage/2 + privOffset, SG_MIN_V, SG_MAX_V) / vRange * h;
                lcd->line(r.p1.x + cycle*w/2 + w/2 - 1, y,
                    r.p1.x + cycle*w/2 + w/2, y2);
            }
            break;
        case SG_USER:
            lcd->line(r.p1.x, y0-1, r.p1.x+w, y0-1, color);
            lcd->line(r.p1.x, y0-0, r.p1.x+w, y0-0, color);
            lcd->line(r.p1.x, y0+1, r.p1.x+w, y0+1, color);
            lcd->rect(r.p1.x+5*w/8, y0-a/4, r.p1.x+7*w/8, y0+a/4, color);
            break;
    }
    if (cyclesToShow != 2) {
        v = rangelimit(privOffset, SG_MIN_V, SG_MAX_V);
        y = r.p2.y - 2 * a * v / vRange;
        lcd->line(r.p1.x + w/2, y, r.p1.x+w,y, color);
    }
}

void SignalGenDisplay::ClearScope(void) {
    // Scope area
    rect_t r = UI_SCOPE_RECT;
    lcd->fillrect(r, UI_ScopeBackColor);
    lcd->rect(r, UI_ScopeFrameColor);
}

void SignalGenDisplay::updateDutyCycle(void) {
    rect_t r = Parameters[0];   // UI_DUTY_CYCLE_RECT;
    color_t fcolor, bcolor;
    
    if (EntryMd != OM_DUTY) {
        fcolor = UI_DutyColor;
        bcolor = UI_ScopeBackColor;
    } else {
        fcolor = UI_ScopeBackColor;
        bcolor = UI_DutyColor;
    }
    lcd->fillrect(r, bcolor);
    lcd->foreground(fcolor);
    lcd->background(bcolor);
    lcd->SetTextCursor(r.p1.x+1, r.p1.y+1);
    lcd->printf("%3.0f %%", dutycycle);
}

void SignalGenDisplay::updateFrequency(void) {
    rect_t r = Parameters[1];   // UI_FREQ_RECT;
    color_t fcolor, bcolor;
    
    if (EntryMd != OM_FREQ) {
        fcolor = UI_FreqColor;
        bcolor = UI_ScopeBackColor;
    } else {
        fcolor = UI_ScopeBackColor;
        bcolor = UI_FreqColor;
    }
    lcd->fillrect(r, bcolor);
    lcd->foreground(fcolor);
    lcd->background(bcolor);
    lcd->SetTextCursor(r.p1.x+1, r.p1.y+1);
    if (frequency >= 1000000.0)
        lcd->printf("%8.3f MHz", frequency/1000000);
    else if (frequency >= 1000.0)
        lcd->printf("%8.3f kHz", frequency/1000);
    else
        lcd->printf("%8.3f Hz ", frequency);    
}

void SignalGenDisplay::updatePeriod(void) {
    float period = 1/frequency;
    rect_t r = Parameters[2];   // UI_PERIOD_RECT;
    color_t fcolor, bcolor;

    if (EntryMd != OM_PERI) {
        fcolor = UI_FreqColor;
        bcolor = UI_ScopeBackColor;
    } else {
        fcolor = UI_ScopeBackColor;
        bcolor = UI_FreqColor;
    }
    lcd->fillrect(r, bcolor);
    lcd->foreground(fcolor);
    lcd->background(bcolor);
    lcd->SetTextCursor(r.p1.x+1, r.p1.y+1);
    if (period < 0.001)
        lcd->printf("%8.3f uS", period * 1000000);
    else
        lcd->printf("%8.3f mS", period * 1000);
}

void SignalGenDisplay::updateVoltage(void) {
    rect_t r = Parameters[3];   // UI_VP2P_RECT;
    color_t fcolor, bcolor;

    if (EntryMd != OM_VOLT) {
        fcolor = UI_VP2PColor;
        bcolor = UI_ScopeBackColor;
    } else {
        fcolor = UI_ScopeBackColor;
        bcolor = UI_VP2PColor;
    }
    lcd->fillrect(r, bcolor);
    lcd->foreground(fcolor);
    lcd->background(bcolor);
    lcd->SetTextCursor(r.p1.x+1, r.p1.y+1);
    lcd->printf("%5.1f v", voltage);
}

void SignalGenDisplay::updateOffset(void) {
    rect_t r = Parameters[4];   // UI_VOFFSET_RECT;
    color_t fcolor, bcolor;

    if (EntryMd != OM_OFFS) {
        fcolor = UI_VOffsetColor;
        bcolor = UI_ScopeBackColor;
    } else {
        fcolor = UI_ScopeBackColor;
        bcolor = UI_VOffsetColor;
    }
    lcd->fillrect(r, bcolor);
    lcd->foreground(fcolor);
    lcd->background(bcolor);
    lcd->SetTextCursor(r.p1.x+1, r.p1.y+1);
    lcd->printf("%+4.2f v", offset);
}

void SignalGenDisplay::DrawKeypadEnabled(bool enable) {
    for (int i=0; i<KeypadCount; i++) {
        DrawButton(UI_Keypad[i], false, SG_KEYPAD, enable, i);
    }
}

void SignalGenDisplay::DrawButton(rect_t r, bool pressed, SG_Waveform mode, bool enable, int label) {
    rect_t wave;
    color_t buttonface = UI_BUTTON_FACE_DISABLED;
    color_t buttonshadow = UI_BUTTON_SHADOW_DISABLED;
    
    //lcd->fillrect(r, UI_ScopeBackColor);
    if (pressed) {
        if (enable) {
            buttonface = UI_BUTTON_FACE_DN;
            buttonshadow = UI_BUTTON_SHADOW;
        }
        lcd->fillrect(r, buttonface);
        lcd->line(r.p1.x+0,r.p1.y+0, r.p2.x+0,r.p1.y+0, buttonshadow);      // top border
        lcd->line(r.p1.x+1,r.p1.y+1, r.p2.x+0,r.p1.y+1, buttonshadow);      // top border
        lcd->line(r.p1.x+2,r.p1.y+2, r.p2.x+0,r.p1.y+2, buttonshadow);      // top border
        lcd->line(r.p1.x+0,r.p1.y+0, r.p1.x+0,r.p2.y+0, buttonshadow);      // left border
        lcd->line(r.p1.x+1,r.p1.y+1, r.p1.x+1,r.p2.y+0, buttonshadow);      // left border
        lcd->line(r.p1.x+2,r.p1.y+2, r.p1.x+2,r.p2.y+0, buttonshadow);      // left border
        wave.p1.x = r.p1.x+5 + 2; wave.p1.y = r.p1.y + 5 + 2;
        wave.p2.x = r.p2.x-5 + 2; wave.p2.y = r.p2.y - 5 + 2;
    } else {
        if (enable) {
            buttonface = UI_BUTTON_FACE_UP;
            buttonshadow = UI_BUTTON_SHADOW;
        }
        lcd->fillrect(r, buttonface);
        lcd->line(r.p1.x+0,r.p2.y-0, r.p2.x-0,r.p2.y-0, buttonshadow);      // bottom border
        lcd->line(r.p1.x+0,r.p2.y-1, r.p2.x-1,r.p2.y-1, buttonshadow);      // bottom border
        lcd->line(r.p1.x+0,r.p2.y-2, r.p2.x-2,r.p2.y-2, buttonshadow);      // bottom border
        lcd->line(r.p2.x-0,r.p1.y+0, r.p2.x-0,r.p2.y-0, buttonshadow);      // right border
        lcd->line(r.p2.x-1,r.p1.y+0, r.p2.x-1,r.p2.y-1, buttonshadow);      // right border
        lcd->line(r.p2.x-2,r.p1.y+0, r.p2.x-2,r.p2.y-2, buttonshadow);      // right border
        wave.p1.x = r.p1.x+5 + 0; wave.p1.y = r.p1.y + 5 + 0;
        wave.p2.x = r.p2.x-5 + 0; wave.p2.y = r.p2.y - 5 + 0;
    }
    switch (mode) {
        case SG_SINE:
        case SG_SQUARE:
        case SG_TRIANGLE:
        case SG_SAWTOOTH:
        case SG_USER:
            DrawWaveform(wave, mode, Black, true);
            break;
        case SG_KEYPAD:
            lcd->foreground(Black);
            lcd->background(buttonface);
            lcd->SetTextCursor((r.p1.x+r.p2.x)/2 - 4,r.p1.y + BTN_H/2 - 8);     // 8x16 char
            lcd->putc(UI_KeyLabels[label]);
            break;
        case SG_START:
            lcd->foreground(Black);
            lcd->background(buttonface);
            lcd->SetTextCursor((r.p1.x+r.p2.x)/2 - 4 * strlen(UI_StartLabels[label]),r.p1.y + BTN_H/2 - 8);
            lcd->puts(UI_StartLabels[label]);
            break;
    }
}

void SignalGenDisplay::ShowStartStop(bool showIt) {
    if (textLen == 0) {
        lcd->fillrect(UI_START_STOP, UI_BackColor);
        if (showIt) {
            DrawButton(UI_START_STOP, signal->isRunning(), SG_START, true, pulseMode ? 2 : signal->isRunning());
        }
    }
}

void SignalGenDisplay::updateTextWindow(void) {
    ShowStartStop(false);
    lcd->window(UI_DATA_ENTRY);
    lcd->fillrect(UI_DATA_ENTRY, White);
    lcd->foreground(Black);
    lcd->background(White);
    lcd->SetTextCursor(UI_DATA_ENTRY.p1.x+1,UI_DATA_ENTRY.p1.y+1);
    lcd->printf("%13s", textBuffer);
    lcd->window();
}

void SignalGenDisplay::clearTextWindow(void) {
    lcd->fillrect(UI_DATA_ENTRY, UI_BackColor);
    textBuffer[0] = '\0';
    textLen = 0;
    ShowStartStop(true);
}

float SignalGenDisplay::rangelimit(float value, float min, float max) {
    if (value < min)
        return min;
    else if (value > max)
        return max;
    else
        return value;
}

void SignalGenDisplay::SaveSettings(OM_Changes reportMode) {
    char buf[20];

    if (reportMode != OM_NONE) {
        Changes |= reportMode;
        printf("SaveSettings - reset timer [%02X]\r\n", Changes);
        timerSave.reset();
        timerSave.start();
    } else if (timerSave.read() > SAVE_AFTER_IDLE_S) {
        timerSave.stop();
        timerSave.reset();
        printf("SaveSettings - timeout [%02X]\r\n", Changes);
        if (Changes & OM_MODE) {
            Changes &= ~ OM_MODE;
            printf("  Signal:Waveform=%s\r\n", ModeNames[mode]);
            ini.WriteString("Signal", "Waveform", ModeNames[mode]);
        }
        if (Changes & OM_PULSE) {
            Changes &= ~ OM_PULSE;
            printf("  Signal:Pulse Mode=%s\r\n", PulseModeLabels[pulseMode]);
            ini.WriteString("Signal", "Pulse Mode", PulseModeLabels[pulseMode]);
        }
        if (Changes & OM_FREQ) {
            Changes &= ~ OM_FREQ;
            snprintf(buf, sizeof(buf),"%5.3f", frequency);
            printf("  Signal:Frequency=%s\r\n", buf);
            ini.WriteString("Signal", "Frequency", buf);
        }
        if (Changes & OM_PERI) {
            Changes &= ~ OM_PERI;
            snprintf(buf, sizeof(buf),"%5.3f", frequency);
            printf("  Signal:Frequency=%s\r\n", buf);
            ini.WriteString("Signal", "Frequency", buf);
        }
        if (Changes & OM_DUTY) {
            Changes &= ~ OM_DUTY;
            snprintf(buf, sizeof(buf),"%1.0f", dutycycle);
            printf("  Signal:Duty Cycle=%s\r\n", buf);
            ini.WriteString("Signal", "Duty Cycle", buf);
        }
        if (Changes & OM_VOLT) {
            Changes &= ~ OM_VOLT;
            snprintf(buf, sizeof(buf),"%3.2f", voltage);
            printf("  Signal:Voltage=%s\r\n", buf);
            ini.WriteString("Signal", "Voltage", buf);
        }
        if (Changes & OM_OFFS) {
            Changes &= ~ OM_OFFS;
            snprintf(buf, sizeof(buf),"%3.2f", offset);
            printf("  Signal:Offset=%s\r\n", buf);
            ini.WriteString("Signal", "Offset", buf);
        }
        if (Changes & OM_BACKL) {
            Changes &= ~OM_BACKL;
            snprintf(buf, sizeof(buf), "%d", lcd->GetBacklight_u8());
            ini.WriteString("Settings", "Backlight", buf);
        }

    }
}

void SignalGenDisplay::resetDataEntry(OM_Changes nextMode, bool save) {
    OM_Changes last = EntryMd;

    printf("-> resetDataEntry(next: %d) curr:%d, save:%d\r\n", nextMode, last, save);
    EntryMd = nextMode;
    if (last != OM_NONE)
        signal->PrepareWaveform(mode, frequency, dutycycle, voltage, offset);
    switch (last) {
        case OM_NONE:
            updateDutyCycle();
            updateFrequency();
            updatePeriod();
            updateVoltage();
            updateOffset();
            break;
        case OM_DUTY:
            updateDutyCycle();
            if (save) {
                SaveSettings(OM_DUTY);
            }
            break;
        case OM_FREQ:
            updateFrequency();
            if (save) {
                SaveSettings(OM_FREQ);
            }
            break;
        case OM_PERI:
            updatePeriod();
            if (save) {
                SaveSettings(OM_FREQ);
            }
            break;
        case OM_VOLT:
            updateVoltage();
            if (save) {
                SaveSettings(OM_VOLT);
            }
            break;
        case OM_OFFS:
            updateOffset();
            if (save) {
                SaveSettings(OM_OFFS);
            }
            break;
        default:
            break;
    }
    DrawKeypadEnabled(EntryMd != OM_NONE);
    if (EntryMd == OM_NONE) {
        clearTextWindow();
    }
    printf("<- end resetDataEntry()\r\n");
}

// Calibrate the resistive touch screen, and store the data on the
// local file system.
//
void SignalGenDisplay::CalibrateTS(void)
{
    FILE * fh;
    tpMatrix_t matrix;
    RetCode_t r;
    Timer testperiod;
    char buf[100];
 
    r = lcd->TouchPanelCalibrate("Calibrate the touch panel", &matrix);
    if (r == noerror) {
        snprintf(buf, sizeof(buf), "%s/tpcal.cfg", Path);
        fh = fopen(buf, "wb");
        if (fh) {
            fwrite(&matrix, sizeof(tpMatrix_t), 1, fh);
            fclose(fh);
            printf("  %s cal written.\r\n", buf);
            lcd->cls();
        } else {
            printf("  couldn't open %s file.\r\n", buf);
        }
    } else {
        printf("error return: %d\r\n", r);
    }
    lcd->cls();
}

// Try to load a previous resistive touch screen calibration from storage. If it
// doesn't exist, activate the touch screen calibration process.
//
void SignalGenDisplay::InitializeTS(void)
{
    FILE * fh;
    tpMatrix_t matrix;
    char buf[100];

    snprintf(buf, sizeof(buf), "%s/tpcal.cfg", Path);
    fh = fopen(buf, "rb");
    if (fh) {
        fread(&matrix, sizeof(tpMatrix_t), 1, fh);
        fclose(fh);
        lcd->TouchPanelSetMatrix(&matrix);
        printf("  tp cal loaded.\r\n");
    } else {
        CalibrateTS();
    }
}