Your are proud of your expensive oscilloscope, now you can turn it into a cheap digital clock!

Overview

Your are proud of your expensive oscilloscope, now you can turn it into a cheap digital clock! Uses 2-bit R2R DAC for 4 voltage levels. Three frames per digit due to retinal persistence.

How to set up

Oscilloscope setup: timebase 0.1ms, 1V/div, external trigger. RA0 triggers oscilloscope vertical sync.

Circuit Schematic

PicOClock circuit schematic
PicOClock circuit schematic

Frame Rendering

Each digit is rendered across 3 frames (upper, middle, lower horizontal lines) using retinal persistence to display the full character.

PicOClock frame 1
Frame 1 — upper horizontal lines
PicOClock frame 2
Frame 2 — middle horizontal lines
PicOClock frame 3
Frame 3 — lower horizontal lines
PicOClock frame 4
Frame 4 — combined result

C Source Code

picoClock.c
/*
 *******************************************************************************
 * PICOCLOCK : PIC Oscilloscope CLOCK
 *******************************************************************************
 *
 * This program shows how to display a digital clock on an oscilloscope
 * with a PIC and only 4 resistors.
 *
 * Circuit schematics :
 *
 * ------------+
 *         RA0 +----------------> to oscilloscope X trigger input
 *             |
 *         RA1 +----------------> pull-up, button to ground : minutes adjustment
 *         RA2 +----------------> pull-up, button to ground : hours adjustment
 *  PIC        |     ____
 *         RB1 +----|____|-----+----------> to oscilloscope Y input
 *             |     680       |
 *             |              +-+
 *             |              | | 330
 *             |              +-+
 *             |     ____      |
 *         RB0 +----|____|-----+
 *             |     680       |
 * ------------+              +-+
 *                            | | 680
 *                            +-+
 *                             |
 *                           -----
 *                            ---   GND
 *                             -
 *
 * Oscilloscope setup :
 * set timebase to 0.1 ms, V/div = 1 V
 * select external trigger.
 *
 * source code for mikro C compiler V7.0.0.3
 * feel free to use this code at your own risks
 *
 * target : PIC16 or PIC18, 16 Mhz crystal
 * HS clock, no watchdog.
 *
 * tested with PIC16F84A and PIC16F877A
 *
 *******************************************************************************
 */
#define TRIGGER         PORTA.F0        // oscilloscope trigger output
#define KEY             PORTA & 0b110   // input keys mask
#define KEY_MIN_UP      PORTA & 0b010   // minute adjust button
#define KEY_HH_UP       PORTA & 0b100   // hour adjust button

/*
 * 2 bits R2R DAC gives 4 output levels :
 */
#define HIGH            PORTB = 0b11    // upper line
#define MID             PORTB = 0b10    // middle line
#define LOW             PORTB = 0b01    // lower line
#define ZERO            PORTB = 0b00    // lowest line

#define MAX_SCALER      15625           // timer 0 overflows/sec @ 16 MHz

#define MAX_DIGIT        6              // number of digits to display
#define SLOTS           (MAX_DIGIT * 3 + 4)     // time slots: 2 header, 3/digit, 2 trailer

/*
 * 10 digits 7 segment encoding + blank
 */
const unsigned char     septSeg[11] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x00 } ;

/*
 * slot index for digit start
 */
const unsigned char     sIdx[] = {1, 4, 8, 11, 15, 18} ;

unsigned char display[MAX_DIGIT] ;      // digit to be displayed

/*
 * time slot encoded line flags :
 * bit 0 is upper line
 * bit 1 is middle line
 * bit 2 is lower line
 * bit 6 is lower vertical bar
 * bit 7 is upper vertical bar
 */
unsigned char line[SLOTS] ;

unsigned char dIdx = 0 ;        // time slot counter
unsigned char fIdx = 0 ;        // frame counter

unsigned int scaler = 0 ;       // RTC scaler
unsigned char   ss = 0, mn = 0, hh = 0 ;        // RTC

void    delay10us()
        {
        Delay_us(10) ;
        }

/*
 * ISR
 */
