corz.org text viewer..
[currently viewing: /public/scripts/ESP32/ESP32_OscilloscopeClock-FastDAC/ESP32_OscilloscopeClock-FastDAC.ino - raw]
/*
    ESP32 Oscilloscope Clock V1.2
    (Experimental FastDAC Version)

    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

    This version draws the clock around six and a half times faster than the original version,
    enabling the possibility of more complex drawing.

    Feel free to do that and send me the coordinates!

    FastDAC code from here:
    https://github.com/bitluni/OsciDisplay/blob/master/OsciVector/FastDAC.h

    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

    Original Oscilloscope Clock Copyright © 2010 Johannes P.M. de Rie
    http://www.dutchtronix.com/ScopeClockH3-1-Enhanced.htm

    Original ESP32 Version by Mauro Pintus, 2018/05/25

    The time code was broken (most likely by changes to the underlying API) and this great sketch
    deserved to get an update, so I ripped out the broken internet/NTP stuff and replaced it.

    The time also now automatically updates every hour, which will fix any drift issues (see also
    "interval" preference, which you might want to tweak for your particular chip/module - the
    hourly update will pick up any slack in your timings. Test! TesT! TEST!).

    I also ripped out Excel stuff and other bits, to simplify things, and made other minor
    improvements and fixes. This should now drop-in-and-go.


    How to use:

    Set your SSID and Password, as well as NTP server address, below.

    Load this sketch onto an ESP32 board (one with DAC pins, i.e. not ESP32-Cam) using the Arduino
    IDE 1.8.7+ (1.8.19 is recommended - 2.* is a disaster!) or VSCode or whatever.

    Connect your oscilloscope channels to the ESP32; one channel to GPIO25 and the other channel to
    GPIO26. You obviously need a dual channel oscilloscope to do this.*

    *   When buying a portable digital oscilloscope, the difference between a single and dual
        channel model will likely be around twenty bucks. SAVE UP! Or whatever it takes to upgrade
        to this level of tech. As well as being infinitely useful, the cool factorness of being able
        to run your scope as a *display* is well worth the extra time/effort = money required.

    **  After hours of searching and researching I personally opted for a DSO2512G. It would be a
        mistake to follow this recommendation without first figuring out what you need a scope
        /for/. Having said that, if you are in the market for a portable dual-channel scope for
        messing with MCUs and audio circuits, general electronics, wowing kids and what-not, the
        DSO2512G is silly money for the tech you get. And that music FFT is a killer feature!

    Connect the ground of the oscilloscope to the GND of the ESP32 board (if it isn't already - I
    have my scope charging on USB right now, which also works; as the other USB socket (of my
    computer) is connected to the UART adaptor, which is connected to the ESP32, and my knee-bone!).

    Put your Oscilloscope in XY mode.

    Adjust the vertical scale of the used channels to fit the clock.
    (Try around 500mV and 100us for a DSO - or hit "Auto"!)

    Enjoy Your new Oscilloscope Clock!!! :)


    Note: This definitely looks better on an analog scope, but I do enjoy the ethereal quality of
    the DSO clock. Playing with the timings can get you some fun, too. I'd rather have a digital
    scope for *so* many other reasons. In fact, having both is better!

    Additional notes:

    By default this sketch will use WiFi+NTP to ascertain the time.

    If you want to show "a clock" as opposed to "the clock", you can set a custom start time below
    (also comment out the line: "#define NTP") and the board will start with the specified time at
    every boot-up. This is handy for testing.

    To change the start time, modify the variables h,m,s below.

    Status is sent down the serial line, so you can see what's going on in your serial monitor @
    115200 baud; particularly useful at startup.


    corz.org @ 2023-1-1

*/


#include "FastDAC.h"
#include "DataTable.h"


// Prefs..

// Comment out this line to use a fixed start time.
#define NTP
//
// If you are messing with the code, it's a good idea to use a fixed time.
// Not only will your sketch compile faster, but you have less chance of
// being banned from your chosen time server.

// Fixed Start Time. If you're using an NTP server, ignore these prefs.
// As it's an analog clock, we use 12 hour clock time, regardless..
int h=10;   //Start Hour
int m=8;    //Start Minutes
int s=37;   //Start Seconds


// havoc for your serial line..
// #define DEBUG


