Signal Generator

Dependencies:   IniManager RA8875 Watchdog mbed-rtos mbed

Fork of speaker_demo_Analog by jim hamblen

Revision:
1:dd07e1deec6c
Child:
2:8f71b71fce1b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SignalGenDisplay.cpp	Fri Jan 13 12:33:37 2017 +0000
@@ -0,0 +1,1011 @@
+
+#include "SignalGenDisplay.h"
+#include "rtos.h"
+#include "IniManager.h"
+
+extern INI ini;
+
+// ##### Main Page #############################################################
+//
+// +---------------------------------------------------------------------------+
+// | +------------------------------------------+   Progam Name and version    |
+// | |                                          |   Manufacturer name          |
+// | |                                          |                              |
+// | |                                          |   [ Text Entry Box         ] |
+// | |      Scope Area                          |   +------------------------+ |
+// | |                                          |   |                        | |
+// | |                                          |   |                        | |
+// | |                                          |   |                        | |
+// | |                                          |   |                        | |
+// | |                                          |   |                        | |
+// | |                                          |   |    Keypad Area         | |
+// | +------------------------------------------+   |                        | |
+// |                                                |                        | |
+// | [duty cycle]  [frequency]     [amplitude]      |                        | |
+// |                                                |                        | |
+// | [    ...   ]  [period   ]     [offset   ]      |                        | |
+// |                                                |                        | |
+// | [     ] [      ] [        ] [        ] [    ]  |                        | |
+// | [Sine ] [Square] [Triangle] [Sawtooth] [User]  +------------------------+ |
+// +---------------------------------------------------------------------------+
+
+
+#define UI_BackColor            RGB(8,8,8)
+
+const rect_t UI_DATA_ENTRY      = {300,45, 475,65};
+
+const rect_t UI_SCOPE_RECT      = {4,5, 290,160};
+#define UI_ScopeBackColor       RGB(0,0,0)
+#define UI_ScopeFrameColor      RGB(255,255,255)
+
+#define SC_LEFT_MARGIN 10
+#define SC_TOP_MARGIN 20
+#define SC_RIGHT_MARGIN 30
+#define SC_BOT_MARGIN 30
+#define WaveOutlineColor        RGB(16,16,32)
+
+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
+};
+const int ParameterCount = sizeof(Parameters)/sizeof(Parameters[0]);
+const char ParameterKeys[] = { 'd', 'f', 'p', 'v', 'o' };
+
+#define UI_DutyColor            Magenta
+#define UI_FreqColor            BrightRed
+#define UI_VP2PColor            DarkBrown
+#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)
+
+const rect_t UI_PROD_RECT       = {298,3, 479,40};
+#define UI_ProductNameColor     UI_BUTTON_FACE_DN // RGB(192,192,192)
+
+#define PI 3.1415
+
+#define BTN_W 54
+#define BTN_H 35
+#define BTN_S  5    // space
+
+#define BTN_MODE_X  2
+#define BTN_MODE_Y  230
+
+#define BTN_KEYP_X  300
+#define BTN_KEYP_Y  70
+
+const rect_t NavToSettings = { 4,200, 60,220 };
+
+
+
+const rect_t UI_Buttons[] = {
+    { 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 },
+};
+const int ButtonCount = sizeof(UI_Buttons)/sizeof(UI_Buttons[0]);
+SignalGenDisplay::SG_Mode UI_ModeList[] = {
+    SignalGenDisplay::SG_SINE,
+    SignalGenDisplay::SG_SQUARE,
+    SignalGenDisplay::SG_TRIANGLE,
+    SignalGenDisplay::SG_SAWTOOTH,
+    SignalGenDisplay::SG_USER,
+};
+const char ModeKeys[] = { 'S','Q','T','W','U' };
+
+const rect_t UI_Keypad[] = {
+    {BTN_KEYP_X+0*(BTN_W+BTN_S),BTN_KEYP_Y+0*(BTN_H+BTN_S), BTN_KEYP_X+0*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+0*(BTN_H+BTN_S)+BTN_H },
+    {BTN_KEYP_X+1*(BTN_W+BTN_S),BTN_KEYP_Y+0*(BTN_H+BTN_S), BTN_KEYP_X+1*(BTN_W+BTN_S)+BTN_W,BTN_KEYP_Y+0*(BTN_H+BTN_S)+BTN_H },
+    {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 },
+    {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 },
+};
+const int KeypadCount = sizeof(UI_Keypad)/sizeof(UI_Keypad[0]);
+const char UI_KeyLabels[] = {
+    '7', '8', '9',
+    '4', '5', '6',
+    '1', '2', '3',
+    '0', '.', '-',
+    '\x1F', '\x1E', '\xB6',
+};
+const char KeyPadKeys[] = { '7', '8', '9', '4', '5', '6', '1', '2', '3',
+     '0', '.', '-', '<', '>', '\n' };
+
+
+// ##### Settings  #############################################################
+//
+// +---------------------------------------------------------------------------+
+// |                                                Progam Name and version    |
+// |                                                Manufacturer name          |
+// |                                                                           |
+// |                                                                           |
+// |                                                                           |
+// |                                                                           |
+// |                                                                +--------+ |
+// |                                                                |        | |
+// |                                                                |        | |
+// |                                                                |        | |
+// |                                                                |        | |
+// |                                                                |        | |
+// |                                                                |        | |
+// |                                                                |--------| |
+// |                                                                |        | |
+// | [    ...   ]                                                   |        | |
+// |                                                                |        | |
+// |                                                                |        | |
+// |                                                                +--------+ |
+// +---------------------------------------------------------------------------+
+
+const point_t suncenter = { 450,65 };
+const rect_t sunray[] = {
+    { 450-2,65-25, 450+2,65+25 },
+    { 450-25,65-2, 450+25,65+2 }
+};
+const rect_t sungraph = { 450-20,100+0, 450+20,265+0 };
+const rect_t inrgraph = { 450-18,100+2, 450+18,265-2 };
+
+
+template <typename T> int sgn(T val) {
+    return (T(0) < val) - (val < T(0));
+}
+
+char SignalGenDisplay::GetTouchEvent(void) {
+    TouchCode_t touch;
+    
+    touch = lcd->TouchPanelReadable();                           // any touch to report?
+    if (touch) {
+        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) {
+            timer.start();
+            timer.reset();
+        }
+        if ((ev == release) || (ev == held && timer.read_ms() > 250)) {
+            timer.reset();
+            switch (vis) {
+                case VS_MainScreen:
+printf("touch [vis: %d] (%d,%d)\r\n", vis, point.x, point.y);
+                    // Mode Keys touch
+                    for (int i=0; i<ButtonCount; i++) {
+                        if (lcd->Intersect(UI_Buttons[i], point)) {
+                            return ModeKeys[i];
+                        }
+                    }
+                    // Parameters
+                    for (int i=0; i<ParameterCount; i++) {
+                        if (lcd->Intersect(Parameters[i], point)) {
+                            return ParameterKeys[i];
+                        }
+                    }
+                    
+                    // Keypad
+                    for (int i=0; i<KeypadCount; i++) {
+                        if (lcd->Intersect(UI_Keypad[i], point)) {
+                            return KeyPadKeys[i];
+                        }
+                    }
+                    
+                    if (lcd->Intersect(NavToSettings, point)) {
+printf("Nav\r\n");
+                        vis = VS_Settings;
+                        Init();
+                        while (lcd->TouchPanelReadable())
+                            ;
+                        Thread::wait(100);
+                    }
+                    break;
+                case VS_Settings:
+                    Thread::wait(20);
+printf("touch [VIS: %d\r\n", vis);
+                    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));
+                        ShowBrightnessSetting();
+                    }
+                    if (lcd->Intersect(NavToSettings, point)) {
+                        // Save settings
+                        char buf[20];
+                        
+                        snprintf(buf, sizeof(buf), "%d", lcd->GetBacklight_u8());
+                        ini.WriteString("Settings", "Backlight", buf);
+                        
+                        // Switch to main screen
+                        vis = VS_MainScreen;
+                        Init();
+                        while (lcd->TouchPanelReadable())
+                            ;
+                        Thread::wait(100);
+                        ShowMenu();
+                    }
+                    break;
+            }
+        }
+    }
+    return 0;
+}
+
+
+SignalGenDisplay::SignalGenDisplay(RA8875 * _lcd, SignalGenerator * _signal,
+    const char * _ProgName, const char * _Manuf, const char * _Ver, const char * _Build) :
+    lcd(_lcd), signal(_signal), ProgName(_ProgName), Manuf(_Manuf), Ver(_Ver), Build(_Build) {
+    vis = VS_MainScreen;
+}
+
+
+SignalGenDisplay::~SignalGenDisplay() {
+}
+
+
+void SignalGenDisplay::Init() {
+    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();
+            // Some defaults for testing 
+            SetDutyCycle(30);
+            SetFrequency(1230.0);
+            SetVoltagePeakToPeak(3.0);
+            SetVoltageOffset(1.50);
+            resetDataEntry();
+            SelectWaveformMode(SignalGenDisplay::SG_SINE);
+            DrawKeypadEnabled(false);
+            DrawNavGadget();
+            break;
+            
+        case VS_Settings:
+            lcd->background(UI_BackColor);
+            lcd->cls(2);
+            lcd->SelectDrawingLayer(1);
+            lcd->SetLayerMode(RA8875::ShowLayer1);
+            lcd->foreground(UI_ProductNameColor);
+            ShowProductInfo();
+            ShowBrightnessSetting();
+            DrawNavGadget();            
+            break;
+    }
+}
+
+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(void) {
+    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);
+    lcd->window();
+}
+
+void SignalGenDisplay::ShowBrightnessSetting(void) {
+    // Sunbeam
+    lcd->fillrect(sunray[0], White);
+    lcd->fillrect(sunray[1], White);
+    lcd->fillcircle(suncenter, 18, UI_BackColor);
+    lcd->fillcircle(suncenter, 15, 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);
+}
+
+SignalGenDisplay::SG_Changes SignalGenDisplay::Poll(char c) {
+    SG_Changes ret = SG_NONE;
+    
+    if (!c) {
+        c = GetTouchEvent();
+    }
+    if (c) {
+        printf("%02X: EntryMd: %d, textLen: %d [%s] VIS: %d\r\n", c, EntryMd, textLen, textBuffer, vis);
+    } 
+    ///     - 'd'       duty cycle entry
+    ///     - 'f'       frequency entry
+    ///     - 'p'       period entry
+    ///     - 'v'       voltage entry
+    ///     - 'o'       offset voltage entry
+    ///     - '0'-'9','.'   numeric entry
+    ///     - <enter>   complete numeric entry
+    ///     - <esc>     abandon numeric entry
+    ///     - <nul>     do nothing, just poll
+    switch (c) {
+        case '?':
+            ShowMenu();
+            break;
+        case 'S':
+            SelectWaveformMode(SG_SINE);
+            signal->SetSignalFrequency(SignalGenerator::SinusSignal, frequency);
+            //ret = SG_SINE;
+            break;
+        case 'Q':
+            SelectWaveformMode(SG_SQUARE);
+            signal->SetSignalFrequency(SignalGenerator::SquareSignal, frequency);
+            //ret = SG_SQUARE;
+            break;
+        case 'T':
+            SelectWaveformMode(SG_TRIANGLE);
+            signal->SetSignalFrequency(SignalGenerator::TriangleSignal, frequency);
+            //ret = SG_TRIANGLE;
+            break;
+        case 'W':
+            SelectWaveformMode(SG_SAWTOOTH);
+            signal->SetSignalFrequency(SignalGenerator::SawtoothSignal, frequency);
+            //ret = SG_SAWTOOTH;
+            break;
+        case 'U':
+            SelectWaveformMode(SG_USER);
+            //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();
+                }
+            }
+            break;
+        case '\x1B':
+            textBuffer[0] = '\0';
+            textLen = 0;
+            EntryMd = SG_NONE;
+            resetDataEntry();
+            break;
+        case '\r':
+        case '\n':
+            if (EntryMd) {
+                if (strlen(textBuffer)) {
+                    switch (EntryMd) {
+                        case SG_DUTY:
+                            SetDutyCycle(atof(textBuffer));
+                            break;
+                        case SG_FREQ:
+                            SetFrequency(atof(textBuffer));
+                            break;
+                        case SG_PERI:
+                            SetPeriod(atof(textBuffer));
+                            break;
+                        case SG_VOLT:
+                            SetVoltagePeakToPeak(atof(textBuffer));
+                            break;
+                        case SG_OFFS:
+                            SetVoltageOffset(atof(textBuffer));
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                EntryMd = SG_NONE;
+                resetDataEntry();
+            }
+            break;
+        case '>':
+            switch (EntryMd) {
+                case SG_DUTY:
+                    SetDutyCycle(dutycycle + 1.0);
+                    break;
+                case SG_FREQ:
+                    SetFrequency(frequency + 1.0);
+                    break;
+                case SG_PERI:
+                    SetPeriod(1/frequency + 0.001);
+                    break;
+                case SG_VOLT:
+                    SetVoltagePeakToPeak(voltage + 0.1);
+                    break;
+                case SG_OFFS:
+                    SetVoltageOffset(offset + 0.01);
+                    break;
+                default:
+                    break;
+            }
+            break;
+        case '<':
+            switch (EntryMd) {
+                case SG_DUTY:
+                    SetDutyCycle(dutycycle - 1.0);
+                    break;
+                case SG_FREQ:
+                    SetFrequency(frequency - 1.0);
+                    break;
+                case SG_PERI:
+                    SetPeriod(1/frequency - 0.001);
+                    break;
+                case SG_VOLT:
+                    SetVoltagePeakToPeak(voltage - 0.1);
+                    break;
+                case SG_OFFS:
+                    SetVoltageOffset(offset - 0.01);
+                    break;
+                default:
+                    break;
+            }
+            break;
+        case 'd':
+            if (EntryMd != SG_DUTY) {
+                resetDataEntry();
+                EntryMd = SG_DUTY;
+                DrawKeypadEnabled(true);
+                updateDutyCycle();
+            } else {
+                EntryMd = SG_NONE;
+                resetDataEntry();
+            }
+            break;
+        case 'f':
+            if (EntryMd != SG_FREQ) {
+                resetDataEntry();
+                EntryMd = SG_FREQ;
+                DrawKeypadEnabled(true);
+                updateFrequency();
+            } else {
+                EntryMd = SG_NONE;
+                resetDataEntry();
+            }
+            break;
+        case 'p':
+            if (EntryMd != SG_PERI) {
+                resetDataEntry();
+                EntryMd = SG_PERI;
+                DrawKeypadEnabled(true);
+                updatePeriod();
+            } else {
+                EntryMd = SG_NONE;
+                resetDataEntry();
+            }
+            break;
+        case 'v':
+            if (EntryMd != SG_VOLT) {
+                resetDataEntry();
+                EntryMd = SG_VOLT;
+                DrawKeypadEnabled(true);
+                updateVoltage();
+            } else {
+                EntryMd = SG_NONE;
+                resetDataEntry();
+            }
+            break;
+        case 'o':
+            if (EntryMd != SG_OFFS) {
+                resetDataEntry();
+                EntryMd = SG_OFFS;
+                DrawKeypadEnabled(true);
+                updateOffset();
+            } else {
+                EntryMd = SG_NONE;
+                resetDataEntry();
+            }
+            break;
+        default:
+            break;
+    }
+    return ret;
+}
+
+bool SignalGenDisplay::SelectWaveformMode(SG_Mode _mode) {
+    if (/* _mode >= SG_SINE && */ _mode <= SG_USER) {
+        mode= _mode;
+        for (int i=0; i<ButtonCount; i++) {
+            DrawButton(UI_Buttons[i], (UI_ModeList[i] == mode) ? true : false, UI_ModeList[i], true);
+        }
+        UpdateScope();
+        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) {
+    printf("-> SetFrequency(%f)\r\n", _frequency);
+    if (_frequency >= 1.0 && _frequency <= 1.0E6) {
+        frequency = _frequency;
+        updateFrequency();
+        updatePeriod();
+        UpdateScope();
+        printf("   end SetFrequency\r\n");
+        return true;
+    } else {
+        printf("   end SetFrequency - out of range\r\n");
+        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 >= -1.65 && _voltage <= 1.65) {
+        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) {
+    printf("-> UpdateScope()\r\n");
+    ClearScope();
+    rect_t r;
+
+    r.p1.x = UI_SCOPE_RECT.p1.x + SC_LEFT_MARGIN;
+    r.p1.y = UI_SCOPE_RECT.p1.y + SC_TOP_MARGIN;
+    r.p2.x = UI_SCOPE_RECT.p2.x - SC_RIGHT_MARGIN;
+    r.p2.y = UI_SCOPE_RECT.p2.y - SC_BOT_MARGIN;
+    lcd->rect(r, WaveOutlineColor);
+    
+    // Draw the Peak to Peak markers
+    lcd->line(r.p1.x,r.p1.y, r.p2.x+3*SC_RIGHT_MARGIN/4,r.p1.y, UI_VP2PColor);
+    lcd->line(r.p1.x,r.p2.y, r.p2.x+3*SC_RIGHT_MARGIN/4,r.p2.y, UI_VP2PColor);
+    lcd->line(r.p2.x+3*SC_RIGHT_MARGIN/4-3,r.p1.y, r.p2.x+3*SC_RIGHT_MARGIN/4-3,r.p2.y, UI_VP2PColor);
+    lcd->filltriangle(
+        r.p2.x+3*SC_RIGHT_MARGIN/4-3,r.p1.y,
+        r.p2.x+3*SC_RIGHT_MARGIN/4-3+2,r.p1.y+3,
+        r.p2.x+3*SC_RIGHT_MARGIN/4-3-2,r.p1.y+3,
+        UI_VP2PColor);
+    lcd->filltriangle(
+        r.p2.x+3*SC_RIGHT_MARGIN/4-3,r.p2.y,
+        r.p2.x+3*SC_RIGHT_MARGIN/4-3+2,r.p2.y-3,
+        r.p2.x+3*SC_RIGHT_MARGIN/4-3-2,r.p2.y-3,
+        UI_VP2PColor);
+
+    // Draw the offset voltage markers
+    loc_t y = (r.p1.y + r.p2.y)/2;
+    dim_t w = (r.p2.x + SC_RIGHT_MARGIN/3 - r.p1.x) / 35;
+    dim_t h = (r.p2.y - r.p1.y);
+    for (int i=0; i<=35+1; i++) {
+        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,y+sgn(offset)*(h/2+7), r.p2.x+SC_RIGHT_MARGIN/3-3,y, UI_VOffsetColor);
+            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);
+            if (abs(offset) > voltage/2)
+                lcd->line(r.p1.x,y+sgn(offset)*(h/2+7), r.p2.x+SC_RIGHT_MARGIN/3,y+sgn(offset)*(h/2+7), UI_VOffsetColor);
+            else
+                lcd->line(r.p1.x,y+(offset/voltage)*h, r.p2.x+SC_RIGHT_MARGIN/3,y+(offset/voltage)*h, UI_VOffsetColor);
+            break;
+    }
+    
+    // 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, r.p1.x,r.p2.y+3*SC_BOT_MARGIN/4, UI_FreqColor);
+    lcd->line(r.p1.x+1*w/2,r.p1.y, 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, r.p1.x,r.p2.y+2*SC_BOT_MARGIN/4, UI_DutyColor);
+    lcd->line(r.p1.x + dc,r.p1.y, 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);
+    DrawWaveform(r, mode, White);
+    printf("   end UpdateScope()\r\n");
+}
+
+//       ++           +----+            +             +
+//      .  .          |    |           / \           /|
+//     .    .         |    |    |     /   \   /     / |
+//           .             |    |          \ /     /  |
+//            ++           +----+           +     +   +
+//
+void SignalGenDisplay::DrawWaveform(rect_t r, SG_Mode mode, color_t color, float dutycycleOverride) {
+    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 dc = ((dutycycleOverride >= 5.0) ? dutycycleOverride : dutycycle)/100.0 * 1*w/2;
+    dim_t a = (r.p2.y - r.p1.y)/2;
+    float v;
+    
+    switch (mode) {
+        case SG_SINE:
+            for (int cycle=0; cycle<2; cycle++) {
+                for (x=0; x<=dc; x++) {
+                    v = offset + voltage/2 * sin(x * 1 * PI / dc);
+                    v = rangelimit(v, SG_MIN_V, SG_MAX_V);
+                    y = r.p2.y - 2 * a * v / SG_AOUT_FS;
+                    lcd->pixel(r.p1.x + cycle * w/2 + x, y, color);
+                }
+                for (x=0; x<=(w/2-dc); x++) {
+                    v = offset - voltage/2 * sin(x * 1 * PI / (w/2-dc));
+                    v = rangelimit(v, SG_MIN_V, SG_MAX_V);
+                    y = r.p2.y - 2 * a * v / SG_AOUT_FS;
+                    lcd->pixel(r.p1.x + cycle * w/2 + dc + x, y, color);
+                }
+            }
+            break;
+        case SG_SQUARE:
+            for (int cycle=0; cycle<2; cycle++) {
+                lcd->line(r.p1.x+cycle*w/2+0*w/8, y0, r.p1.x+cycle*w/2+0*w/8, y0-a, color);   // rise
+                lcd->line(r.p1.x+cycle*w/2+0*w/8, y0-a, r.p1.x+cycle*w/2+dc, y0-a, color);      // horz
+                lcd->line(r.p1.x+cycle*w/2+dc, y0-a, r.p1.x+cycle*w/2+dc, y0+a, color);         // fall
+                lcd->line(r.p1.x+cycle*w/2+dc, y0+a, r.p1.x+cycle*w/2+4*w/8, y0+a, color);      // horz
+                lcd->line(r.p1.x+cycle*w/2+4*w/8, y0+a, r.p1.x+cycle*w/2+4*w/8, y0, color);   // rise
+            }
+            break;
+        case SG_TRIANGLE:
+            for (int cycle=0; cycle<2; cycle++) {
+                lcd->line(r.p1.x+cycle*w/2+0*w/8, y0+0, r.p1.x+cycle*w/2+dc/2,  y0-a, color);   // rise 2
+                lcd->line(r.p1.x+cycle*w/2+dc/2,  y0-a, r.p1.x+cycle*w/2+dc/1,  y0,  color);    // fall 1
+                lcd->line(r.p1.x+cycle*w/2+dc/1,  y0,   r.p1.x+cycle*w/2+(w/2+dc)/2, y0+a, color);   // fall 2
+                lcd->line(r.p1.x+cycle*w/2+(w/2+dc)/2, y0+a, r.p1.x+cycle*w/2+4*w/8, y0, color);   // rise 1
+            }
+            break;
+        case SG_SAWTOOTH:
+            for (int cycle=0; cycle<2; cycle++) {
+                lcd->line(r.p1.x+cycle*w/2+0*w/8+0, y0+a, r.p1.x+cycle*w/2+dc,      y0, color);
+                lcd->line(r.p1.x+cycle*w/2+dc,      y0,   r.p1.x+cycle*w/2+4*w/8-1, y0-a, color);
+                lcd->line(r.p1.x+cycle*w/2+4*w/8-1, y0-a, r.p1.x+cycle*w/2+4*w/8,   y0+a, color);
+            }
+            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;
+    }
+}
+
+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 != SG_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 != SG_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);
+    printf("EntryMode: %d, bg: %08X, fg: %08X\r\n", EntryMd, bcolor, fcolor);
+    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 != SG_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 != SG_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 != SG_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_Mode 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, 50.0);
+            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;
+    }
+}
+
+void SignalGenDisplay::updateTextWindow(void) {
+    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("%21s", textBuffer);
+    lcd->window();
+}
+
+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::ShowMenu(void) {
+    if (Manuf) {
+        printf("\r\n%s v%s by %s build %s\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("                            <cr>:  Save number\r\n");
+    printf("                           <esc>:  Exit number entry\r\n");
+    //printf("  4:  Reverse sawtoothSignal\r\n");
+}
+
+
+void SignalGenDisplay::resetDataEntry(void) {
+    SG_Changes last = EntryMd;
+    printf("-> resetDataEntry()\r\n");
+    EntryMd = SG_NONE;
+    switch (last) {
+        case SG_NONE:
+            updateDutyCycle();
+            updateFrequency();
+            updatePeriod();
+            updateVoltage();
+            updateOffset();
+            lcd->fillrect(UI_DATA_ENTRY, UI_BackColor);
+            textBuffer[0] = '\0';
+            textLen = 0;
+            break;
+        case SG_DUTY:
+            updateDutyCycle();
+            break;
+        case SG_FREQ:
+            updateFrequency();
+            break;
+        case SG_PERI:
+            updatePeriod();
+            break;
+        case SG_VOLT:
+            updateVoltage();
+            break;
+        case SG_OFFS:
+            updateOffset();
+            break;
+        default:
+            break;
+    }
+    DrawKeypadEnabled(false);
+    printf("   end resetDataEntry()\r\n");
+}
\ No newline at end of file