#include // constants stored in flash (program memory) - Cx51 compiler uses 'code' keyword code unsigned char font[] = { // 8x8 font for ASCII 32 to ASCII 127 (last char currently blank - could be Euro or Pound symbol?) 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA0,0xA0,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFC,0xFC,0xFF,0xFC,0xFC,0xFF,0xFF,0xEB,0x80,0x80,0xEB,0x80,0x80,0xEB, 0xFF,0xED,0xC5,0x94,0x94,0xD1,0xDB,0xFF,0xFF,0xCD,0xA9,0x93,0xE7,0xC9,0x95,0xB3, 0xBF,0x97,0xC5,0x88,0xA6,0xB0,0x81,0xCF,0xFF,0xFF,0xFF,0xFC,0xF8,0xFB,0xFF,0xFF, 0xFF,0xFF,0xBE,0x9C,0xC1,0xE3,0xFF,0xFF,0xFF,0xFF,0xE3,0xC1,0x9C,0xBE,0xFF,0xFF, 0xF7,0xD5,0xC1,0xE3,0xE3,0xC1,0xD5,0xF7,0xFF,0xF7,0xF7,0xC1,0xC1,0xF7,0xF7,0xFF, 0xFF,0xFF,0xFF,0x9F,0x1F,0x7F,0xFF,0xFF,0xFF,0xF7,0xF7,0xF7,0xF7,0xF7,0xF7,0xFF, 0xFF,0xFF,0xFF,0x9F,0x9F,0xFF,0xFF,0xFF,0xFE,0xFC,0xF9,0xF3,0xE7,0xCF,0x9F,0xBF, 0xFF,0xC1,0x80,0xB2,0xA6,0x80,0xC1,0xFF,0xFF,0xFF,0xFF,0x80,0x80,0xF9,0xFB,0xFF, 0xFF,0xB9,0xB0,0xA6,0x8E,0x9C,0xBD,0xFF,0xFF,0xC9,0x80,0xB6,0xB6,0x9C,0xDD,0xFF, 0xFF,0xEF,0x80,0x80,0xEC,0xE9,0xE3,0xE7,0xFF,0xC6,0x82,0xBA,0xBA,0x98,0xD8,0xFF, 0xFF,0xCF,0x86,0xB6,0xB4,0x81,0xC3,0xFF,0xFF,0xF8,0xF0,0x86,0x8E,0xFE,0xFE,0xFF, 0xFF,0xC9,0x80,0xB6,0xB6,0x80,0xC9,0xFF,0xFF,0xE1,0xC0,0x96,0xB6,0xB0,0xF9,0xFF, 0xFF,0xFF,0xFF,0x99,0x99,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x99,0x19,0x7F,0xFF,0xFF, 0xFF,0xDD,0xDD,0xEB,0xEB,0xF7,0xF7,0xFF,0xFF,0xEB,0xEB,0xEB,0xEB,0xEB,0xEB,0xFF, 0xFF,0xF7,0xF7,0xEB,0xEB,0xDD,0xDD,0xFF,0xFF,0xF9,0xF0,0xA6,0xAE,0xFC,0xFD,0xFF, 0xFF,0xE1,0xAC,0xA2,0xA2,0x9C,0xC1,0xFF,0xFF,0x81,0x80,0xF6,0xF6,0x80,0x81,0xFF, 0xFF,0xC9,0x80,0xB6,0xB6,0x80,0x80,0xFF,0xFF,0xBE,0xBE,0xBE,0x9C,0xC1,0xE3,0xFF, 0xFF,0xE3,0xC1,0x9C,0xBE,0x80,0x80,0xFF,0xFF,0xBE,0xBE,0xB6,0xB6,0x80,0x80,0xFF, 0xFF,0xFE,0xFE,0xF6,0xF6,0x80,0x80,0xFF,0xFF,0x85,0x84,0xB6,0xBE,0x80,0xC1,0xFF, 0xFF,0x80,0x80,0xF7,0xF7,0x80,0x80,0xFF,0xFF,0xFF,0xBE,0x80,0x80,0xBE,0xFF,0xFF, 0xFF,0xC0,0x80,0xBF,0xBF,0x9F,0xDF,0xFF,0xFF,0xBE,0x9C,0xC9,0xE3,0xF7,0x80,0x80, 0xFF,0xBF,0xBF,0xBF,0xBF,0x80,0x80,0xFF,0xFF,0x80,0x80,0xF9,0xF3,0xF9,0x80,0x80, 0xFF,0x80,0x80,0xE7,0xF3,0xF9,0x80,0x80,0xFF,0xC1,0x80,0xBE,0xBE,0x80,0xC1,0xFF, 0xFF,0xF9,0xF0,0xF6,0xF6,0x80,0x80,0xFF,0xFF,0xBF,0x81,0x80,0x9E,0xBE,0x80,0xC1, 0xFF,0x99,0x80,0xE6,0xF6,0x80,0x80,0xFF,0xFF,0xCD,0x84,0xA6,0xB2,0x90,0xD9,0xFF, 0xFF,0xFE,0xFE,0x80,0x80,0xFE,0xFE,0xFF,0xFF,0xC0,0x80,0xBF,0xBF,0x80,0xC0,0xFF, 0xFF,0xF0,0xC0,0x8F,0x8F,0xC0,0xF0,0xFF,0xFF,0x80,0x80,0xCF,0xE7,0xCF,0x80,0x80, 0xBE,0x9C,0xC9,0xE3,0xE3,0xC9,0x9C,0xBE,0xFE,0xFC,0xF9,0x83,0x83,0xF9,0xFC,0xFE, 0xFF,0xBE,0xBC,0xB8,0xB2,0xA6,0x8E,0x9E,0xFF,0xFF,0xBE,0xBE,0x80,0x80,0xFF,0xFF, 0xBF,0x9F,0xCF,0xE7,0xF3,0xF9,0xFC,0xFE,0xFF,0xFF,0x80,0x80,0xBE,0xBE,0xFF,0xFF, 0xFF,0xFB,0xF9,0xFC,0xFC,0xF9,0xFB,0xFF,0xFF,0xFF,0xBF,0xBF,0xBF,0xBF,0xBF,0xBF, 0xFF,0xFF,0xFB,0xF8,0xFC,0xFF,0xFF,0xFF,0xFF,0x87,0x83,0xAB,0xAB,0x8B,0xDF,0xFF, 0xFF,0xC7,0x83,0xBB,0xBB,0x80,0x80,0xFF,0xFF,0xFF,0xBB,0xBB,0xBB,0x83,0xC7,0xFF, 0xFF,0x80,0x80,0xBB,0xBB,0x83,0xC7,0xFF,0xFF,0xE7,0xA3,0xAB,0xAB,0x83,0xC7,0xFF, 0xFF,0xFF,0xFA,0xFA,0x80,0x81,0xFB,0xFF,0xFF,0x83,0x03,0x5B,0x5B,0x43,0xE7,0xFF, 0xFF,0x87,0x83,0xFB,0xFB,0x80,0x80,0xFF,0xFF,0xFF,0xBF,0x82,0xC2,0xFF,0xFF,0xFF, 0xFF,0xFF,0x82,0x02,0x7F,0x7F,0x7F,0xFF,0xFF,0xBB,0x93,0xC7,0xEF,0x80,0x80,0xFF, 0xFF,0xFF,0xBF,0x80,0xC0,0xFF,0xFF,0xFF,0xFF,0x87,0x83,0xF3,0xE7,0xF3,0x83,0x83, 0xFF,0x87,0x83,0xFB,0xFB,0x83,0x83,0xFF,0xFF,0xC7,0x83,0xBB,0xBB,0x83,0xC7,0xFF, 0xFF,0xE7,0xC3,0xDB,0xDB,0x03,0x03,0xFF,0xFF,0x03,0x03,0xDB,0xDB,0xC3,0xE7,0xFF, 0xFF,0xF7,0xF3,0xFB,0xFB,0x83,0x83,0xFF,0xFF,0xDF,0x8B,0xAB,0xAB,0xA3,0xB7,0xFF, 0xFF,0xFF,0xBB,0xBB,0x80,0xC0,0xFB,0xFF,0xFF,0x83,0x83,0xBF,0xBF,0x83,0xC3,0xFF, 0xFF,0xE3,0xC3,0x9F,0x9F,0xC3,0xE3,0xFF,0xFF,0xC3,0x83,0x9F,0xCF,0x9F,0x83,0xC3, 0xFF,0xBB,0x93,0xC7,0xEF,0xC7,0x93,0xBB,0xFF,0xE3,0xC3,0x9F,0x1F,0x43,0xE3,0xFF, 0xFF,0xBB,0xB3,0xA3,0x8B,0x9B,0xBB,0xFF,0xFF,0xFF,0xBE,0x88,0xC1,0xF7,0xFF,0xFF, 0xFF,0xFF,0xFF,0x80,0x80,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0xC1,0x88,0xBE,0xFF,0xFF, 0xFF,0xFD,0xF9,0xFB,0xF9,0xFD,0xF9,0xFB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; // display is 32-char circumference, but only display 16 chars in red on one side and the same in blue on the other code unsigned char botRow[] = " It was the best of times, it was the worst of times, " "it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, " "it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, " "we had everything before us, we had nothing before us, we were all going direct to Heaven, " "we were all going direct the other way - in short, the period was so far like the present period, " "that some of its noisiest authorities insisted on its being received, for good or for evil, " "in the superlative degree of comparison only. "; unsigned int botRowOffset; unsigned char botRowPartChar; // global vars. Cx51 compiler has 16-bit ints (shorts are also 16-bit) unsigned char code * data topFontPtr; // ptr in data to code unsigned char - for top row of display unsigned char code * data botFontPtr; // variables accessed from within interrupt AND from normal code declared volatile struct getM16 { // access the middle 16-bit chunk of a 32-bit value unsigned char first8; int m16; unsigned char last8; }; union union32 { // phased locked loop counter generates rotation angles from P4.3 (INT2) photodiode index pulse unsigned long count32; struct getM16 m16; unsigned char count8[4]; }; union union32 rateCalc; unsigned char topRow[16]; // (ASCII - 32) top row - driven by clock. Display is 32-char circumference but chars 16-31 are just a copy of chars 0-15 unsigned int PLLrate = 22500U; // 22500 generates about 256 counts per rev in rateCalc[1] @ ~772 rpm (4.4 volts) and 9.6 kHz volatile unsigned char ticks; // used for timers unsigned char topAng; unsigned char botAng; unsigned int fracSecs; // fractions of second in ticks unsigned char turnsInCurrentSecond; unsigned int topAngle, topAngleRate; // unsigned int botAngle, botAngleRate; // unsigned char b; // unsigned int fontIndex; volatile bit turning; unsigned int ticksThisTurn; void T1INT(void) interrupt 3 using 1 { // configured to fire @ 9.6 kHz bit INT2snapshot; bit prevINT2snapshot; // edge detection on INT2snapshot unsigned int i; ++ticks; ++ticksThisTurn; // run ASCII clock hh:mm:ss embedded in first 8 chars of topRow[] if (++fracSecs == 9600) { // just update time and exit interrupt fracSecs = 0; turning = turnsInCurrentSecond > 5; turnsInCurrentSecond = 0; if (++topRow[7] == 26) { // units seconds overflow topRow[7] = 16; if (++topRow[6] == 22) { // tens seconds overflow (1 minute) topRow[6] = 16; if (++topRow[4] == 26) { // units minutes overflow topRow[4] = 16; if (++topRow[3] == 22) { // tens minutes overflow (1 hour) topRow[3] = 16; if (++topRow[1] == 20 && topRow[0] == 18) { // midnight topRow[0] = topRow[1] = 16; } else if (topRow[1] == 26) { // 10am or 8pm topRow[1] = 16; ++topRow[0]; } } } } } } else { // run 'phase-locked loop' on P4.3 (INT2) photodiode index pulse. INT2 is low when photodiode is illuminated prevINT2snapshot = INT2snapshot; INT2snapshot = INT2; if (!INT2snapshot && prevINT2snapshot && (rateCalc.count8[0] || rateCalc.count8[1] > 64)) { // edge detect: rateCalc.count8[1] should count to 64 in about 1/4 rev // correction is: PLLrate += (0x01000000 - rateCalc.count32) >> 10; // but we can use some tricks to do it faster PLLrate -= rateCalc.m16.m16 >> 2; /* this version that worked with an unsigned m16 was a bit slower if (rateCalc.count8[0]) { // PLLrate was too high - need to subtract PLLrate -= rateCalc.m16.m16 >> 2; // this is just a quick-and-very-dirty way of avoiding a 10-bit shift on a 32-bit word (assumes the correction is < 16384) } else { // PLL rate was too low (or perfect) - need to add correction PLLrate += ((rateCalc.m16.m16 >> 2) ^ 0x3FFFU) + 1; // twos complement } */ rateCalc.count32 = 0L; // constrain PLL rate between 16384 and 32767 (actual rate at 3.7V about 20480, at 5.1V about 24832 if (PLLrate & 0x8000U) PLLrate = 0x7FFFU; else if (!(PLLrate & 0x4000U)) PLLrate = 0x4000U; topAngleRate = PLLrate + 47; // this has the effect of slowly rotating the display anti-clockwise, so it can be seen from all sides // anti-clockwise so as to contrast with 'scrolly text' which appears to 'rotate' clockwise, though the seam between red and blue is sync-locked ++turnsInCurrentSecond; // set timer 2 to generate 256 interrupts per turn (to give 256 pixels) based on ticksThisTurn count from previous turn // clocksPerPixel = ticksThisTurn * 3/4 but timer2 counts up so formula is 0xFFFF - ((3 * ticksThisTurn) / 4) // the -5 or -1 depending on whether or not botAng counted past 255 last turn syncs the 'seam' between red and blue to the I.R. LED (eventually) i = 0xFFFFU - ((ticksThisTurn * 3 - (botAng & 0x80 ? 5 : 1)) >> 2); // move the high and low bytes of the result into i and ticksThisTurn ticksThisTurn = i & 0x00FF; i >>= 8; EA = 0; // disable interrupts while writing two 8-bit registers RCAP2H = i; RCAP2L = ticksThisTurn; EA = 1; ticksThisTurn = 0; } else { rateCalc.count32 += PLLrate; // target is 256 counts per rev in rateCalc.count8[1] } } if (turning) { // output pixels TR2 = 1; // enable timer 2 interrupts i = topAngle; topAngle += topAngleRate; if (topAngle < i) { // time to output next pixels if (!(++topAng & 0x07)) { // start of a new character topFontPtr = font + (topRow[15 - ((topAng & 0x7F) >> 3)] << 3); } P1 = P2 = *topFontPtr++; } } else { // not turning TR2 = 0; // disable timer 2 interrupts } } void T2INT(void) interrupt 5 using 2 { unsigned char b; TF2 = 0; // clear timer 2 overflow flag //TOPLED = ~TOPLED; b = ++botAng + botRowPartChar; if (b & 0x80) { // if in the second half of a turn if (!(b & 0x07)) { // start of a new character botFontPtr = font + ((botRow[botRowOffset - ((b & 0x7F) >> 3)] - 32) << 3); } P0 = P3 = *botFontPtr++; } else if (!b) { P0 = P3 = 0xFF; // LEDs off for first half of turn } else if (botAng == 0x40) { // advance botRow offset if (!botRowPartChar) { botRowPartChar = 6; if (!botRow[++botRowOffset]) { botRowOffset = 15; } } else if (botRowPartChar == 6) { botRowPartChar = 4; } else if (botRowPartChar == 4) { botRowPartChar = 2; } else { botRowPartChar = 0; } } } void setTime(unsigned char hour, unsigned char minute, unsigned char second) using 0 { int i; for (i = 0; i < 16; i++) { topRow[i] = 0; } topRow[5] = topRow[2] = ':' - 32; topRow[7] = (second % 10) + '0' - 32; topRow[6] = (second / 10) + '0' - 32; topRow[4] = (minute % 10) + '0' - 32; topRow[3] = (minute / 10) + '0' - 32; topRow[1] = (hour % 10) + '0' - 32; topRow[0] = (hour / 10) + '0' - 32; } void main() using 0 { unsigned int debounceTimer; unsigned int cyclingPatternTimer; unsigned int photoDiodeOnTime; unsigned int photoDiodeOffTime; int i; unsigned char cyclingPattern1; // copied to all LEDS to indicate clock setting complete unsigned char cyclingPattern2; // used to indicate clock setting stage on P2 unsigned char hourSet, minuteSet; // count flashes when setting time char mode; // for clock setting unsigned char elapsed, ticksSnapshot, prevTicks; bit debouncedPhotoDiode; bit previousDebouncedPhotoDiode; setTime(11, 11, 0); botRowOffset = 15; // set up timer 1 interrupt for 9.6 kHz (assuming 11.0592 MHz xtal) TMOD = 0x20; // timer 1 in 8-bit auto-reload mode // TH1 = 0xA0; // reload value on overflow. A0 generates 9.6 kHz; 00 gives (slowest) 3.6 kHz - these figures for cpu_12t TH1 = 0x40; // equivalent of 0xA0 for cpu_6t ET1 = 1; // enable timer 1 interrupt EA = 1; // enable interrupts TR1 = 1; // run timer 1 PT1 = 1; // timer 1 has high priority PT2 = 0; // timer 2 has low priority T2MOD = 0x00; // configure timer 2 to count up with output (P1.0) disabled RCLK = 0; TCLK = 0; // configure timer 2 in 16-bit auto-reload mode EXEN2 = 0; // timer 2 ignores P1.1 transitions C_T2 = 0; // timer 2 in timer (not counter) mode CP_RL2 = 0; // timer 2 enable auto-reload on overflow RCAP2L = 0xCD; // initial reload value (will be altered by T1 interrupt calculations) RCAP2H = 0xFD; // initial reload value - top 8 bits ET2 = 1; // enable timer 2 interrupts cyclingPattern1 = 0x77; // just two LEDs illuminated: more sucks milliamps and needs higher supply voltage cyclingPattern2 = 0xFF; // all LEDs off for mode 0 - gets, 1, 2, 3, 4 LEDS on to indicate clock setting stage mode = 0; P0 = P1 = P2 = P3 = 0xFF; // all LEDs off for (;;) { prevTicks = ticksSnapshot; ticksSnapshot = ticks; elapsed = ticksSnapshot - prevTicks; if (turning) { // if rotor is spinning // P3 = (rateCalc.count8[1] | 0x3F) & 0xFE; // P0 = (topAng | 0x3F) & 0xFE; // TOPLED = ~TOPLED; // loop time indicator // P3 = ~elapsed; // loop time indicator mode = 0; // reset mode ready for next stop } else { // rotor not spinning - so allow clock setting and photodiode diagnostic cyclingPatternTimer += elapsed; if (cyclingPatternTimer >= 1200) { cyclingPatternTimer -= 1200; i = cyclingPattern1; i <<= 1; cyclingPattern1 = (unsigned char)i; if (i & 0x0100) cyclingPattern1 |= 0x01; i = cyclingPattern2; i <<= 1; cyclingPattern2 = (unsigned char)i; if (i & 0x0100) cyclingPattern2 |= 0x01; } previousDebouncedPhotoDiode = debouncedPhotoDiode; if (debouncedPhotoDiode != INT2) { debounceTimer += elapsed; if (debounceTimer > 960) { // 0.1 seconds debouncedPhotoDiode = INT2; } } else { debounceTimer = 0; } if (debouncedPhotoDiode) { // not illuminated photoDiodeOnTime = 0; photoDiodeOffTime += elapsed; if (photoDiodeOffTime > 28800U) { // stop timer at three seconds photoDiodeOffTime = 28800U; } } else { // illuminated photoDiodeOffTime = 0; photoDiodeOnTime += elapsed; if (photoDiodeOnTime > 28800U) { // stop timer at three seconds photoDiodeOnTime = 28800U; } } if (mode < 5) { P0 = debouncedPhotoDiode ? 0xFF : 0xFC; // illuminate 2 P0 LEDs when photodiode illuminated - all off otherwise } switch (mode) { case 0: // idle: three secs of illumination moves to mode 1 cyclingPattern2 = 0xFF; // all LEDs off P1 = P2 = P3 = 0xFF; hourSet = minuteSet = 0; if (photoDiodeOnTime == 28800U) { photoDiodeOnTime = 0; cyclingPattern2 = 0x7F; // one LED on mode = 1; } break; case 1: // counting hours in tens: three secs illumination moves to mode 2. three secs off escapes to mode 0 P2 = cyclingPattern2; if (previousDebouncedPhotoDiode && !debouncedPhotoDiode) { // if it has just been illuminated hourSet += 10; if (hourSet > 20) hourSet = 20; } if (photoDiodeOnTime == 28800U) { photoDiodeOnTime = 0; cyclingPattern2 = 0x3F; // two LEDs on mode = 2; } if (photoDiodeOffTime == 28800U) { mode = 0; } break; case 2: // counting hours: three secs illumination moves to mode 2. three secs off escapes to mode 0 P2 = cyclingPattern2; if (previousDebouncedPhotoDiode && !debouncedPhotoDiode) { // if it has just been illuminated if (++hourSet > 23) hourSet = 23; } if (photoDiodeOnTime == 28800U) { photoDiodeOnTime = 0; cyclingPattern2 = 0x1F; // three LEDs on mode = 3; } if (photoDiodeOffTime == 28800U) { mode = 0; } break; case 3: // counting minutes in tens: three secs illumination moves to mode 3. three secs off escapes to mode 0 P2 = cyclingPattern2; if (previousDebouncedPhotoDiode && !debouncedPhotoDiode) { // if it has just been illuminated minuteSet += 10; if (minuteSet > 50) minuteSet = 50; } if (photoDiodeOnTime == 28800U) { photoDiodeOnTime = 0; cyclingPattern2 = 0x0F; // four LEDs on mode = 4; } if (photoDiodeOffTime == 28800U) { mode = 0; } break; case 4: // counting minutes in ones: three secs illumination moves to mode 4. three secs off escapes to mode 0 P2 = cyclingPattern2; if (previousDebouncedPhotoDiode && !debouncedPhotoDiode) { // if it has just been illuminated if (++minuteSet > 59) minuteSet = 59; } if (photoDiodeOnTime == 28800U) { photoDiodeOnTime = 0; mode = 5; } if (photoDiodeOffTime == 28800U) { mode = 0; } break; case 5: // keep setting the clock to the specified time till rotation starts P3 = P2 = P1 = P0 = cyclingPattern1; setTime(hourSet, minuteSet, 0); break; } } } }