Signal Generator

Dependencies:   IniManager RA8875 Watchdog mbed-rtos mbed

Fork of speaker_demo_Analog by jim hamblen

Revision:
3:d22f3e52d06a
Parent:
2:8f71b71fce1b
Child:
4:10281ddb673d
--- a/SignalGenDisplay.cpp	Sun Jan 15 03:11:22 2017 +0000
+++ b/SignalGenDisplay.cpp	Mon Jan 16 04:33:06 2017 +0000
@@ -5,8 +5,8 @@
 #include "SignalGenDisplay.h"
 #include "rtos.h"
 #include "IniManager.h"
+#include "BPG_Arial08x08.h"
 
-extern INI ini;
 
 // ##### Main Page #############################################################
 //
@@ -14,14 +14,14 @@
 // | +--- Scope Area ---------------------------+   Progam Name and version    |
 // | |                                          |   Manufacturer name          |
 // | | +---- Wave Outline - -                   |                              |
+// | | |                                        |   [Start/Stop    ]  [      ] |
 // | | |                                        |   [Text Entry Box]  [ Back ] |
-// | | |                                        |                              |
-// | |                                          |   +------------------------+ |
+// | |                                          |   +-- Keypad Area ---------+ |
 // | |                                          |   |                        | |
 // | |                                     |    |   |                        | |
 // | |                                     |    |   |                        | |
 // | |                                  ---+    |   |                        | |
-// | |                                          |   |    Keypad Area         | |
+// | |                                          |   |                        | |
 // | +------------------------------------------+   |                        | |
 // |                                                |                        | |
 // | [duty cycle]  [frequency]     [amplitude]      |                        | |
@@ -48,15 +48,10 @@
 #define UI_BUTTON_SHADOW_DISABLED   RGB(32,0,0)
 #define UI_ProductNameColor         UI_BUTTON_FACE_DN
 
-// Rectangular Zones
-const rect_t UI_DATA_ENTRY      = {300,53, 410,73};
-const rect_t UI_SCOPE_RECT      = {4,5, 290,160};
 #define SC_LEFT_MARGIN              10       // Scope left margin
-#define SC_TOP_MARGIN               20
+#define SC_TOP_MARGIN               10
 #define SC_RIGHT_MARGIN             30
-#define SC_BOT_MARGIN               30
-const rect_t UI_WAVE_RECT       = {4+SC_LEFT_MARGIN,5+SC_TOP_MARGIN, 290-SC_RIGHT_MARGIN,160-SC_BOT_MARGIN};
-
+#define SC_BOT_MARGIN               20
 #define BTN_W       54          // Button width
 #define BTN_H       32          // Button height
 #define BTN_S       5           // Button white-space
@@ -67,6 +62,15 @@
 #define BTN_KEYP_X  300         // Keypad left edge
 #define BTN_KEYP_Y  53          // Keypad top edge
 