#if defined NTP
#include <WiFi.h>
#include "time.h"
#include "sntp.h"

    const char* ssid       = "WiFi-SSID";              // Set you WiFi-SSID
    const char* password   = "WiFi-password";          // Set you WiFi-password

    // Uncomment and set this to your nearest NTP server..
    //const char* ntpServer = "1.uk.pool.ntp.org";

    // Use this when testing/hammering..
    const char* ntpServer = "time1.google.com";
    // comment out when done!

    // Set this to your local timezone..
    const char* time_zone = "GMT+0BST-1,M3.5.0/01:00:00,M10.5.0/02:00:00";

    // This string will setup the time server negotiation with proper rules about
    // when and for how long daylight saving time occurs.
    // (Google this exact string for a list of all the others)

    // here's one you can use to test it's working..
    //const char* time_zone = "CST+6CDT,M3.2.0/2,M11.1.0/2";

    // No more prefs to set. Enjoy!

    int status = WL_IDLE_STATUS;
    bool useRealTime = true;
    bool doneUpdate = false;

#endif //

// Variables
int           lastx,lasty;
unsigned long currentMillis  = 0;
unsigned long previousMillis = 0;
int           Timeout        = 15;  // WiFi time-out, in seconds. Must be a positive integer.
const    long interval       = 996; // milliseconds, you might want to tweak this
                                    // to get a better accuracy.


void setup() {

    Serial.begin(115200); // always use this speed for debug data!

    Serial.println("\n");
    Serial.println("ESP32 Oscilloscope Clock v1.2");

    dac_output_enable(DAC_CHANNEL_1);
    dac_output_enable(DAC_CHANNEL_2);

#if defined NTP

        Serial.print("\nConnecting to Wi-Fi..");

        WiFi.begin (ssid, password);
        while (WiFi.status() != WL_CONNECTED) {
            delay(500);
            Serial.print(".");
            delay(500);
            Serial.print(".");
            Timeout--;
            if (Timeout==0){
                useRealTime = false;
                Serial.println("WiFi Timeout");
                break;
            }
        }

       Serial.println("");

       if (useRealTime && Timeout!=0){

            Serial.println("WiFi connected");

            if (useRealTime) {
                configTzTime(time_zone, ntpServer);
                Serial.print("Time: ");
                printLocalTime();
            } else {
                useRealTime = false;
                Serial.println("Time functions disabled");
            }
        }

        if (!useRealTime) {
            Serial.println("Using Fixed Time");
        }
#endif

#if !defined NTP
        Serial.println("Using Fixed Time");
#endif

    // Serial.println("");

    // magic!
    h=(h*5)+m/12;
}


void loop() {


  currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    s++;
  }
  if (s==60) {
    s=0;
    m++;
    if ((m==12)||(m==24)||(m==36)||(m==48)) {
      h++;
    }
  }
  if (m==60) {
    m=0;
    h++;
  }
  if (h==60) {
    h=0;
  }

#if defined NTP

  // Update time hourly..
  if (m==0 && s<=1 && !doneUpdate) {
    Serial.print("\nUpdated Time: ");
    printLocalTime();
    h=(h*5)+m/12;
    doneUpdate = true;
  }

  if (m==1 && doneUpdate) {
    doneUpdate = false;
  }
#endif


    DACPrepare(true);

  // Optionals

  // PlotTable(DialDots,sizeof(DialDots),0x00,1,0);
  // PlotTable(TestData,sizeof(TestData),0x00,0,00); //Full
  // PlotTable(TestData,sizeof(TestData),0x00,0,11); //Without square

  // int i;

  // Serial.println("Out Ring");                         //2 to back trace
  // for (i=0; i < 100; i++) PlotTable(DialData,sizeof(DialData)+i,0x00,2,0);

  // Serial.println("Diagonals");                        //2 to back trace
  // for (i=0; i < 2000; i++) PlotTable(DialData,sizeof(DialData),0x00,0,0);


  // PlotTable(byte *SubTable, int SubTableSize, int skip, int opt, int offset)

    PlotTable(DialDigits12,sizeof(DialDigits12),0x00,1,0);//2 to back trace
    PlotTable(DialData,sizeof(DialData),0x00,1,0);      //2 to back trace

    PlotTable(HrPtrData, sizeof(HrPtrData), 0xFF,0,9*h);  // 9*h
    PlotTable(MinPtrData,sizeof(MinPtrData),0xFF,0,9*m);  // 9*m
    PlotTable(SecPtrData,sizeof(SecPtrData),0xFF,0,5*s);  // 5*s


    DACUnprepare(true);

#if defined DEBUG
    Serial.printf("\nHours:%d  Minutes:%d  Seconds:%d\n", h, m, s);
#endif

}




// conditions probably redundant - I must check the compiler docs!