void    interrupt(void)
        {
        if(INTCON.T0IF)
                {
                scaler++ ;
                if(scaler > MAX_SCALER)
                        {
                        scaler = 0 ;
                        ss++ ;
                        if(ss == 60)
                                {
                                ss = 0 ;
                                mn++ ;
                                if(mn == 60)
                                        {
                                        mn = 0 ;
                                        hh++ ;
                                        if(hh == 24)
                                                {
                                                hh = 0 ;
                                                }
                                        }
                                }
                        }

                if(line[dIdx].F6 && line[dIdx].F7)              // full vertical bar
                        {
                        LOW, HIGH, LOW, HIGH ;
                        LOW, HIGH, LOW, HIGH ;
                        LOW, HIGH, LOW, HIGH ;
                        LOW, HIGH, LOW, HIGH ;
                        LOW, HIGH, LOW, HIGH ;
                        LOW, HIGH, LOW, HIGH ;
                        LOW, HIGH, LOW, HIGH ;
                        LOW, HIGH, LOW, HIGH ;
                        }
                else if(line[dIdx].F6)                          // lower vertical bar
                        {
                        MID, LOW, MID, LOW ;
                        MID, LOW, MID, LOW ;
                        MID, LOW, MID, LOW ;
                        MID, LOW, MID, LOW ;
                        MID, LOW, MID, LOW ;
                        MID, LOW, MID, LOW ;
                        MID, LOW, MID, LOW ;
                        MID, LOW, MID, LOW ;
                        }
                else if(line[dIdx].F7)                          // upper vertical bar
                        {
                        MID, HIGH, MID, HIGH ;
                        MID, HIGH, MID, HIGH ;
                        MID, HIGH, MID, HIGH ;
                        MID, HIGH, MID, HIGH ;
                        MID, HIGH, MID, HIGH ;
                        MID, HIGH, MID, HIGH ;
                        MID, HIGH, MID, HIGH ;
                        MID, HIGH, MID, HIGH ;
                        }

                if(dIdx == 7)                                   // hour:minute separator
                        {
                        LOW, Delay10us() ;
                        MID, Delay10us() ;
                        }
                else if(dIdx == 14)                             // minute:second separator
                        {
                        if(scaler < MAX_SCALER / 2)             // blink 0.5 Hz
                                {
                                LOW, Delay10us() ;
                                MID, Delay10us() ;
                                }
                        }

                switch(fIdx)
                        {
                        case 0:                                 // upper line
                                if(line[dIdx] & 1)
                                        { HIGH ; }
                                else
                                        { ZERO ; }
                                break ;
                        case 1:                                 // middle line
                                if(line[dIdx] & 2)
                                        { MID ; }
                                else
                                        { ZERO ; }
                                break ;
                        case 2:                                 // lower line
                                if(line[dIdx] & 4)
                                        { LOW ; }
                                else
                                        { ZERO ; }
                                break ;
                        }

                dIdx++ ;
                if(dIdx == SLOTS)
                        {
                        dIdx = 0 ;

                        TRIGGER = 1 ;                           // trigger the scope
                        fIdx++ ;
                        if(fIdx == 3)
                                {
                                fIdx = 0 ;
                                }
                        TRIGGER = 0 ;
                        }
                INTCON.T0IF = 0 ;
                }
        }

/*
 * main entry
 */
void    main()
        {
#ifdef P16F877A
        ADCON1 = 7 ;
        CMCON = 7 ;
#endif

        TRISA = 0b110 ;
        PORTA = 0 ;

        TRISB = 0 ;
        PORTB = 0 ;

        memset(&line, 0, sizeof(line)) ;
        memset(display, 0, sizeof(display)) ;

        OPTION_REG = 0b11011000 ;                               // timer 0 prescaler 1:1
        INTCON = 0b10100000 ;                                   // start interrupts

        for(;;)
                {
                unsigned char i ;

                if(KEY)                                         // button pressed?
                        {
                        if(KEY_MIN_UP)                          // adjust minutes
                                {
                                ss = 0 ;
                                mn++ ;
                                }
                        else if(KEY_HH_UP)                      // adjust hours
                                {
                                ss = 0 ;
                                hh++ ;
                                }
                        mn %= 60 ;
                        hh %= 24 ;
                        Delay_ms(100) ;                         // debounce
                        }

                /*
                 * prepare display buffer
                 */
                display[5] = ss % 10 ;                          // seconds
                display[4] = ss / 10 ;

                display[3] = mn % 10 ;                          // minutes
                display[2] = mn / 10 ;

                display[1] = hh % 10 ;                          // hours
                display[0] = (hh > 9) ? hh / 10 : 10 ;          // blank if zero

                /*
                 * prepare time slot flags
                 */
                for(i = 0 ; i < MAX_DIGIT ; i++)
                        {
                        unsigned char s ;
                        unsigned char *p ;

                        s = septSeg[display[i]] ;               // 7 segment encoding

                        p = &line[sIdx[i]] ;                    // pointer to time slot

                        (*p).F0 = s.F0 ;                        // a segment
                        (*p).F1 = s.F6 ;                        // g segment
                        (*p).F2 = s.F3 ;                        // d segment

                        (*p).F6 = s.F4 ;                        // e segment
                        (*p).F7 = s.F5 ;                        // f segment

                        p++ ;                                   // center part

                        (*p).F0 = s.F0 ;                        // a (continuation)
                        (*p).F1 = s.F6 ;                        // g (continuation)
                        (*p).F2 = s.F3 ;                        // d (continuation)

                        p++ ;                                   // right part

                        (*p).F6 = s.F2 ;                        // b segment
                        (*p).F7 = s.F1 ;                        // c segment
                        }
                }
        }