+// Rectangular Zones
+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};
+const char * UI_StartLabels[3]  = { "Start", "Stop", "Pulse" };
+const char StartStopKeys[]      = { 'G', 'O', 'P' };
+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};
+const rect_t UI_SCOPE_RECT      = {4,5, 290,160};
+
+const rect_t UI_WAVE_RECT       = {4+SC_LEFT_MARGIN,5+SC_TOP_MARGIN, 290-SC_RIGHT_MARGIN,160-SC_BOT_MARGIN};
+
 const rect_t Parameters[] = {
     {4,170, 60,190},            // 'd'uty cycle
     {90,170, 186,190},          // 'f'requency
@@ -77,7 +81,7 @@
 const int ParameterCount = sizeof(Parameters)/sizeof(Parameters[0]);
 const char ParameterKeys[] =    { 'd', 'f', 'p', 'v', 'o' };
 
-const rect_t UI_PROD_RECT =     {298,3, 479,40};
+const rect_t UI_PROD_RECT =     {  298,3, 479,51 };
 const rect_t NavToSettings =    {  4,200, 60,220 };
 
 // Mode Buttons
@@ -146,12 +150,12 @@
 // +---------------------------------------------------------------------------+
 // |                                                Progam Name and version    |
 // |                                                Manufacturer name          |
-// |                                                                           |
+// |                                                Build Date                 |
+// | Signal Generator Mode                                                     |
 // |                                                                  \ | /    |
-// |                                                                  = O =    |
+// |   ( * ) Continuous                                               = O =    |
 // |                                                                  / | \    |
-// |                                                                +--------+ |
-// |                                                                |        | |
+// |   (   ) One-Shot                                               +--------+ |
 // |                                                                |        | |
 // |                                                                |        | |
 // |                                                                |        | |
@@ -165,20 +169,48 @@
 // |                                                                +--------+ |
 // +---------------------------------------------------------------------------+
 
-const point_t suncenter = { 450,65 };
+const point_t suncenter = { 450,85 };
 const rect_t sunray[] = {
-    { 450-2,65-25, 450+2,65+25 },
-    { 450-25,65-2, 450+25,65+2 }
+    { 450-2, 85-25, 450+2, 85+25 },
+    { 450-25,85-2,  450+25,85+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 };
+const rect_t sungraph = { 450-18,120+0, 450+18,265+0 };
+const rect_t inrgraph = { 450-16,120+2, 450+16,265-2 };
+
+const rect_t SignalMode =
+    { 20,50, 20+140,70 };
+const char * SignalModeLabel = "Signal Mode";
+
+const rect_t radio_Cycles[] = { 
+    { 40, 80, 40+120,100 },
+    { 40,110, 40+120,130 }
+};
+const int radio_CyclesCount = sizeof(radio_Cycles)/sizeof(radio_Cycles[0]);
+const char * radio_CyclesLabels[] = {
+    "Continuous",
+    "One-Shot"
+};
+#define UI_CyclesColor              Green
+#define UI_CyclesBackColor          RGB(0,0,0)
+
+// rect_t radio_Cycles[], radio_CyclesCount, char * radio_CyclesLabels[]
 
 #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,
+
+// #############################################################################
+
+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), ProgName(_ProgName), Manuf(_Manuf), Ver(_Ver), Build(_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;
 }
 
@@ -187,74 +219,107 @@
 }
 
 
-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
+    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) {
-            timer.start();
-            timer.reset();
+            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 && timer.read_ms() > 250)) {
-            timer.reset();
+        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
-                    for (int i=0; i<ModeCount; i++) {
-                        if (lcd->Intersect(ModeButtons[i], point)) {
-                            return ModeKeys[i];
+                    if (ev == release) {
+                        for (int i=0; i<ModeCount; i++) {
+                            if (lcd->Intersect(ModeButtons[i], point)) {
+                                return ModeKeys[i];
+                            }
                         }
                     }
                     // Parameters
-                    for (int i=0; i<ParameterCount; i++) {
-                        if (lcd->Intersect(Parameters[i], point)) {
-                            return ParameterKeys[i];
+                    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];
+                            }
                         }
                     }
                     
-                    // Keypad
-                    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);
                         }
                     }
-                    
-                    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 (lcd->Intersect(NavToSettings, point)) {
-                        // Save Backlight settings on screen change
-                        char buf[20];
-                        snprintf(buf, sizeof(buf), "%d", lcd->GetBacklight_u8());
-                        ini.WriteString("Settings", "Backlight", buf);
-                        
-                        // Switch to main screen
-                        vis = VS_MainScreen;
-                        Refresh();
-                        while (lcd->TouchPanelReadable())
-                            ;
-                        Thread::wait(100);
-                        ShowMenu();
+                    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;
+                                ShowCyclesControl();
+                            }
+                        }
                     }
                     break;
             }
@@ -267,15 +332,17 @@
 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), "Sine");
+        ini.ReadString("Signal", "Waveform", buf, sizeof(buf), ModeNames[0]);
         for (int i=0; i<ModeCount; i++) {
             if (strcmp(ModeNames[i], buf) == 0) {
                 mode = (SG_Mode)i;
@@ -293,6 +360,15 @@
     
         ini.ReadString("Signal", "Offset", buf, sizeof(buf), "1.5");
         offset = atof(buf);
+        
+        ini.ReadString("Signal", "Pulse Mode", buf, sizeof(buf), radio_CyclesLabels[0]);
+        for (int i=0; i<radio_CyclesCount; i++) {
+            if (strcmp(radio_CyclesLabels[i], buf) == 0) {
+                pulseMode = i;
+                break;
+            }
+        }
+        ShowMenu();
     }
     switch (vis) {
         case VS_MainScreen:
@@ -318,14 +394,31 @@
             lcd->SelectDrawingLayer(1);
             lcd->SetLayerMode(RA8875::ShowLayer1);
             lcd->foreground(UI_ProductNameColor);
-            ShowProductInfo();
+            ShowProductInfo(true);
+            ShowCyclesControl();
             ShowBrightnessSetting();
             DrawNavGadget();
-            DrawModeButtons();
             break;
     }
 }
 
