// hourglass kit with 57 LEDs driven by STC15W204 This version with all the patterns is too large to fit in an STC15W201 // if you have the smaller-memory STC15W201, see hourglass-201a.c and hourglass-201b.c for pruned-down versions which will fit // // ceptimus June 2022 // compile with sdcc hourglass.c // flash to chip using stc8prog -p /dev/ttyUSB0 -r 50 -e -f hourglass.ihx // (omit -r 50 if you don't have an auto-powercycle adapter) // // LEDs are connected in a 5x6 matrix with two LEDs of opposite polarity at each node: so could do 5x6x2 = 60 LEDs, but the last three are missing. // LED numbering and multiplex stage: // // -P10 +P10 -P11 +P11 -P12 +P12 -P13 +P13 -P14 +P14 -P15 +P15 stage // +P30 1 11 21 31 41 51 0 (P30 is also RXD) // -P30 6 16 26 36 46 56 1 // // +P31 2 12 22 32 42 52 2 (P31 is also TXD) // -P31 7 17 27 37 47 57 3 // // +P33 3 13 23 33 43 53 4 // -P33 8 18 28 38 48 5 // // +P36 4 14 24 34 44 54 6 // -P36 9 19 29 39 49 7 // // +P37 5 15 25 35 45 55 8 // -P37 10 20 30 40 50 9 // // The low-numbered LEDs are the bottom of the hourglass, using the standard kit-supplied program. // // Push button on P32. P30 is also RXD, P31 also TXD. P54(RST) and P55 unused. // // Physical layout: // // 51 1 // 45 8 // 52 40 14 2 // 46 36 19 9 // 53 41 33 23 15 3 // 47 37 31 26 20 10 // 54 42 34 30 29 28 24 16 4 // 48 38 32 27 21 11 // 55 43 35 25 17 5 // 49 39 22 12 // 56 44 18 6 // 50 13 // 57 7 #include<8052.h> __sfr __at (0x8E) AUXR; __sfr __at (0x91) P1M1; __sfr __at (0x92) P1M0; __sfr __at (0xB1) P3M1; __sfr __at (0xB2) P3M0; __sfr __at (0xD6) T2H; __sfr __at (0xD7) T2L; __sfr __at (0xAF) IE2; #define uint8_t unsigned char typedef void(*functionPointer)(); uint8_t port3bitTable[5] = {0,1,3,6,7}; volatile uint8_t columns[10]; volatile uint8_t pushButton = 0; // interrupt routine, besides multiplexing the LEDs, debounces the push button and sets this flag on a fresh press unsigned int patternStage; // used by the various patternX() functions to keep track of progress __code uint8_t knightRider[] = {3,15,23,27,28,29,33,41,53}; // LEDs on centre axis of hourglass // perimeter of lower triangle (1st 19) 'inner perimeter' next 10. centre last, upperPerimeter[n] = 56 - perimeter[n] __code uint8_t perimeter[] = {0,1,2,3,4,5,6,12,17,21,24,26,27,25,22,18,13,7,8,9,10,11,16,20,23,19,14,15}; __code uint8_t horizStarts[] = {0,7,13,18,22,25,27,28,29,30,32,35,39,44,50,57}; // first LED on each hourglass horizontal row __code uint8_t destinations[] = {3,4,2,9,10,15,1,5,8,11,0,6,7,12,14,16,19,20,13,17,23,18,21,22,24,25,26}; // destination for each grain of sand in the hourglass animation __code uint8_t diagonals[] = {0,1,7,2,8,13,3,9,14,18,4,10,15,19,22,5,11,16,20,23,25,6,12,17,21,24,26,27}; void T2INT(void) __interrupt 12 __using 1 { // timer 2 interrupt set to happen at 3600 Hz, so overall multiplex cycle at 360 Hz static uint8_t pushButtonDebounce = 0; static uint8_t stage = 9; // multiplex LEDs (ten rows of 6 columns) uint8_t m0, m1, p; if (!P3_2) { // if button pressed if (!pushButtonDebounce) { // ...and was released for previous 50ms pushButton = 1; // set flag for main routine } pushButtonDebounce = 180; // 50ms } else if (pushButtonDebounce) { // run timer when button is released --pushButtonDebounce; } m0 = columns[stage] & 0x3F; m1 = m0 ^ 0x3F; // for lit bits m0=1, m1=0 (push-pull); for unlit bits m0=0; m1=1 (input only); P1_6 and P1_7 remain m0=0,m1=0 (standard quasi-bidirectional) p = stage & 0x01 ? m0 : m1; P1M0 = 0x00; // set all 'column' (P1) I/O to input only P1M1 = 0x3F; P3M0 = 0x00; // set all 'row' (P3) I/O input only P3M1 = 0xCB; P1 = p; // preset column output bits high or low P1M0 = m0; P1M1 = m1; // switch lit column bits to push-pull mode, other bits remain input only m0 = 0x01 << port3bitTable[stage >> 1]; m1 = m0 ^ 0xCB; // will set selected I/O to push-pull mode p = (stage & 0x01) ? m1 : m0; // output bits high or low P3 = p | 0x04; // preset row output bit high or low; P3_2 (push button input) always high P3M0 = m0; P3M1 = m1; // set push-pull mode for that row output bit if (!stage) { // each interrupt selects next of 10 rows. stage = 9; } else { --stage; } } // code-space saving kludge: prevent linker from linking in integer divide ( / ) and integer modulus ( % ) functions by never using them, and instead do this repeated subtraction uint8_t div; unsigned int divMod(unsigned int n, uint8_t d) { div = 0; // on return, div = n / d; while (n >= d) { n -= d; div++; } return n; // return n % d; } uint8_t rowMask(uint8_t n) { n = divMod(n, 10); div = 0x01 << div; // div now holds mask return n > 4 ? ((n - 5) << 1) | 1 : n << 1; // row } void setLED(uint8_t n, uint8_t on) { // n:0 to 56; on:true or false n = rowMask(n); // row if (on) { columns[n] |= div; } else { columns[n] &= ~div; } } void toggleLED(uint8_t n) { // n:0 to 56; toggle between lit and unlit n = rowMask(n); // row columns[n] ^= div; } void setAllLEDs(uint8_t on) { uint8_t i; for (i = 0; i < 57; i++) { setLED(i, on); } } void delayMillis(uint8_t ms) { uint8_t i; unsigned int j; for (i = 0; i < ms; i++) { for (j = 0; j < 804; j++) { // 804 seems about right with default FOSC = 11.0592 MHz __asm nop __endasm; } } } // patterns void ptn0(void) { // light LED 0 to 56 in ascending order for 0.1s setLED((uint8_t)patternStage, 1); delayMillis(100); setLED((uint8_t)patternStage, 0); if (++patternStage > 56) { patternStage = 0; } } void ptn1(void) { // light LED 56 to 0 in descending order for 0.1s if (!patternStage) { patternStage = 57; } --patternStage; setLED((uint8_t)patternStage, 1); delayMillis(100); setLED((uint8_t)patternStage, 0); } void ptn2(void) { // knight rider KITT-style bouncing LEDs on hourglass centre axis uint8_t led; led = patternStage < 9 ? knightRider[patternStage] : knightRider[16 - patternStage]; setLED(led, 1); delayMillis(125); setLED(led, 0); if (++patternStage > 15) { patternStage = 0; } } void ptn3(void) { // cycle around perimeters of upper and lower hourglass triangles uint8_t led; led = perimeter[patternStage]; setLED(led, 1); setLED(56 - led, 1); delayMillis(56); setLED(led, 0); setLED(56 - led, 0); if (++patternStage > 17) { patternStage = 0; } } void ptn4(void) { // like pattern 0, but toggle toggleLED((uint8_t)patternStage); delayMillis(100); if (++patternStage > 56) { patternStage = 0; } } void ptn5(void) { // like pattern 1, but toggle if (!patternStage) { patternStage = 57; } --patternStage; toggleLED((uint8_t)patternStage); delayMillis(100); } void ptn6(void) { // like pattern 2, but toggle toggleLED(patternStage < 9 ? knightRider[patternStage] : knightRider[16 - patternStage]); delayMillis(125); if (++patternStage > 15) { patternStage = 0; } } void ptn7(void) { // like pattern 3. but toggle uint8_t led; led = perimeter[patternStage]; toggleLED(led); toggleLED(56 - led); delayMillis(56); if (++patternStage > 17) { patternStage = 0; } } void toggleHorizRow(void) { uint8_t led; for (led = horizStarts[patternStage]; led < horizStarts[patternStage + 1]; led++) { toggleLED(led); } } void ptn8(void) { // horizontal rows, from bottom toggleHorizRow(); delayMillis(100); toggleHorizRow(); if (horizStarts[++patternStage] == 57) { patternStage = 0; } } void ptn9(void) { // horizontal rows, from top if (!patternStage) { patternStage = 15; } --patternStage; toggleHorizRow(); delayMillis(100); toggleHorizRow(); } void ptn10(void) { // horizontal rows, from bottom and top together toggleHorizRow(); patternStage = 14 - patternStage; toggleHorizRow(); delayMillis(100); toggleHorizRow(); patternStage = 14 - patternStage; toggleHorizRow(); if (horizStarts[++patternStage] == 57) { patternStage = 0; } } void ptn11(void) { // like pattern8, but toggle toggleHorizRow(); delayMillis(100); if (horizStarts[++patternStage] == 57) { patternStage = 0; } } void ptn12(void) { // like pattern9, but toggle if (!patternStage) { patternStage = 15; } --patternStage; toggleHorizRow(); delayMillis(100); } void ptn13(void) { // like pattern10, but toggle toggleHorizRow(); patternStage = 14 - patternStage; toggleHorizRow(); patternStage = 14 - patternStage; delayMillis(100); if (horizStarts[++patternStage] == 57) { patternStage = 0; } } void ptn14(void) { // toggle every 16th LED - looks like random-order fill and then empty toggleLED(patternStage); delayMillis(50); patternStage += 16; patternStage = divMod(patternStage, 57); } void ptn15(void) { // spiral around upper and lower hourglass triangles uint8_t led; led = perimeter[patternStage]; setLED(led, 1); setLED(56 - led, 1); delayMillis(100); setLED(led, 0); setLED(56 - led, 0); if (++patternStage > 27) { toggleLED(28); patternStage = 0; } } void ptn16(void) { // spiral in, then out around upper and lower hourglass triangles uint8_t led; led = perimeter[patternStage > 27 ? 54 - patternStage : patternStage]; setLED(led, 1); setLED(56 - led, 1); delayMillis(100); setLED(led, 0); setLED(56 - led, 0); if (++patternStage > 54) { toggleLED(28); patternStage = 0; } } void ptn17(void) { // like ptn15, but toggle uint8_t led; led = perimeter[patternStage]; toggleLED(led); toggleLED(56 - led); delayMillis(100); if (++patternStage > 27) { toggleLED(28); patternStage = 0; } } void ptn18(void) { // like ptn16, but toggle uint8_t led; led = perimeter[patternStage > 27 ? 54 - patternStage : patternStage]; toggleLED(led); toggleLED(56 - led); delayMillis(100); if (++patternStage > 54) { toggleLED(28); patternStage = 0; } } void ptn19(void) { // shrinking triangles uint8_t i, start, end; switch (patternStage) { case 0: start = 0; end = 18; break; case 1: start = 18; end = 27; break; default: start = 27; end = 28; break; } for (i = start; i < end; i++) { setLED(perimeter[i], 1); setLED(56 - perimeter[i], 1); } delayMillis(200); delayMillis(200); for (i = start; i < end; i++) { setLED(perimeter[i], 0); setLED(56 - perimeter[i], 0); } if (++patternStage > 2) { toggleLED(28); patternStage = 0; } } void ptn20(void) { // expanding triangles uint8_t i, start, end; switch (patternStage) { case 0: start = 27; end = 28; break; case 1: start = 18; end = 27; break; default: start = 0; end = 18; break; } for (i = start; i < end; i++) { setLED(perimeter[i], 1); setLED(56 - perimeter[i], 1); } delayMillis(200); delayMillis(200); for (i = start; i < end; i++) { setLED(perimeter[i], 0); setLED(56 - perimeter[i], 0); } if (++patternStage > 2) { toggleLED(28); patternStage = 0; } } void ptn21(void) { // like ptn19, but toggle uint8_t i, start, end; switch (patternStage) { case 0: start = 0; end = 18; break; case 1: start = 18; end = 27; break; default: start = 27; end = 28; break; } for (i = start; i < end; i++) { toggleLED(perimeter[i]); toggleLED(56 - perimeter[i]); } delayMillis(200); delayMillis(200); if (++patternStage > 2) { toggleLED(28); patternStage = 0; } } void ptn22(void) { // like ptn20, but toggle uint8_t i, start, end; switch (patternStage) { case 0: start = 27; end = 28; break; case 1: start = 18; end = 27; break; default: start = 0; end = 18; break; } for (i = start; i < end; i++) { toggleLED(perimeter[i]); toggleLED(56 - perimeter[i]); } delayMillis(200); delayMillis(200); if (++patternStage > 2) { toggleLED(28); patternStage = 0; } } void ptn23(void) { // cycle all LEDs diagonally setLED(diagonals[patternStage], 1); setLED(56 - diagonals[patternStage], 1); delayMillis(70); setLED(diagonals[patternStage], 0); setLED(56 - diagonals[patternStage], 0); if (++patternStage > 27) { toggleLED(28); patternStage = 0; } } void ptn24(void) { // line-at-a-time diagonal fill uint8_t i, start, end; start = (patternStage * (patternStage + 1)) >> 1; end = start + patternStage + 1; for (i = start; i < end; i++) { setLED(diagonals[i], 1); setLED(56 - diagonals[i], 1); } delayMillis(255); for (i = start; i < end; i++) { setLED(diagonals[i], 0); setLED(56 - diagonals[i], 0); } if (++patternStage > 6) { toggleLED(28); patternStage = 0; } } void ptn25(void) { // like ptn23, but toggle toggleLED(diagonals[patternStage]); toggleLED(56 - diagonals[patternStage]); delayMillis(70); if (++patternStage > 27) { toggleLED(28); patternStage = 0; } } void ptn26(void) { // like ptn24, but toggle uint8_t i, start, end; start = (patternStage * (patternStage + 1)) >> 1; end = start + patternStage + 1; for (i = start; i < end; i++) { toggleLED(diagonals[i]); toggleLED(56 - diagonals[i]); } delayMillis(255); if (++patternStage > 6) { toggleLED(28); patternStage = 0; } } const functionPointer patterns[] = {ptn0,ptn1,ptn2,ptn3,ptn4,ptn5,ptn6,ptn7,ptn8,ptn9,ptn10,ptn11,ptn12,ptn13,ptn14,ptn15,ptn16,ptn17,ptn18,ptn19,ptn20,ptn21,ptn22,ptn23,ptn24,ptn25,ptn26}; // reverse-engineered the trickling sand hourglass animation in the kit as supplied // seems to light (PCB numbering) one of the three LEDs: 30, 29, 28, none, 30, 29, ... the none stage lasts about half as long as each single LED lit stage // so mimicking that as 7 stages: top lit for 2, then middle for 2, bottom for 2, then one stage with none // about every 11th tick one grain drops to destinations[] in order. The grain drops from (PCB numbering) source = 56 - destination. // there are 27 grains to drop with a delay before the first and after the last - so a total of 28 * 11 = 308 steps void hourGlassPattern(uint8_t millis) { uint8_t i; if (!patternStage) { // start (reset) setAllLEDs(1); // fill entire hourglass with sand for (i = 0; i < 29; i++) { // empty lower half setLED(i, 0); } } // animate the three centre (trickling) LEDs in 7 stages i = divMod(patternStage, 7); setLED(27, i == 4 || i == 5); setLED(28, i == 2 || i == 3); setLED(29, i < 2); // every 11th step, drop one grain i = divMod(patternStage, 11); if (patternStage && !i) { i = destinations[div - 1]; setLED(56 - i, 0); // remove next grain from top of hourglass setLED(i, 1); // and add grain to bottom } delayMillis(millis); delayMillis(millis); if (++patternStage > 307) { patternStage = 0; } } void main() __using 0 { uint8_t pattern = 0; AUXR &= 0xE3; // bit 4, T2R is low (Timer 2 not running); bit 3, T2_C/T low so timer counter 2 works as timer; bit 2 T2x12 is low, so clock source for timer 2 is FOSC/12 // when T2R == 0 (timer 2 not running), writes to T2H and T2L actually write to the hidden registers RL_T2H and RL_T2L (the 16-bit auto-reload value) T2L = 0x00; T2H = 0xFF; // count from 0xFF00 to 0x10000 (256 counts) before interrupt, so interrupt frequency = FOSC/12/256 = 11059200/12/256 = 3600 Hz AUXR |= 0x10; // run timer 2 IE2 |= 0x04; // set bit2 ET2 to enable interrupts from timer 2 EA = 1; // enable interrupts while(1) { if (pushButton) { pushButton = 0; if (++pattern > 34) { pattern = 0; } if (!pattern || pattern >= 8) { // reset to start of pattern when changing, but not when just speeding up the hourglass pattern setAllLEDs(0); patternStage = 0; } } if (pattern < 8) { hourGlassPattern(0xFF >> pattern); // pattern 0: slow increasing to pattern 7: ludicrous speed } else { patterns[pattern - 8](); } } }