// 12-hour clock code for (6-digit, 7-segment display with AT89C2051 microcontroller, three push buttons, buzzer, and four LED 'colons' // ceptimus January 2024 // compile using SDCC: sdcc clock12h.c #include /* the common anode, seven segment display is addressed using these bits of P1: --2-- | | 1 3 | | --0-- | | 5 4 | | --6-- */ // digits are addressed by P3_x: 4, 0, 5, 2, 1, 3 left to right // the four 'colon' LEDs are lit by pulling P1.7 low // the buzzer is sounded by pulling P3.7 low // the three buttons pull pins to GND when pressed, and are shared with the digit select lines: // ... left to right: S3:P3.5, S2:P3.4, S1:P3.2 __code unsigned char sevenSeg[22] = { // [10] and [21] are blank digit to replace leading '0' 0x01, 0x67, 0x12, 0x22, 0x64, 0x28, 0x08, 0x63, 0x00, 0x20, 0x7F, // with colon lit 0x81, 0xE7, 0x92, 0xA2, 0xE4, 0xA8, 0x88, 0xE3, 0x80, 0xA0, 0xFF // with colon unlit }; __code unsigned char digitSelectMask[6] = {0xEF, 0xFE, 0xDF, 0xFB, 0xFD, 0xF7}; __code unsigned char digitLimit[6] = {1, 9, 5, 9, 5, 9}; volatile unsigned char digit[6] = {1, 2, 0, 0, 0, 0}; // start at 12:00 hours volatile unsigned char buttons = 0xFF; volatile unsigned char isrCountHigh = 0; unsigned char brightness = 0; // 0 is full brightness, 1 = 0.25, 2 = 0.50, 3 = 0.75 unsigned char colonMode = 2; // 0 = on, 1 = off, 2 = pulsing void timer1_isr(void) __interrupt (3) __using (1) { // every 250us static unsigned char isrCountLow = 0; static unsigned char colonOffset = 0; // zero for lit, eleven for unlit static unsigned char displayDigit = 0; // 0 = left (10 hours); 5 = right (1 seconds) unsigned char i; if ((isrCountLow & 0x03) == brightness) { P1 = 0xFF; // all segments, and colon, off P3 |= 0x3F; // no digit selected: brightness gives four levels of PWM LED brightness } if (++isrCountLow & 0xF0) { // counts interrupts 0-15 and wraps isrCountLow = 0; if (++isrCountHigh == 250) { // 250us * 16 * 250 = 1s colonOffset = colonMode ? 0 : 11; // colon lit for first half of each second in colon modes 1 and 2 isrCountHigh = 0; // clock advances one second for (i = 5; !(i & 0x80); i--) { if (++digit[i] <= digitLimit[i]) { break; } else { digit[i] = 0; } } if (i & 0x80) { // just incremented hour from _9 to 00 digit[0] = 1; // correct 00 to 10 } else if (i == 1 && digit[0] == 1 && digit[1] == 3) { // rollover from 12:59 to _1:00 digit[0] = 10; digit[1] = 1; } } else if (isrCountHigh == 125) { // work 'colon' mask depending on colon mode if (colonMode == 2) { colonOffset = 11; // switch colon off for last half of each second, in mode 2 } } } else if ((isrCountLow & 0x07) == 1) { // every 8 * 250us = 2000us // multiplex 6-digit display. one digit per 2000us, so all six at 83.33Hz if (++displayDigit == 6) { displayDigit = 0; buttons = P3; // sample every 12 ms } P3 &= digitSelectMask[displayDigit]; // select digit P1 = sevenSeg[digit[displayDigit] + colonOffset]; // and display it } } void main(void) { unsigned char ticks; // counts at 250 Hz, when waiting right button up unsigned char prevISRcount; // detects passing of one tick when waiting right button up 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) { if (!(buttons & 0x20)) { // left button if (++digit[1] > 9) { // advance hour digit[1] = 0; ++digit[0]; if (digit[0] > 10) { // when changing hour from _9, will instantaneously be X0 digit[0] = 1; // change to 10 } } else if (digit[0] == 1 && digit[1] >= 3) { // wrap from 12:XX to _1:XX digit[0] = 10; digit[1] = 1; } while (!(buttons & 0x20)) continue; // wait button up } if (!(buttons & 0x10)) { // middle button if (++digit[3] > 9) { // advance minute digit[3] = 0; if (++digit[2] > 5) { digit[2] = 0; } } while (!(buttons & 0x10)) { // wait button up digit[4] = 0; digit[5] = 0; // hold seconds at zero while waiting isrCountHigh = 0; // keep internal 'partial seconds' variable at zero (there remains a ~4ms jitter on restart due to isrCountLow) } } if (!(buttons & 0x04)) { // right button ++brightness; brightness &= 0x03; prevISRcount = isrCountHigh; ticks = 0; while (!(buttons & 0x04)) { // wait button up if (isrCountHigh != prevISRcount) { // button has been held for 1/250 sec prevISRcount = isrCountHigh; if (!++ticks) { // button has been held for 256/250 seconds P3_7 = 0; // activate buzzer colonMode = colonMode ? --colonMode : 2; // change colon mode brightness = 0; // set full brightness when long-pressing to change colon mode break; // only one colon mode change per long button press (no auto-repeat) } } } while (!(buttons & 0x04)) continue; // wait button up P3_7 = 1; // buzzer off } } } /* following 'modeline' configures my text editor, Xed, to use 2-space 'tabs' it must be either the first line of the file or in the last three lines # vi:si:et:sw=2:sts=2:ts=2 */