#if defined NTP
void printLocalTime() {

    struct tm timeinfo;

    if (!getLocalTime(&timeinfo)) {

        Serial.println("Failed to obtain time");
        useRealTime = false;

    } else {

        Serial.println(&timeinfo, "%H:%M:%S, %A, %B %d %Y");

        h=(int)timeinfo.tm_hour;
        m=(int)timeinfo.tm_min;
        s=(int)timeinfo.tm_sec;
        if (h > 12) h=h-12;

        // Pluralize.. (devs need to take the time to do this)
        String hpl = ""; String mpl = ""; String spl = "";
        if (h != 1) hpl = "s"; if (m != 1) mpl = "s"; if (s != 1) spl = "s";
        Serial.printf("Setting Analog Clock to %i Hour%s, %i Minute%s and %i Second%s.\n", h, hpl.c_str(), m, mpl.c_str(), s, spl.c_str());
    }
}
#endif



void PlotTable(byte *SubTable, int SubTableSize, int skip, int opt, int offset) {

    int i=offset;
    while (i<SubTableSize){
        if (SubTable[i+2]==skip){
        i=i+3;
        if (opt==1) if (SubTable[i]==skip) i++;
        }
        Line(SubTable[i],SubTable[i+1],SubTable[i+2],SubTable[i+3]);
        if (opt==2){
        Line(SubTable[i+2],SubTable[i+3],SubTable[i],SubTable[i+1]);
        }
        i=i+2;
        if (SubTable[i+2]==0xFF) break;
  }
}



// Line
//**/**************************************************************************
// Bresenham's Algorithm implementation optimized
// also known as a DDA - digital differential analyzer

// Pretty sure this code originates with w2aew (https://www.youtube.com/@w2aew) ;o)

void Line(byte x1, byte y1, byte x2, byte y2)
{
    int acc;
    // for speed, there are 8 DDA's, one for each octant
    if (y1 < y2) { // quadrant 1 or 2
        byte dy = y2 - y1;
        if (x1 < x2) { // quadrant 1
            byte dx = x2 - x1;
            if (dx > dy) { // < 45
                acc = (dx >> 1);
                for (; x1 <= x2; x1++) {
                    Dot(x1, y1);
                    acc -= dy;
                    if (acc < 0) {
                        y1++;
                        acc += dx;
                    }
                }
            }
            else {   // > 45
                acc = dy >> 1;
                for (; y1 <= y2; y1++) {
                    Dot(x1, y1);
                    acc -= dx;
                    if (acc < 0) {
                        x1++;
                        acc += dy;
                    }
                }
            }
        }
        else {  // quadrant 2
            byte dx = x1 - x2;
            if (dx > dy) { // < 45
                acc = dx >> 1;
                for (; x1 >= x2; x1--) {
                    Dot(x1, y1);
                    acc -= dy;
                    if (acc < 0) {
                        y1++;
                        acc += dx;
                    }
                }
            }
            else {  // > 45
                acc = dy >> 1;
                for (; y1 <= y2; y1++) {
                    Dot(x1, y1);
                    acc -= dx;
                    if (acc < 0) {
                        x1--;
                        acc += dy;
                    }
                }
            }
        }
    }
    else { // quadrant 3 or 4
        byte dy = y1 - y2;
        if (x1 < x2) { // quadrant 4
            byte dx = x2 - x1;
            if (dx > dy) {  // < 45
                acc = dx >> 1;
                for (; x1 <= x2; x1++) {
                    Dot(x1, y1);
                    acc -= dy;
                    if (acc < 0) {
                        y1--;
                        acc += dx;
                    }
                }

            }
            else {  // > 45
                acc = dy >> 1;
                for (; y1 >= y2; y1--) {
                    Dot(x1, y1);
                    acc -= dx;
                    if (acc < 0) {
                        x1++;
                        acc += dy;
                    }
                }

            }
        }
        else {  // quadrant 3
            byte dx = x1 - x2;
            if (dx > dy) { // < 45
                acc = dx >> 1;
                for (; x1 >= x2; x1--) {
                    Dot(x1, y1);
                    acc -= dy;
                    if (acc < 0) {
                        y1--;
                        acc += dx;
                    }
                }

            }
            else {  // > 45
                acc = dy >> 1;
                for (; y1 >= y2; y1--) {
                    Dot(x1, y1);
                    acc -= dx;
                    if (acc < 0) {
                        x1--;
                        acc += dy;
                    }
                }
            }
        }

    }
}


// Each individual dot..
inline void Dot(int x, int y) {

    if (lastx!=x){
      lastx=x;
      DAC1Write(x);
    }

    if (lasty!=y){
      lasty=y;
      DAC2Write(y);
    }
}

Welcome to corz.org!

I'm always messing around with the back-end.. See a bug? Wait a minute and try again. Still see a bug? Mail Me!