// hourglass kit with 57 LEDs driven by STC15W201 (this is a pruned version of hourglass.c: it easily fits in an STC15W201) // it just mimics the original kit-supplied hourglass pattern // ...but with better multiplexing, and switch debouncing, and eight available animation speeds rather than three // if you have the STC15W204, which has 4K flash memory instead of the -201's 1K, see hourglass.c - it has many more patterns // ceptimus June 2022 // compile with sdcc hourglass-201a.c // flash to chip using stc8prog -p /dev/ttyUSB0 -r 50 -e -f hourglass-201a.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 __code 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 __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 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; } void setLED(uint8_t n, uint8_t on) { // n:0 to 56; on:true or false uint8_t mask; uint8_t row; row = divMod(n, 10); mask = 0x01 << div; row = row > 4 ? ((row - 5) << 1) | 1 : row << 1; if (on) { columns[row] |= mask; } else { columns[row] &= ~mask; } } void main() __using 0 { uint8_t i; uint8_t pattern = 0; unsigned int j; unsigned int patternStage = 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 patternStage = 0; while(1) { if (pushButton) { pushButton = 0; if (++pattern > 7) { pattern = 0; } } // 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 if (!patternStage) { // start (reset) for (i = 0; i < 27; i++) { setLED(i, 0); // empty lower half setLED(i + 30, 1); // fill top half } } // 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 } for (i = 0; i < 0xFF >> pattern; i++) { // delay approx ms (* 2) for (j = 0; j < 1608; j++) { __asm nop __endasm; } } if (++patternStage > 307) { patternStage = 0; } } }