// 12-hour clock code for SH-E 879 clock kit board (6-digit, 7-segment display with AT89C2051 microcontroller and single push button) // ceptimus May 2022 // short-press button to increment currently selected (flashing) field or exit to normal (clock running) mode when seconds '00' is flashing // long-press button change to next field, or enter clock-setting mode from running mode // fields are selected in the order: units minutes, tens minutes, hours, seconds ('00') // compile using SDCC: sdcc -mmcs51 --std-c99 clock12h.c #include /* contrary to the circuit diagram included with the SH-E 879 kit, the seven segment display is addressed using these bits of P1: --0-- | | 2 1 | | --3-- | | 5 6 | | --4-- */ __code unsigned char sevenSeg[11] = {0x88, 0xBD, 0xC4, 0xA4, 0xB1, 0xA2, 0x82, 0xBC, 0x80, 0xA0, 0xFF}; // sevenSeg[10] is leading blank for 1:00 - 9:59 // digits are addressed in slightly quirky order by P3_x: 0, 1, 2, 4, 3, 5 left to right __code unsigned char digitOrder[4] = {5,3,4,2}; __code unsigned char digitLimit[4] = {9,5,9,5}; volatile unsigned char running = 0; // flag for clock running volatile unsigned char runningDigits[6] = {1, 2, 0, 0, 0, 0}; // start setting at 12 o'clock volatile unsigned char hour = 12; volatile unsigned char selected = 0; void timer1_isr(void) __interrupt (3) __using (1) { // every 250us static unsigned char isrCountLow = 16; static unsigned char isrCountHigh = 250; // 250us * 16 * 250 = 1s static unsigned char displayDigit = 0; static unsigned char pressedCount = 0; // debounce and long-press detect for pushbutton unsigned char i; if (--isrCountLow == 0) { // every 16 * 250us = 4000us isrCountLow = 16; if (--isrCountHigh == 0) { isrCountHigh = 250; } } else if ((isrCountLow & 0x07) == 4) { // every 8 * 250us = 2000us // multiplex 6-digit display. one digit per 2000us, so all six at 83.33Hz P1 = 0xFF; // all segments off P3 |= 0x3F; // no digit selected if (++displayDigit == 6) { displayDigit = 0; } P3 &= ~(1 << displayDigit); // select digit i = sevenSeg[runningDigits[displayDigit]]; // flash selected digit(s) when in setting mode if (!running && isrCountHigh < 125) { if (selected == 3 && (displayDigit == 3 || displayDigit == 5)) { // seconds i = 0xFF; } else if (selected == 0 && displayDigit == 4) { // units minutes i = 0xFF; } else if (selected == 1 && displayDigit == 2) { // tens minutes i = 0xFF; } else if (selected == 2 && displayDigit < 2) { // hours i = 0xFF; } } P1 = i; } else if (running && isrCountHigh == 1 && isrCountLow == 1) { // increment clock once per second for (i = 0; i < 4; i++) { if (++runningDigits[digitOrder[i]] <= digitLimit[i]) { break; } else { runningDigits[digitOrder[i]] = 0; if (i == 3) { // next hour if (++hour > 12) { hour = 1; } if (hour > 9) { runningDigits[0] = 1; runningDigits[1] = hour - 10; } else { runningDigits[0] = 10; // blank runningDigits[1] = hour; } } } } } else if (isrCountLow == 8) { // debounce button every 4ms (but on a different interrupt to the display multiplex) if (P3_7) { // button released if (!running && pressedCount && pressedCount < 250) { // it was a short press if (selected == 3) { // seconds was selected isrCountLow = 16; isrCountHigh = 250; running = 1; // back to running mode } else if (selected == 0) { // units minutes if (++runningDigits[4] > 9) { runningDigits[4] = 0; } } else if (selected == 1) { // tens minutes if (++runningDigits[2] > 5) { runningDigits[2] = 0; } } else if (selected == 2) { // hours if (++hour > 12) { hour = 1; } if (hour > 9) { runningDigits[0] = 1; runningDigits[1] = hour - 10; } else { runningDigits[0] = 10; // blank runningDigits[1] = hour; } } } pressedCount = 0; } else { // button pressed if (++pressedCount >= 250) { if (pressedCount == 250) { // long press if (running) { selected = 0; runningDigits[3] = runningDigits[5] = 0; running = 0; } else if (++selected > 3) { selected = 0; } } pressedCount = 251; } } } } void main(void) { TMOD = 0x20; // timer 1 in 8-bit auto-reload mode TH1 = 0x06; // count from 0x06 to 0xFF then wrap. with 12MHz crystal, will interrupt every 250us IE = 0x88; // enable timer 1 interrupts TCON |= 0x40; // run timer 1 while (1) continue; }