/* * ds1302_demo.c * (c) bryan levin (linux-works), 2009 * v1.00 */ //#define DEBUG_IR //#define DEBUG_IR1 #define SUPERCAP_INSTALLED 1 // yes, we use this in our circuit #include //#include #include #include #include #include #include #include #include #include // standard i2c arduino lib #include "LCDi2c4bit.h" // this is our new library, note quite in 'system space' yet #define SplashScrnTime 2 // Splash Screen display time, seconds char Title[] = { "LinuxWorks Labs"}; char Version[] = { "DS1302 RTC 1.00"}; /* * IR key scans for sony remotes */ /* keyscans for all known keys on MY remote play mode: continue 157 shuffle 181 program 159 number keypad: 1 128 2 129 3 130 4 131 5 132 6 133 7 134 8 135 9 136 >10 167 10 160 clear 143 time 168 check 141 repeat 172 fader 223 play 178 pause 185 stop 184 ams_left 176 ams_right 177 vol_up 146 skip_left 179 skip_right 180 vol_down 147 ***************************** */ #define KEYSCAN_continue 157 #define KEYSCAN_shuffle 181 #define KEYSCAN_program 159 #define KEYSCAN_1 128 #define KEYSCAN_2 129 #define KEYSCAN_3 130 #define KEYSCAN_4 131 #define KEYSCAN_5 132 #define KEYSCAN_6 133 #define KEYSCAN_7 134 #define KEYSCAN_8 135 #define KEYSCAN_9 136 #define KEYSCAN_gt10 167 #define KEYSCAN_10 160 #define KEYSCAN_clear 143 #define KEYSCAN_time 168 #define KEYSCAN_check 141 #define KEYSCAN_repeat 172 #define KEYSCAN_fader 223 #define KEYSCAN_play 178 #define KEYSCAN_pause 185 #define KEYSCAN_stop 184 #define KEYSCAN_ams_left 176 #define KEYSCAN_ams_right 177 #define KEYSCAN_vol_up 146 #define KEYSCAN_skip_left 179 #define KEYSCAN_skip_right 180 #define KEYSCAN_vol_down 147 // IR decoding #define start_bit 2000 // Start bit threshold (Microseconds) #define bin_1 1000 // Binary 1 threshold (Microseconds) #define bin_0 400 // Binary 0 threshold (Microseconds) #define PULSE_DUR 1000 #define PULSE_2200 2200 #define SONY_IR_PROTO_PULSE_TRAIN_LEN 11 // more arduino pin definitions #define IR_INPUT_PIN 8 // (yellow) vishay TSOP 38khz module // DS1302 connections #define CE1302_PIN 4 // CE (1302 pin-5) #define DAT1302_PIN 3 // i/o (1302 pin-6) #define CLK1302_PIN 2 // clock (1302 pin-7) /* * misc constants */ #define IR_READ_COUNT 20 /* * eeprom locations (slot numbers in eeprom address space) */ #define EEPROM_MAGIC 0 #define EEPROM_POWER 1 // 1 = on (and running) /* * globals (that may init from EEPROM at power-up) */ // boolean flags boolean power = 1; boolean clock_set_mode = 0; // non-boolean (enums, etc) byte timer_mode = 1; // 0=stopped, 1=running, 2=paused byte clock_hours = 0; byte clock_minutes = 0; byte clock_seconds = 0; // timestamp values (ULONG) unsigned long sys_uptime = 0; unsigned long stopwatch_timer_begin = 0; // millis() values unsigned long last_clock_redraw = 0; unsigned long lastButtonPressTime = 0; // most recent timestmap of a user action // lcd line buffers (for 4x20 display) char int_s[41]; // length of 1 lcd line (and 1 for zero byte) char line1[12]; char line2[12]; char line3[12]; char line4[12]; /* * bargraph class */ #define OUTLINE_TOP 159 #define NO_OUTLINE_TOP 128 const byte graph_horiz_line_pre[] = { NO_OUTLINE_TOP,128,159,159,159,159,128,NO_OUTLINE_TOP}; const byte graph_horiz_line_post[] = { NO_OUTLINE_TOP,128,128,149,149,128,128,NO_OUTLINE_TOP}; const byte graph_b1[] = { NO_OUTLINE_TOP,128,144,149,149,144,128,NO_OUTLINE_TOP}; const byte graph_b2[] = { NO_OUTLINE_TOP,128,152,157,157,152,128,NO_OUTLINE_TOP}; const byte graph_b3[] = { NO_OUTLINE_TOP,128,156,157,157,156,128,NO_OUTLINE_TOP}; const byte graph_b4[] = { NO_OUTLINE_TOP,128,158,159,159,158,128,NO_OUTLINE_TOP}; const byte graph_b5[] = { NO_OUTLINE_TOP,128,159,159,159,159,128,NO_OUTLINE_TOP}; // these 2 can share the same 'cell' since only 1 is on at a time const byte graph_play[] = { 136,140,142,143,142,140,136,128}; const byte graph_pause[] = { 155,155,155,155,155,155,155,128}; /* * lcd physical constants */ #define HORIZ_PIXELS_PER_CHAR 5 // not user configurable, leave this alone #define LCD_PHYS_ROWS 20 #define LCD_PHYS_LINES 4 // char cell 'memory map' locations for start of line1 and line2 on the lcd #define LCD_CURS_POS_L1_HOME 0x80 #define LCD_CURS_POS_L2_HOME 0xC0 #define LCD_CURS_POS_L3_HOME 0x94 #define LCD_CURS_POS_L4_HOME 0xD4 #define HISTOGRAM_CLOCK_DISPLAY_LINE 0 // top (first) line /* * start of RTC (realtime clock) code * originally written by tom for his all-in-1 camera intervalometer */ // DS1302 Opcodes #define WriteCtrl B10001110 #define ReadSecs B10000001 #define WriteSecs B10000000 #define ReadMins B10000011 #define WriteMins B10000010 #define ReadHrs B10000101 #define WriteHrs B10000100 #define WriteTrickle B10010000 #define ReadDay B10001011 #define WriteDay B10001010 #define ReadDate B10000111 #define WriteDate B10000110 #define ReadMonth B10001001 #define WriteMonth B10001000 #define ReadYear B10001101 #define WriteYear B10001100 #ifdef SUPERCAP_INSTALLED // Sets DS1302 trickle charger for supercap backup // 1 diode, 2k series resistance #define TrickleSet B10100101 #else // Sets DS1302 for no trickle charger output. Use this for battery backup. #define TrickleSet B11111111 #endif // supercap byte Secs; // DS1302's BCD values byte Mins; byte Hrs; // Hrs has 12/24 and AM/PM bits stripped #ifdef USE_12_HR_TIME_FORMAT byte PMFlag; #endif byte AlmScpd[3]; // Alarm Time scratchpad: bcd Hrs, Mins, PMflag byte BeepTimeout[3]; // Beep timeout time byte StartTimeRegs[3]; byte StopTimeRegs[3]; //byte DateRegs[4]; // Day (1-7, Sun = 1), Month, Date, Year (BCD) int ShotsTaken; byte IdleCounter; byte DayMap; const char DOWStr[8][4] = { " ", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; const char MonthStr[13][4] = { " ", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; // Max days for each month const byte MaxDays[13] = { 99,31,29,31,30,31,30,31,31,30,31,30,31}; /* * create an instance of the lcd i2c 4bit class. * * this does a lot of stuff! you get back a 'filled in' variable called 'lcd' * and the screen is initialized and ready for you to write to. */ #define backlightPin 9 // a pwm-capable pin, so I picked #9 LCDI2C4Bit lcd = LCDI2C4Bit(0xa7, 2, 16, backlightPin); //0xa7 is the hardware addr of the i2c chip void redraw_clock() { if (timer_mode == 0) { // 0=stopped, 1=running, 2=paused return; } // TOYclock (real time hardware TimeOfYear clock) GetTime(); lcd.cursorTo(0, 0); // first line, 12th char over from left ShowTime(); } void hello_banner() { delay(100); lcd.clear(); lcd.cursorTo(0, 0); lcd.print(Title); lcd.cursorTo(1, 0); lcd.print(Version); sys_uptime = millis(); // we just started running. keep track of our 'birth' time delay(3000); redraw_clock(); } void goodbye_banner() { lcd.clear(); lcd.cursorTo(0, 0); lcd.print("DS1302 demo says"); lcd.cursorTo(0, 2); lcd.print(" ...goodbye"); sys_uptime = 0; delay(3000); } void setup() { byte b; delay(200); //let things settle at bootup time Serial.begin(9600); /* * we have to join the i2c bus. just call this routine at the top, as well. */ Wire.begin(); if (EEPROM.read(EEPROM_MAGIC) != 05) { power = 1; clock_set_mode = 0; // init all of EEPROM area EEwrite(EEPROM_POWER, power); EEwrite(EEPROM_MAGIC, 05); // this signals that we're whole again ;) } /* * read from EEPROM */ power = EEPROM.read(EEPROM_POWER); // last-state of power on/off //analogReference(INTERNAL); // 1v level on analog inputs analogReference(DEFAULT); /* * all the i/o pins that we plan to use */ pinMode(IR_INPUT_PIN, INPUT); // TSOP IR receiver module /* * setup RTC (realtime hardware chip-based clock) */ pinMode(CE1302_PIN, OUTPUT); pinMode(DAT1302_PIN, OUTPUT); pinMode(CLK1302_PIN, OUTPUT); Init1302(); // hardware chip-based RTC #ifdef USE_12_HR_TIME_FORMAT PMFlag = 0; #endif #if 0 // debug! Hrs = bin2bcd(07); Mins = bin2bcd(55); Secs = bin2bcd(00); SetTime(); #endif lcd.init(); // lets start with a clear screen (never assume it comes to you, clean) lcd.clear(); // turn on backlight, full blast lcd.backLight(255); //LCD_cgram_load_normal_bargraph(); // always display this at startup hello_banner(); lcd.clear(); // ok banner, now go away // lastButtonPressTime = get_abs_time(); // user did hit a key; save that timestamp /* * if power is supposed to be OFF, do some safety things */ if (power == 0) { lcd.clear(); // we don't *really* turn ourselves off. users just think so ;) } Serial.println("DS1302 demo RTC running."); } void loop() { byte read_count = 0; //long this_timestamp = 0; while (1) { /* * scan keyboard */ for (read_count=0; read_count 0) { bin_Hrs--; } else { bin_Hrs = 0; // wrap around } Hrs = bin2bcd(bin_Hrs); Secs = 0; // bin2bcd(0); // reset seconds when we change our time SetTime(); delay(100); // debounce break; // increae HOURS case KEYSCAN_skip_right: //GetTime(); bin_Hrs = bcd2bin(Hrs); if (bin_Hrs < 23) { bin_Hrs++; } else { bin_Hrs = 0; // wrap around } Hrs = bin2bcd(bin_Hrs); Secs = 0; // bin2bcd(0); // reset seconds when we change our time SetTime(); delay(100); // debounce break; // increase MINUTES case KEYSCAN_vol_up: //GetTime(); bin_Mins = bcd2bin(Mins); if (bin_Mins < 59) { bin_Mins++; } else { bin_Mins = 0; // wrap around } Mins = bin2bcd(bin_Mins); Secs = 0; // bin2bcd(0); // reset seconds when we change our time SetTime(); delay(100); // debounce break; // decrease MINUTES case KEYSCAN_vol_down: //GetTime(); bin_Mins = bcd2bin(Mins); if (bin_Mins > 0) { bin_Mins--; } else { bin_Mins = 59; // wrap around } Mins = bin2bcd(bin_Mins); Secs = 0; // bin2bcd(0); // reset seconds when we change our time SetTime(); delay(100); // debounce break; } // switch } // while } void handle_IR_keys_normal_mode() { byte ms,ls; int key; float t; // temperature setting /* * we got a valid IR start pulse! fetch the keycode, now. */ key = get_IR_key(); switch (key) { // no key pressed - quick return case 0: case -1: return; /* * stopwatch/clock timer */ // this toggles normal display mode and 'set TIME/DATE' mode case KEYSCAN_check: if (power == 0) break; // ignore keys if power not on lastButtonPressTime = get_abs_time(); // user did hit a key; save that timestamp if (clock_set_mode == 0) { clock_set_mode = 1; // toggle it screen_set_time_date(); } else { clock_set_mode = 0; // toggle it lcd.clear(); } delay(500); // debounce break; case KEYSCAN_play: if (power == 0) break; // ignore keys if power not on lastButtonPressTime = get_abs_time(); // user did hit a key; save that timestamp timer_mode = 1; // 0=stopped, 1=running, 2=paused //clock_hours = 0; //clock_minutes = 0; //clock_seconds = 0; stopwatch_timer_begin = millis(); //last_clock_redraw = millis(); // our new reference point (self-trigger at 1-sec intervals) redraw_clock(); delay(500); // debounce break; case KEYSCAN_pause: if (power == 0) break; // ignore keys if power not on lastButtonPressTime = get_abs_time(); // user did hit a key; save that timestamp // toggle from pause to unpause (play), as needed if (timer_mode == 1) { // we were in PLAY mode, until just now timer_mode = 2; // 0=stopped, 1=running, 2=paused } else if (timer_mode == 2) { // we were in PAUSE, so UNpause us, now timer_mode = 1; stopwatch_timer_begin = millis(); //last_clock_redraw = millis(); // our new reference point (self-trigger at 1-sec intervals) redraw_clock(); } delay(500); // debounce break; case KEYSCAN_stop: if (power == 0) break; // ignore keys if power not on lastButtonPressTime = get_abs_time(); // user did hit a key; save that timestamp timer_mode = 0; // 0=stopped, 1=running, 2=paused stopwatch_timer_begin = 0; //clock_hours = 0; //clock_minutes = 0; //clock_seconds = 0; //last_clock_redraw = 0; lcd.cursorTo(12, 0); // first line, 12th char over from left lcd.print(" "); // clear out the clock delay(500); // debounce break; /* * power button */ case KEYSCAN_continue: /*KEYSCAN_POWER*/ lastButtonPressTime = get_abs_time(); // user did hit a key; save that timestamp if (power == 1) { power = 0; EEwrite(EEPROM_POWER, power); goodbye_banner(); lcd.clear(); // simulate power being off delay(100); } else { power = 1; EEwrite(EEPROM_POWER, power); hello_banner(); lcd.clear(); redraw_clock(); // no need to debounce, the banner has enough delay } break; } // switch } int get_IR_key() { int data[SONY_IR_PROTO_PULSE_TRAIN_LEN+1]; byte i; int result = 0; int seed = 1; if (pulseIn(IR_INPUT_PIN, LOW, PULSE_DUR) < PULSE_2200) { return -1; } for (i=0; i bin_1) { // is it a 1? data[i] = 1; } else { if (data[i] > bin_0) { // is it a 0? data[i] = 0; } else { data[i] = 2; // Flag the data as invalid; I don't know what it is! } } } for (i=0; i 1) { return -1; // Return -1 on invalid data } } for (i=0; i> 4) + '0'; *ls = (val & 0x0f) + '0'; } void bin2ascii(const byte val, byte* ms, byte* ls) { *ms = val / 10 + '0'; *ls = val % 10 + '0'; } byte bcd2bin(const byte val) { return (((val >> 4) * 10) + (val & 0x0f)); } byte bin2bcd(const byte val) { return ((val / 10 * 16) + (val % 10)); } void LCD_send_string(const char *str, const byte addr) { // Send string at addr, if addr <> 0, or cursor position if addr == 0 if (addr != 0) { lcd.command(addr); } byte i = 0; while ( (str[i] != 0) && (i < LCD_PHYS_ROWS-1) ) { lcd.write(str[i++]); } } void LCD_cgram_load_normal_bargraph() { byte i; /* * load special graphic chars into 'cgram' */ lcd.command(0x40); // start off writing to CG RAM char 0 /* * 2 graphic bitmaps for PLAY and PAUSE icons */ // slot-0 for (i=0; i<8; i++) { lcd.write(graph_play[i]); } /* * full box (graphic) bitmap */ // slot-2 for (i=0; i<8; i++) { lcd.write(graph_horiz_line_pre[i]); } // empty box (...) graphic // slot-3 for (i=0; i<8; i++) { lcd.write(graph_horiz_line_post[i]); } /* * 5 data point (graphic) bitmaps */ // slot-4 for (i=0; i<8; i++) lcd.write(graph_b1[i]); // slot-5 for (i=0; i<8; i++) lcd.write(graph_b2[i]); // slot-6 for (i=0; i<8; i++) lcd.write(graph_b3[i]); // slot-7 for (i=0; i<8; i++) lcd.write(graph_b4[i]); // slot-8 for (i=0; i<8; i++) lcd.write(graph_b5[i]); } // value is a float from 0..1 (0 to 100% of fullscale) void draw_graphic_bar(char *dest_buf, float value, int total_bargraph_size) { byte i; int scaled_bit_pos; int scaled_char_pos; byte byte_graph_code; byte dest_buf_idx=0; float scaled_temp; // which char position gets the graph point? scaled_temp = value * (float)total_bargraph_size; scaled_char_pos = (int) scaled_temp; // truncate the fraction and keep the int value scaled_bit_pos = (int) (scaled_temp * (float)HORIZ_PIXELS_PER_CHAR); // (1) draw from 0 up to user 'lower limit' if (scaled_char_pos > 0) { for (i=0; ; i++) { if (i < scaled_char_pos) { dest_buf[dest_buf_idx++] = 1; // 'small dots' line style //lcd.write(1); // 'small dots' line style } else break; } } // (2) we're at the cell that needs the 'graphic' // which graphic (0..5) should be used? if (i < total_bargraph_size) { byte_graph_code = (scaled_bit_pos - (scaled_char_pos * HORIZ_PIXELS_PER_CHAR)); dest_buf[dest_buf_idx++] = byte_graph_code+3; // skipping over the first 2+1 magic chars //lcd.write(byte_graph_code+3); // skipping over the first 2+1 magic chars } // (3) draw postamble, if any if (i < total_bargraph_size) { for (i=scaled_char_pos+1; i < total_bargraph_size; i++) { dest_buf[dest_buf_idx++] = 2; // continue drawing the horiz_line, to the end, in 'small dots' //lcd.write(2); // continue drawing the horiz_line, to the end, in 'small dots' } } dest_buf[total_bargraph_size+1] = '\0'; } void Init1302() { // This routine is, most likely, called with the DS1302 running on the // supercap or battery backup, so it is important to preserve its // time values. send1302cmd(WriteCtrl, 0); // Clear write protect Secs = get1302data(ReadSecs); // Clear CH bit, preserving secs reg Secs &= B01111111; send1302cmd(WriteSecs, Secs); #ifdef USE_12_HR_TIME_FORMAT Hrs = get1302data(ReadHrs); // Set 12 hour format, // preserving hrs reg value (Hrs & B00100000) ? PMFlag = 1 : PMFlag = 0; Hrs |= B10000000; // bit 7 in 1302's hours reg // sets 12 hour format send1302cmd(WriteHrs, Hrs); Hrs &= B00011111; // Store Hrs locally // without flag bits #endif send1302cmd(WriteTrickle, TrickleSet); // begin charging the supercap } #ifdef NOTYET void Get4DigTime(byte* reg, byte addr, byte count) { int i; #ifdef NOTYET for (i=0; i 0) ? PMFlag = 1 : PMFlag = 0; Hrs &= B00011111; #endif } void ShowTime() { byte ms, ls; bcd2ascii(Hrs, &ms, &ls); // suppress leading zeroes? if (ms == '0') { lcd.write(' '); } else { lcd.write(ms); } lcd.write(ls); lcd.write(':'); bcd2ascii(Mins, &ms, &ls); lcd.write(ms); lcd.write(ls); lcd.write(':'); bcd2ascii(Secs, &ms, &ls); lcd.write(ms); lcd.write(ls); #ifdef USE_12_HR_TIME_FORMAT //lcd.write(' '); (PMFlag) ? lcd.write('P') : lcd.write('A'); lcd.write('M'); lcd.command(addr); #endif } // Write time to DS1302 void SetTime() { #ifdef USE_12_HR_TIME_FORMAT (PMFlag) ? temp = Hrs | B10100000 : temp = Hrs | B10000000; send1302cmd(WriteHrs, temp); #endif send1302cmd(WriteHrs, Hrs); send1302cmd(WriteMins, Mins); send1302cmd(WriteSecs, Secs); } #ifdef NOTYET void GetDate() { DateRegs[0] = get1302data(ReadDay); // 1-7, Sun = 1 DateRegs[1] = get1302data(ReadMonth); DateRegs[2] = get1302data(ReadDate); DateRegs[3] = get1302data(ReadYear); } #endif #ifdef NOTYET // Write date to DS1302 void SetDate() { byte temp; send1302cmd(WriteDay, DateRegs[0]); send1302cmd(WriteMonth, DateRegs[1]); send1302cmd(WriteDate, DateRegs[2]); send1302cmd(WriteYear, DateRegs[3]); temp = get1302data(ReadSecs); // lock in the changes send1302cmd(WriteSecs, temp); } #endif #ifdef NOTYET void ShowDate(byte addr) { byte ms,ls; LCD_send_string(DOWStr[DateRegs[0]], addr); lcd.write(' '); LCD_send_string(MonthStr[bcd2bin(DateRegs[1])], 0); lcd.write(' '); bcd2ascii(DateRegs[2],&ms,&ls); lcd.write(ms); lcd.write(ls); LCD_send_string(", ", 0); bcd2ascii(DateRegs[3], &ms, &ls); lcd.write(ms); lcd.write(ls); } #endif // ==================== 8-byte Time Routines =============================// #ifdef NOTYET void LoadTimeRegs(byte eepromaddr, byte* targaddr) { // Loads 8 bytes from EEPROM to RAM int i; for (i=0; i < 8; i++) { *(targaddr + i) = EEPROM.read(eepromaddr + i); } } #endif #ifdef NOTYET void SaveTimeRegs(byte eepromaddr, byte* sourceaddr) { // Stores 8 RAM bytes from RAM to EEPROM for (int i=0; i < 8; i++) { EEwrite(eepromaddr + i, *(sourceaddr + i)); } } #endif #ifdef NOTYET void Inc4DigBCDTime(byte *reg, byte incmins) { // Adds incmins to the pointed bcd register set (hrs, mins, PMflag) and // places the result back into the pointed register set byte hh,mm; byte flag; hh = bcd2bin(*reg); mm = bcd2bin(*(reg+1)); mm += incmins ; if (mm > 59) { mm = 0; hh++ ; if (hh == 12) *(reg + 2) = 1 - *(reg + 2); // Toggle AM/PM flag else if (hh > 12) hh = 1; } *reg = bin2bcd(hh); *(reg + 1) = bin2bcd(mm); } #endif // =================== DS1302 Driver Routines ==============================// void send1302cmd(byte cmd1, byte cmd2) { digitalWrite(CE1302_PIN, 1); // Set CE1302 high delayMicroseconds(2); shiftOut(DAT1302_PIN, CLK1302_PIN, LSBFIRST, cmd1); delayMicroseconds(2); // This delay might not be needed shiftOut(DAT1302_PIN, CLK1302_PIN, LSBFIRST, cmd2); digitalWrite(CE1302_PIN, 0); // Set CE1302 low } byte get1302data(byte cmd) { byte dat; digitalWrite(CE1302_PIN, 1); // CE1302 high shiftOut(DAT1302_PIN, CLK1302_PIN, LSBFIRST, cmd); dat = shiftin(); digitalWrite(CE1302_PIN, 0); // CE1302 low return (dat); } // Shifts in a byte from the DS1302. // This routine is called immediately after a shiftout. The first bit is // present on DAT1302 at call, so only 7 additional clock pulses are // required. // Restores DAT1302 to OUTPUT prior to return. byte shiftin() { byte dat = 0; int i = 0; pinMode(DAT1302_PIN, INPUT); // Set DAT1302 as input for (i=0; i < 7; i++) { if (digitalRead(DAT1302_PIN) == 1) { dat |= B10000000; // we found a 1-bit, so add it to our collection ;) } dat >>= 1; // shift over so that we can logical-OR the next 1-bit (if any) digitalWrite(CLK1302_PIN, 1); // Strobe in next data bit using CLK1302 delay(1); digitalWrite(CLK1302_PIN, 0); delay(1); } pinMode(DAT1302_PIN, OUTPUT); // Restore DAT1302 as output return (dat); }