+// rect_t radio_Cycles[], radio_CyclesCount, char * radio_CyclesLabels[]
+
+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', radio_CyclesLabels[x]);
+    }
+}
+
 
 void SignalGenDisplay::DrawModeButtons(void) {
     for (int i=0; i<ModeCount; i++) {
@@ -343,22 +436,37 @@
 }
 
 
-void SignalGenDisplay::ShowProductInfo(void) {
+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, 15, White);
+    lcd->fillcircle(suncenter, 12, White);
     lcd->rect(sungraph, Blue);
     float bl = lcd->GetBacklight();
     lcd->fillrect(inrgraph, UI_BackColor);
@@ -387,29 +495,36 @@
 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);
-    } 
-    ///     - '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
+    }
+    /// 01234567890-. #?SQTW dfpvo < > <bs> <enter> <esc>
     switch (c) {
         case '#':
-            lcd->DumpRegisters();
+            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);
@@ -693,7 +808,7 @@
 }
 
 bool SignalGenDisplay::SetVoltageOffset(float _voltage) {
-    if (_voltage >= -1.65 && _voltage <= 1.65) {
+    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;
@@ -720,6 +835,8 @@
     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);
     
@@ -737,6 +854,10 @@
         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;
@@ -761,7 +882,9 @@
             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;
@@ -797,15 +920,23 @@
         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_Mode mode, color_t color, bool drawPure) {
     loc_t x,y;
@@ -857,23 +988,43 @@
             break;
         case SG_TRIANGLE:
             for (int cycle=0; cycle<2; 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+privDutyCycleX/2,  upp, color);                 // rise 2
-                lcd->line(r.p1.x+cycle*w/2+privDutyCycleX/2,  upp, r.p1.x+cycle*w/2+privDutyCycleX/1, mid,  color);     // fall 1
-                lcd->line(r.p1.x+cycle*w/2+privDutyCycleX/1,  mid,   r.p1.x+cycle*w/2+(w/2+privDutyCycleX)/2, low, color);  // fall 2
-                lcd->line(r.p1.x+cycle*w/2+(w/2+privDutyCycleX)/2, low, r.p1.x+cycle*w/2+4*w/8, mid, color);            // rise 1
+                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<2; 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+0, low, r.p1.x+cycle*w/2+privDutyCycleX,     mid, color);
-                lcd->line(r.p1.x+cycle*w/2+privDutyCycleX,    mid,   r.p1.x+cycle*w/2+4*w/8-1, upp, color);
-                lcd->line(r.p1.x+cycle*w/2+4*w/8-1, upp, r.p1.x+cycle*w/2+4*w/8,   low, color);
+                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:
@@ -925,7 +1076,9 @@
     lcd->foreground(fcolor);
     lcd->background(bcolor);
     lcd->SetTextCursor(r.p1.x+1, r.p1.y+1);
-    if (frequency >= 1000.0)
+    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);    
@@ -1044,16 +1197,32 @@
             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, false, 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("%21s", textBuffer);
+    lcd->printf("%13s", textBuffer);
     lcd->window();
 }
 
@@ -1061,6 +1230,7 @@
     lcd->fillrect(UI_DATA_ENTRY, UI_BackColor);
     textBuffer[0] = '\0';
     textLen = 0;
+    ShowStartStop(true);
 }
 
 float SignalGenDisplay::rangelimit(float value, float min, float max) {
@@ -1118,6 +1288,12 @@
             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);
+        }
+
     }
 }
 
@@ -1174,4 +1350,55 @@
         clearTextWindow();
     }
     printf("<- end resetDataEntry()\r\n");
-}
\ No newline at end of file
+}
+
+// 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();
+    }
+}
+