' {$STAMP BS2} ' {$PBASIC 2.5} ' See http://www.numericana.com/answer/bs2.htm for explanations. ' (c) 2014 by Gerard P. Michon. All rights reserved. ' ' Input/Output pin and dedicated bit variables ================================== ' synchro PIN 0 ' 1 Hz signal from GPS, for frequency calibration INPUT synchro ' An open-collector double-duty pin, tied to a 10k pull-up resistor : trigger PIN 1 ' "External trigger" for oscilloscope (open-collector) adjust PIN trigger ' Button to round RTC to the nearest 30-second count INPUT adjust ' The two lines of the I2C bus ("Inter IC" synchronous serial communications) scl PIN 2 ' Both SCL and SDA have pull-up resistors sda PIN 3 ' Typically 1.8k to 10k (2.15 k is used here) ' Beyond the BS2, the I2CIN & I2COUT primtives assume (SDA,SCL) = (0,1) or (8,9) ' pendulum VAR Bit ' LSBit in the real-time clock's count of seconds ' Pendulum is thus a 0.5 Hz signal with a 50% duty cycle (1 Hz updates) ' and a jitter of 6.3 ms (since the RTC is polled at about 158.514 Hz). dubious VAR Bit ' = 1 when no attempt was made to reset RTC ' after timekeeping was interrupted. ' Pins 5,6 and 7 are unused at this time. ' The LCD reserves pins 8 (backlight) and 9 (lcd) for its exclusive use. ' ' The 6 top pins (10 to 15) which can be shared between the LCD and other devices, ' include the 4-bit bidirectional parallel data bus "D" (pins 12,13,14,15). ' That bus has 30k pull-up resistors on it, courtesy of the HD44780 LCD unit. ' General-purpose variables ===================================================== ' Four independent groups (16 bits, 16 bits, 1 bit) and one buffer ' x VAR Word ' 16-bit quantity counter VAR x ' For general-purpose 16-bit local counting i VAR counter.BYTE0 ' The 8-bit version of that j VAR counter.BYTE1 ' A 4-bit counter independent from i y VAR Word ' 16-bit quantity frame VAR y ' For fast 9-bit "last read" on I2C bus char VAR frame.BYTE0 ' Argument of output routines, result of some temp VAR frame.BYTE1 ' Local use, argument for numeric outputs hidigit VAR temp.NIB1 lodigit VAR temp.NIB0 z VAR Byte m VAR z.NIB0 n VAR z.NIB1 flag VAR Bit ' Temporary local use buffersize CON 12 ' Maximum size of the buffer *** MUST BE EVEN bufferw VAR Word(buffersize>>1) ' RAM buffer organized into 16 bit words ' The buffer uses a reserved byte variable to indicate its actual LENGTH. ' In addition, the I2C routines use RAM registers to store a 16-bit access code ' (two bytes are needed for 10-bit devices) and 36 bits for a pointer to the ' internal space of the current device (4 bits indicate the address width ' measured in bytes, from 0 to 4, using the other 32 bits to hold that address). ' Finally, a STREAM bit indicates what mode this software uses to transfer ' information over the I2C bus: Either by transfering the entire content of the ' RAM buffer (to/from the aforementioned access code and internal pointer) or ' as a continuous stream of data transferred by opening and closing the bus. ' Initializations =============================================================== ' INPUT backlight ' Turn backlight off (until initialization is over) GOSUB LCD_init ' Initialize LCD, upload custom characters counter = Splash GOSUB LCD_showstring ' Clear screen, generate initial screenful HIGH backlight ' Leave backlight on, for normal operation ' A reset could have interrupted the transfer of a byte over the I2C bus, ' so we must finish or abort it here (to reset the slaves's finite-state automaton): LOW scl ' Silence START/STOP signals INPUT sda ' Monitor SDA until the slave releases it FOR char = 1 TO 9 ' (no more than 9 clock pulses can be needed to do so) IF sda THEN EXIT PULSOUT scl,7 ' 14 us pulses accomodate very slow devices NEXT INPUT scl LOW sda ' START signal will reset slave's finite-state automaton ' The I2C standards forbids us to issue a STOP right away, so we'll let the next ' transaction proceed immediately (possibly generating another START, as is allowed). GOSUB I2C_setRTC ' Access DS3231/DS3232 via I2C bus routines flag = 0 ' Meaning that time wasn't set/adjusted in this session GOSUB RTC_control ' Check for oscillator failure, make sure it runs! ' ********************* OPTIONAL TIME SET-UP (new RTC or one whose battery failed) ' Set the real-time clock (RTC) to a table-specified time and date. ' GOSUB RTC_settime ' ***** If desired, modify the table below (see RTC_settime code) ' remove quote from above line, compile/execute, RESTORE QUOTE & RECOMPILE. ' Top-level loop ================================================================= ' Main: ' Beginning of top-level loop ' Task 1 : Display local date and time (and/or adjust clock) --------------------- ' ' Upon display of time and date, we allow adjustment to the 30-th second of the current ' minute when ADJUST (= TRIGGER pin) is release after being grounded for a little while. ' The ADJUST button should be used at least once per century... :) GOSUB I2C_time ' Fetch time and date from RTC (7 bytes) ' ******************************************* WARNING: Following code full of bugs! temp = buffer(5)&$1F ' Month number in BCD ($01 to $12) IF temp < 3 THEN flag = 0 ELSE flag = 1 GOSUB Dec2bin ' Convert to binary x = (temp + 9) // 12 ' Month index (0 = March ... 11 = February) x = (x * $3D3 + $10) >> 5 ' x = days from March 1 to first of month temp = buffer(4) x = 10 * hidigit + lodigit + x ' x = number of days since previous March 0 temp = buffer(6) : GOSUB Dec2bin char = temp + flag ' Year index (lower digits) in binary m = char // 4 ' m = year index modulo 4 READ Century, temp : GOSUB Dec2bin ' Get year/100 in binary y = 25 * temp + (char >> 2) ' y = olympiad index (16 bits) ' We now separate y into three bit fields: Gregorian periods (100 olympiads, ' in the upper byte) centuries (25 olympiads, 2 bits) and the remainder (5 bits). ' We must do so without any additional user RAM, since we're out of space! y = ((y / 100) << 8) + (y // 100) ' TEMP = Gregorian cycles ; CHAR = olympiads char = (char / 25) << 6 | (char // 25) leap VAR char.BIT5 ' We make good use of the one free bit IF m=3 AND ( char & %00011111 < 24 OR char >> 6 = 3 ) THEN leap = 0 ELSE leap = 1 ' TEMP is now the number of centuries, ' The date index (defined as the number of days ellapsed since March 0 of year 0) ' is now equal to: 146097*temp + 36524*(char>>6) + 1461*(char & $1F) + 365*m + x ' The day of the week depends simply on this quantity modulo 7 ' We should compare this to what's stored on the RTC chip: n = ( 5 * (char>>6 + (char & $1F)) + m + x - (buffer(3) & 7)) // 7 ' The only mistake the DS3231 can make is to insert a wrong Feb. 29. (as it will in ' 2100) so that it may report {Wed. March 12} when {Wed. March 13} is correct. ' Thus, we should simply add the above discrepancy N (a number from 0 TO 6) to correct ' flawlessly all mistakes accumulated for up to... four centuries. x = x + n ' ... / ... ' ******************************************* WARNING: Preceding code full of bugs! IF adjust = 0 THEN buffer = $30 ' Display "30 seconds", pending debounce GOSUB LCD_normal ' Start at top-left corner of screen IF dubious THEN counter = Undef : GOSUB LCD_showstring ELSE READ Century, temp ' Fetch century from Basic Stamp EEPROM temp = temp + bufferb(47) ' Add century toggle (BIT7 of Register $05) GOSUB Hexa2 ' Show first two digits of year temp = buffer(6) :GOSUB Hexa2 ' Show last two digits of year temp = buffer(5)&$1F :GOSUB Hyphen_hexa2 ' Hyphen, then month (removing century bit) temp = buffer(4) :GOSUB Hyphen_hexa2 ' Hyphen, then date (i.e., day of the month) ENDIF GOSUB Space2 ' 2 spaces between date and time temp = buffer(2) ' Get byte for hours IF temp.BIT6 THEN ' If in AM/PM format, then convert to 24h... IF (temp&$3F)=$12 THEN temp = temp-$12 ' Interpret 12am and 12pm as 0am and 0pm IF temp.BIT5 THEN temp = temp + $32 ' If PM time, clear PM flag and add $12 (BCD) IF temp.NIB0 > 9 THEN temp = temp+6 ' Propagate BCD carry, if needed ENDIF temp = temp & $3F :GOSUB hexa ' Show (military) hour, without leading zero temp = buffer(1) :GOSUB Colon_hexa2 ' Show minutes as 2 digits preceded by colon temp = buffer :GOSUB Colon_hexa2 ' Show seconds as 2 digits preceded by colon DayField CON LCD_pos | 64 ' Beginning of line 2 on LCD DayName CON Week-10 counter = (buffer(3)&7)*10 + DayName ' For 0, display " Set time" & "Dimanche " IF pendulum THEN counter = counter + 70 ' Alternate between French and English day char = DayField : GOSUB LCD_run ' Show COUNTER string at position CHAR IF adjust THEN Run ' No delay unless ADJUST button is pressed pendulum = 0 : buffer = $31 ' "30" is shown. Update needed for "31" ' We use each adjusment (there should be at least one per century) as am opportunity to ' fix every millenium bugs till AD 9999, with an additional 5000-year grace period: IF bufferb(47) THEN ' Gone from year 99 to 00 since last adjust? bufferb(47) = 0 ' Yes, reset toggle for the next century ! READ Century, temp IF lodigit = 9 THEN lodigit = $F ' BCD carry (first executed just after AD 2999) WRITE Century, temp+1 ' Update century in the EEPROM of this BS2 ENDIF ' 5 millenia allowed beyond 9999 (up to "F999") PAUSE 40 ' Debounce button (a switch or a temporary wire) DO : LOOP UNTIL adjust ' Wait (possibly many seconds) for button release GOSUB I2C_writebuffer ' Send adjusted time to RTC chip Run: ' Task 2 : Display current temperature ------------------------------------------- fahrenheit VAR pendulum ' Use degrees Fahrenheit on odd seconds GOSUB I2C_temperature degrees VAR x.HIGHBYTE ' Three convenient compile-time declarations fraction VAR x.LOWBYTE sign VAR bufferw.BIT7 ' Remains stable during temperature display degrees = buffer fraction = buffer(1) ' x = 256 * celsius (two's complement) READ Offset, Word y ' y = empirical temperature correction x = x + y ' Temperatures are assumed to be between -128 C (-249 F) and +124 C (255 F) IF fahrenheit THEN ' 16-bit Celsius to 17-bit Fahrenheit x = x + 32760 ' Translate to make number positive x = (x/10)*9 - 25388 ' HALF of (degrees Fahrenheit + 0.5) sign = x.HIGHBIT x = x << 1 ELSE sign = x.HIGHBIT ' Save sign ENDIF IF sign THEN x = -x ' Take absolute value ' Always print 6 characters for temperature (so previous display is overwritten) TempField CON LCD_pos | 78 ' Right-justified 6 characters on 2nd line char = TempField : GOSUB LCD_ctrl ' Move cursor to that field on screen IF degrees < 100 THEN GOSUB Space IF degrees < 10 THEN GOSUB Space ON sign GOSUB Space, Hyphen ' Print temperature sign IF degrees > 99 THEN flag = 1 char = degrees / 100 : GOSUB Hexnib degrees = degrees // 100 ELSE flag = 0 ENDIF char = degrees / 10 IF flag OR char THEN GOSUB Hexnib char = degrees // 10 : GOSUB Hexnib char = $DF : GOSUB Cout ' Use code for "degree" on HD44780 LCD IF fahrenheit THEN char="F" : GOTO Suffix ' Show only whole degrees if Fahrenheit FracDegree DATA "C258" ' Decoding table for quarter-degrees READ FracDegree+(fraction>>6), char ' C,2,5,8 for 0, 0.25, 0.5, 0.75 Celsius ' This scheme makes displayed Celsius convert to the SAME Fahrenheit as the original ! Suffix: ' Last character of temperature display GOSUB Cout ' Task 3 : Monitor real-time seconds (+ oscilloscope eye-candy) ------------------ ' This was my I2C sandbox, for oscilloscope tests and other ' experiments, BEFORE organizing the various tasks into a system of subroutines. ' It's now a standalone test of the RTC chip (DS3231) showing heartbeat updates... ' For speed in the "short loop", ACK bits are ignored using one-way 9-bit primitives! ' The period of the "Shortloop" (6.3 ms) is also the jitter of the PENDULUM signal. LOW scl ' Pulling SCL low makes SDA changes irrelevant HIGH sda Shortloop: ' SCL is known to be low at this point INPUT scl ' SCL and SDA are now high LOW sda ' START signal (SDA goes low when SCL is high) LOW trigger ' Falling edge at beginning of RTC access sequence SHIFTOUT sda, scl, MSBFIRST, [%110100000\9] ' Send write code, overwrite ack [$68,0,0] SHIFTOUT sda, scl, MSBFIRST, [0\9] ' Send register address (0) and overwrite ACK INPUT sda INPUT scl LOW sda ' START signal LOW scl SHIFTOUT sda, scl, MSBFIRST, [%110100010\9] ' Send read code, overwrite ack [$68,1,0] INPUT trigger ' Rising edge just before 1 Hz BCD counting! SHIFTIN sda, scl, MSBPRE, [frame\9] ' The ninth bit in this "read" is a NAK write parity VAR frame.BIT1 ' Location of lowest data bit in the above IF parity = pendulum THEN Shortloop ' Take shortcut, except once per second pendulum = parity LOW sda INPUT scl INPUT sda ' STOP signal, release bus ' Task 5 : Synchronize and sustain a physical pendulum --------------------------- ' This part is executed at a 1 Hz frequency (with a jitter of 6.3 ms) ' ' IF pendulum THEN HIGH kick ELSE LOW kick ' Constant time of execution ' PAUSE 8 ' Duration of the kick, in milliseconds ' INPUT kick ' ' One method to provide initial synchronization of the kicks is to monitor ' the position of the pendulum (using ultrasounds, light or magnetic sensing). GOTO Main ' Next iteration of top-level loop ' ============================================================ Some utilitiesines: ' ' DEC2BIN changes the (8-Bit) content of TEMP from decimal (BCD) to binary: Dec2bin: temp = 10 * hidigit + lodigit RETURN ' BIN2DEC converts a value from 0 to 99 in TEMP to BCD format: Bin2dec: temp = (temp / 10)<<4 | (temp // 10) RETURN ' ============================================================ Output subroutines: ' ' The argument of the basic numeric routines (except HEXNIB) is TEMP (8 bits). ' (The argument of HEXNIB and the main ascii routine "cout" is CHAR.) ' CHAR is almost always overwritten by these routines. TEMP is always preserved. ' HEXA prints TEMP in hexadecimal, without a leading zero ' It always outputs 2 characters (possibly with a leading space). Hexa: IF hidigit THEN Hexa2: GOSUB space ' Print space GOTO Hexa0 ' COLON_HEXA2 prints TEMP as two hexadecimal digits, preceded by a colon Colon_hexa2: char = ":" : GOSUB Cout GOTO Hexa2 ' HYPHEN_HEXA2 prints TEMP as two hexadecimal digits, preceded by an hyphen: Hyphen_hexa2: GOSUB Hyphen ' ' HEXA2 prints both nibbles of TEMP in hexadecimal (or 2 digits, if BCD format) Hexa2: char = hidigit GOSUB Hexnib ' hexa0 prints the lowest nibble of TEMP Hexa0: char = lodigit ' ' HEXNIB prints the low nibble of CHAR in hexadecimal Hexnib: abcdef CON "A"-10 ' Compute that at compile time char.HIGHNIB = 0 ' Isolate low nibble (useless?) IF char > 9 THEN char = char + abcdef ELSE char = char + "0" GOTO cout ' HYPHEN prints an hyphen (minus sign) Hyphen: char = "-" : GOTO Cout ' SPACE and SPACE2 print one or two blanks, respectively: Space2: GOSUB Space Space: char = " " : GOTO Cout ' SHOWTEMP prints TEMP in ASCII (overwriting CHAR) showtemp: char = temp ' ' COUT prints CHAR in ASCII (using the current output device) Cout: GOTO LCD_out ' Only one output device at this time ' ------------------------ LCD low-level subroutines. ' ' All LCD routines (except LCDinit) use the 8-bit "char" variable as input. ' ' The software fully maintains the state of the the LCD "enable" pin ' (E = LCD = Pin 9, #6 on LCD connector). A pull-down resistor is NOT needed. ' This pin is strobed with a positive pulse to transfer data to/from the LCD. ' ' In addition to the LCD backlight under software control on a dedicated pin (8) ' we use one of the four wiring schemes recommended by Parallax for LCD use: ' backlight PIN 8 ' Backlight anode (all BS2 pins have built-in current limiters) lcd PIN 9 ' LCD connector pin 6 (this is the "enable" pin E of the lcd) rw PIN 10 ' LCD connector pin 5 (could be tied to GND for low-speed write-only) rs PIN 11 ' LCD connector pin 4 (high for characters, low for commands) db4 PIN 12 ' LCD connector pin 11 LSBit of data nibble in 4-bit mode db5 PIN 13 ' LCD connector pin 12 db6 PIN 14 ' LCD connector pin 13 db7 PIN 15 ' LCD connector pin 14 MSBit of data nibble in 4-bit mode bf PIN 15 ' The MSBit of the data bus doubles as a read-only "busy flag". ' Rest of the LCD connector wiring: ' 1 = GND (0V) 2 = Vcc (5V) 3 = Vee (Contrast, between 0V and 5V using 10k pontentiometer). ' 7 to 10 = unconnected (would be high nibble in 8-bit mode) ' 16 = backlight cathode (connected to GND for the lighting management described here). ' The backlight tested draws 6.74 mA from pin 8, through the BS2 built-in resistor. ' One variable to allow end-of-line (code 13) implementation in messages LCD_line VAR Nib ' Valid values are 1,2,3 or 4 ' LCD command codes: LCD_clr CON %00000001 ' Clear the screen, cursor at top left LCD_hide CON %00001000 ' Blank display, no underscore, no blinker LCD_show CON %00001100 ' Show data display, hide cursor LCD_cg CON %01000000 ' First of 64 locations in CGRAM ("Charater Generator") LCD_pos CON %10000000 ' OR with desired 7-bit DDRAM position ("Data Display") ' LCD_INIT should be used at power-up (or as part of a full reset). ' This is the correct (clean) HD44780 setup for 4-bit operation. LCD_init: LOW lcd ' LCD enable pin (must NOT be disturbed by non-LCD usage) LOW rw ' 0=write, 1=read (can be disturbed by non-LCD) LOW rs ' 0=command, 1=data (can be disturbed by non=LCD) DIRD = %1111 ' Set up 4-bit data bus "D" (pins 12,13,14,15) for output OUTD = %0011 ' Initial high-nibble for reset (always in 8-bit mode) PAUSE 100 ' Give HD44780 plenty of time to power up PULSOUT lcd,7 ' Low nibble is ignored (don't bother grounding low pins) PAUSE 5 ' Long delay (more than 4.1 ms is required by specs) PULSOUT lcd,7 ' Strobe second time (still in 8-bit mose) PAUSE 1 ' The HD44780 would ignore any strobes for at least 100 us PULSOUT lcd,7 ' Strobe third time (still in 8-bit mose) PAUSE 1 ' The HD44780 would ignore any strobes for at least 100 us OUTD = %0010 ' Command code to establish 4-bit mode PULSOUT lcd,7 ' Strobe that command as 8-bit data (low pins are ignored) PAUSE 1 ' The HD44780 would ignore any strobes for at least 100 us ' Only now can we send 8-bit data as two successive nibbles and possibly check the ' "busy flag" (BF) for sending at high-speed without safety delays. ' The mandatory part of the initialization consists of only one more command which ' must be consistent with the actual format of the driven display (otherwise unknown ' to the HD44780 controller itself). A wrong N can make part of the LCD unusable and ' modify the contrast for the rest. (A wrong F seems inconsequential.) char=%00101000 ' Multiple lines, 5x8 font. [Can't be changed past this point] GOSUB LCD_send ' %0010NFxx -> N=0 for single line, F=1 for 5x11 matrix ' That's the end of the critical part. The rest can be redone or modified at will: char=LCD_hide ' Display OFF (to hide previous junk, if needed) GOSUB LCD_send char=%00000110 ' Auto-increment cursor, no display shift GOSUB LCD_send ' %00001AS -> A=1 move cursor right, S=1 shift display ' We complete the initialization by uploading custom characters, ' switching back to DDRAM and unblanking the display: ' char=LCD_cg : GOSUB LCD_ctrl ' Point to first location of CGRAM FOR counter = Chars TO EndChar-1 ' Upload 8 (or less) custom characters READ counter,char : GOSUB LCD_out NEXT ' ' LCD_normal is the "warm reset" procedure (it doesn't clear the screen but unblanks it). LCD_normal: LCD_line = 1 ' Top line char=LCD_pos ' Enable DDRAM and point to its first location (top-left corner) GOSUB LCD_ctrl char=%00001100 ' Display ON (D=1) with invisible cursor (C=B=0) ' %00001DCB -> C=1 underscored cursor, B=1 blinking ' ' LCD_ctrl sends a control command to the LCD unit. LCD_ctrl: LOW rs ' Select command mode GOSUB LCD_write IF char < 3 THEN PAUSE 3 ' Allow time for a long command (as we won't check BF) RETURN ' LCD_out sends a character to be displayed at the current cursor location on the LCD. LCD_out: HIGH rs ' Select data mode ' LCD_write: ' Send command (if RS=0) or data (if RS=1) DIRD = %1111 ' 4 bits to write LOW rw ' LCD_send can be used when RS is established and bus is ready for writing LCD_send: OUTD = char.HIGHNIB ' High nibble first PULSOUT lcd,1 ' Positive pulse (for falling-edge strobe) OUTD = char.LOWNIB PULSOUT lcd,1 RETURN ' ------------------------------------------------------------------------------------- ' The following routines show, on the LCD, stored strings which end with the ' token 8 (end of message) and may include other special tokens (9,10,11,12,13) ' to position the cursor at the beginning of some line. ' ' LCD_run displays the string indicated by COUNTER after issuing the command in CHAR ' (that command can be a positioning code or the "clear screen" code). LCD_run: GOSUB LCD_ctrl ' Send command code in CHAR GOTO LCD_showstring ' LCD_shownext: GOSUB LCD_out ' ' LCD_SHOWSTRING displays, at the current LCD cursor position, the string ' pointed to by COUNTER, until the endmarker (the 8 token) is found in it. LCD_showstring: READ counter,char counter = counter + 1 IF char > 15 THEN LCD_shownext ' Ordinary predefined character IF char < 8 THEN LCD_shownext ' Ordinary custom character IF char = 13 THEN char = (LCD_line+1)&3 + 9 ' 13 is like positioning to "next" line IF char < 13 THEN LCD_line = char-8 ' Update line number to 1,2,3 or 4 IF char = 14 THEN LCD_line = 1 ' Reset line to 1 for a "clear screen" READ CommandCode + char , char ' Fetch corresponding LCD command code IF char THEN LCD_run ' Zero "command code" means end of string RETURN ' ------------- Permanent data (stored in BS2 microcontroller EEPROM) ' ' At compile time, the above PBASIC code is uploaded into high EEPROM addresses. ' The following DATA is stored into low EEPROm, starting with address 0: ' CommandCode CON LCD_commands-8 ' Our special tokens go from 8 to 15 LCD_commands DATA 0 ' 8 = End of message (special handling) DATA $80, $C0, $94, $D4 ' 9,10,11 or 12 = Goto line 1,2,3 or 4 DATA 13 ' 13 = end of line (goto next line) DATA LCD_clr ' 14 = clear screen DATA 0 ' 15 reserved Splash DATA 14 ' Clear screen ' DATA 9,"2014-06-24 11:02:00" ' Line 1 (full 20-character width) DATA 12," G",0,"r",1,"rd P. Michon" ' Line 4 (bottom line) ' DATA 12," by" ' Line 4 overwrite ' DATA 10,"Introduction" ' Line 2 ' DATA 11," to Microcontrollers" ' Line 3 DATA 8 ' End of message ' The constant width (9) of these strings allow them to fully overwrite each other: ' Undef DATA " " ' Extra space makes 10-character string DATA " Set time",8 ' If day=0, show " Set time" & "Dimanche" Week DATA "Lundi ",8 ' (One way to say something's wrong.) DATA "M",1,"rdi ",8 DATA "Mercredi ",8 DATA "Jeudi ",8 DATA "Vendredi ",8 DATA "S",1,"medi ",8 DATA "Dim",1,"nche ",8 DATA "Mond",1,2," ",8 DATA "Tuesd",1,2," ",8 DATA "Wednesd",1,2,8 DATA "Thursd",1,2," ",8 DATA "Frid",1,2," ",8 DATA "S",1,"turd",1,2," ",8 DATA "Sund",1,2," ",8 ' The following custom characters are uploaded to the LCD just after initialization. ' There could be up to 8 custom characters (8 bytes/character, self-explanatory format). Chars DATA DATA %00010 ' * Most common accented letter in French (code 0) DATA %00100 ' * DATA %01110 ' *** DATA %10001 ' * * DATA %11111 ' ***** DATA %10000 ' * DATA %01110 ' *** DATA %00000 ' DATA %00000 ' "a" symbol in the style of standard "d" (code 1) DATA %00000 ' DATA %01101 ' ** * DATA %10011 ' * ** DATA %10001 ' * * DATA %10001 ' * * DATA %01111 ' **** DATA %00000 ' DATA %00000 ' "y" with descender (code 2) DATA %00000 ' DATA %10001 ' * * DATA %10001 ' * * DATA %10001 ' * * DATA %01111 ' **** DATA %00001 ' * DATA %01110 ' *** Example of a nonzero bottom row EndChar DATA ' End of EEPROM custom character generator table ' ------------------------ Inter-IC bus (I2C) subroutines: ' ' ------------------------ Naming some I2C addresses (mostly predefined by manufacturers): ' A "_suffix" (0,1,2,3) tells how many bits in the address are user-programmable. ' A single IC pin can provide either one or two addressing bit. The latter case occurs ' with address pins that can be tied to Gnd, Vdd, SDA or SCL (e.g., ADS1113/4/5). ' Alternatively, the address setting may be in EEPROM, modifiable by I2C (e.g., DS1077) ' or part of the address can be burned-in at the factory (e.g., MCP4725). ' dac_0 CON %0011000 ' $18 : 24-bit audio DAC (tlv320dac3120, mono 95 dB SNR) expand_3 CON %0100111 ' $20-$27: I/O expander (MCP23008=8-bit, MCP23017=16-bit) adj_3 CON %0101111 ' $28-$2F: Rheostat(s)/potentiometer(s) MCP453X/455X/463X/465X tsl2591 CON %0101001 ' $29 : Dual-diode high-range light sensor otp_1 CON %0101101 ' $2C-$2D: One-time-programmable potentiometer (AD5171) rheost_2 CON %0101100 ' $2C-$2F: Two 8-bit rheostats (AD5248) pot_0 CON %0101111 ' $2F : Two 8-bit potentiometers (AD5243) adc_2 CON %1001011 ' $48-$4B: ADC ADS1113/4/5 (tie ADDR to Gnd, Vdd, SDA or SCL) eeprom_2 CON %1010011 ' $50-$53: 2-jumper EEPROM (e.g., AT24C512) eeprom_3 CON %1010111 ' $50-$57: 3-jumper EEPROM (e.g., 24C32A) ' %1010xx. ' $50-$57: AT24CM01 functions like TWO separate AT24C512 ' Miscellaneous : pcf8563 CON %1010001 ' $51 : Real-time clock (RTC) by NXP semiconductors ds1077_3 CON %1011111 ' $58-$5F: ($58, normally) Programmable oscillator DS1077 mpl3115a2 CON %1100000 ' $60 : Altimeter 30 cm (Xtrinsic MPL3115A2) dac_1 CON %1100000 ' $60-$67: 12-bit DAC (MCP4725 = %1100yyx, jumper for x) mcp4725 CON %1100011 ' $62-$63: 12-bit DAC (MCP4725 = %110001x, jumper for x) rtc_0 CON %1101000 ' $68 : Real-time clock (Maxim DS1307, DS3231 or DS3232) bmp180 CON %1110111 ' $77 : Altimeter 25 cm (Bosh BMP085, or newer BMP180) ' ========================================== I2C debugging ' Abort program into sleep mode (backlight will blink for 18 ms every 2.304 s or so) I2C_notfound: ' DEBUG "Device not found." I2C_error: I2C_abort: ' GOSUB I2C_close ' DEBUG 13,"I2C size = ", DEC I2C_size ' DEBUG 13,"Codes: $",HEX2 I2C_code," ",HEX2 I2C_id10, " Register $",HEX2 I2C_register Fatal: ' DEBUG 13,"Fatal error.",13 END ' ========================================== I2C memory usage ' Three local variables: COUNTER (with named lowbyte) CHAR and ACK. i2c_7 VAR counter.LOWBYTE ' Only used as argument of I2C_set7 (usually, 7-bit address) ack VAR Bit ' Ninth bit (sent by receiver after a byte of data) 0 if OK ' One reserved 16-bit "i2c" variable (with named portions) contains valid codes to access ' the "current" I2C device. Don't change any of it without the primitives provided below. i2c VAR Word i2c_code VAR i2c.HIGHBYTE ' The main 8-bit access code i2c_mode VAR i2c_code.LOWBIT ' 1=read, 0=write i2c_id10 VAR i2c.LOWBYTE ' Lower 8 bits (undefined in usual 7-bit addressing) ' ' The following access parameter must also be declared i2c_size VAR Nib ' Witdth of internal address in bytes (0,1,2,3 or 4) ' ' Internal address pointer (at most 32 bits) to be initialized directly: i2c_volume VAR Byte i2c_page VAR Byte ' Upper part of a 24-bit large memory space (header=3) i2c_address VAR Word ' 16-bit memory address (header=2) EEPROM, RAM, etc. i2c_register VAR i2c_address.LOWBYTE ' 8-bit register address (header=1) ' Note that i2c_header is only used by I2C_fetch. If only the 3 "manual" routines I2C_fetch1/2/3 ' are used instead, then i2c_header need not be initialized. For a huge 32-bit address space, ' i2c_header contains the top 8 bits and the use of I2C_fetch4 would then be MANDATORY. ' If STREAM=0 all I2C transfers consist in reading or writing the entire content of the buffer. ' Otherwise, the bus is opened, bytes are transferred one at a time, then the bus is closed. stream VAR Bit ' 0 for buffered mode, 1 for stream mode ' The constituents of the buffer: length VAR Byte ' Actual size of the buffer odd VAR length.BIT0 ' Parity of the above number of bytes buffer VAR bufferw.BYTE0 ' The same space sliced into bytes (main access) buffern VAR bufferw.NIB0 ' The same space sliced into nibbles bufferb VAR bufferw.BIT0 ' The same space sliced into bits ' ========================================== I2C routines for "declaring" a slave node ' I2C_set7 will use COUNTER's low part (aka i2c_7) as a 7-bit I2C address whenever possible. I2C_set7: IF i2c_7 <= %0000111 THEN I2C_set8 ' Lowest 8 codes can't be 7-bit addresses IF i2c_7 < %1111000 THEN I2C_set ' Neither can anything beyond 120 ' Outside of the range 8-119 an valid slave address can only use 10-bit addressing... ' I2C_set8 and I2C_set10 extract the slave address from the lowest 8 or 10 bits of COUNTER, I2C_set8: counter.HIGHBYTE = 0 ' Expand 8-bit argument to 16 bits I2C_set10: i2c = counter << 3 i2c_code = (i2c_code & %00000110) | %11110000 ' Insert the two MSbits into main access code i2c_id10 = counter.LOWBYTE ' Use LSByte as secondary access code RETURN I2C_setRTC: i2c_7 = $68 ' Valid 7-bit address for DS1307, DS3231 or DS3232 i2c_size = 1 ' Internal address is a single byte ' ' I2C_set opens one of the 112 valid 7-bit slave addresses, assuming matching declarations. I2C_set: ' Enter one of the 112 valid 7-bit slave addresses i2c_code = i2c_7 << 1 ' Access code (for writing) = twice 7-bit address RETURN ' ========================================== I2C bus openers: ' ' As the I2C standard disallows empty messages (a START immediately followed by a STOP) ' START signals are only executed as part of the I2C_request subroutine defined below, ' which is itself our MANDATORY way to send the current slave address over the I2C bus. ' (Note that both 7-bit and 10-bit adressing are supported with almost no extra overhead.) ' To open an I2C device for reading: ' Use I2C_readold to open a device at the last location used (allowing for autoincrement). ' Use I2C_readnew to open the device at a new location (using a 8,16, 24 or 32-bit address). ' See also I2C_readnew1, which opens a device, reads one byte (into CHAR) and closes the bus. ' To open an I2C device for writing, use only I2C_writenew (the first routine defined below). ' Except for single-byte devices, a fixed number of bytes are always send first, just after ' opening a device this way, to specify the target location where subsequent bytes are ' supposed to go before the bus is closed (via I2C_close). ' I2C_writeraw opens the bus with a request to write on the current device without sending ' the MANDATORY target address (suitable to send stored data starting with target address) ' either for a straight write (if FLAG=0) or as part of a read/write (IF FLAG=1). ' [ GOSUB level 1 ] I2C_writeraw: i2c_mode = 0 GOTO I2C_request ' 0 means "write mode" ' I2C_readold simply opens the current device at the location of its internal pointer ' (which may have been auto-incremented from a previously declared location). ' [ GOSUB level 1 ] I2C_readold: i2c_mode = 1 ' 1 means "read mode" ' The only proper way to (re)start the I2C bus is as part of a new read/write request: I2C_request: LOW scl ' Make sure START/STOP is silenced INPUT sda ' Make sure SDA is pulled high INPUT scl ' Allow START/STOP LOW sda ' START signal (SDA from hi to lo while SCL hi) SHIFTOUT sda, scl, MSBFIRST, [i2c_code] ' Send 8-bit code SHIFTIN sda, scl, LSBPRE, [ack\1] ' Fetch ACK bit from slave IF ack THEN I2C_notfound IF i2c_code < %11110000 THEN RETURN ' Skip second byte except for 10-bit addressing IF flag AND i2c_mode = 0 THEN RETURN ' Also skip in second addressing during read/write ' DEBUG WARNING: Check that the above combination of flags is correct ! ' SHIFTOUT sda, scl, MSBFIRST, [i2c_id10] ' Send lower 8 bits of 10-bit address SHIFTIN sda, scl, LSBPRE, [ack\1] ' Fetch ACK bit from slave IF ack THEN I2C_notfound RETURN ' Return with ACK=0 if no problems ' I2C_readnew opens the bus to read from the declared pointer (1,2,3 or even 4-byte wide). ' It works for one-byte devices with 0 bytes of adress but I2C_readold is twice as fast. ' It either leaves the bus open for clocking data in (when BUFFERFLAG=0) or else it ' simply puts into the BUFFER the number of bytes specified by LENGTH. ' [ GOSUB level 2 ] I2C_temperature: ' Assuming current device is DS3231 or DS3232 I2C_register = $11 ' Two bytes for 256 * Temperature (signed) I2C_readword: length = 2 GOTO I2C_readbuffer ' RTC_SETTIME sets the time on any DS1307/DS3231/DS3232 RTC connected to the I2C bus. ' Here's the (modifiable) data that the above routine transfers to the RTC chip: Clock DATA $00 ' seconds (in BCD) DATA $09 ' minutes (in BCD) DATA $07 ' 24-hour military format (00 to 23) DATA $03 ' "User-defined" scheme (Sunday = 7) DATA $16 ' Date (day of the month in BCD, from 1 to 31) DATA $07 ' Month in current century (add $80 for "next" century) Year DATA $14 ' Year, modulo 100 Century DATA $20 ' Not sent to RTC chip (remains here for constant reference) Aging DATA $7F ' Factoring setting was $00 ' Not stored on the RTC chip either is the following (signed) temperature correction: Offset DATA Word %1111111010000000 ' Permanent -1.5C temperature correction RTC_settime: ' RTC chip is already selected for I2C access I2C_register = $00 ' Address of first register to update stream = 1 ' Bypass buffer GOSUB I2C_writenew ' Open for sequential writing, starting at $00 FOR counter = Clock TO Year ' 7 data bytes READ counter,char GOSUB I2C_write ' IF ack THEN ' DEBUG "RTC update failed after ",DEC counter-Clock," registers." ' GOTO I2C_abort ' ENDIF NEXT RTC_adjusted: ' This entry also used after a "30 sec. ADJUST" flag = 1 ' FLAG=0 means time hasn't been set in this session RTC_control: ' Load-and-modify all RTC control/alarm registers I2C_register = 7 ' Fetch last RTC registers (beyond date/time bytes) length = 12 ' (that's 12 bytes out of a total of 19) GOSUB I2C_readbuffer ' ----------------------- Here's where loaded registers can be checked and modified ' Control byte ($0E) [ Originally = $DF , reset TO $1C or $00] ' BIT7 : EOSC (enable oscillator). = 0 to allow unit to keep time on battery power. ' BIT6 : BBSQW (battery backed square wave). = 0 to make SQW high-impedance on battery power. ' BIT5 : CONV (initiate exceptional temperature conversion & TCXO update). = 0 if none needed. ' BIT4,BIT3 : RS2,RS1 (rate select). = 00 for 1 Hz frequency on SQW pin ' BIT2 : INTN (interrupt control). = 0 to put signal on SQW, rather than alarm iterrupts. ' BIT1 : A2IE (alarm-2 interrupt enable). = 0 to disable alarm-1 interrupts. ' BIT0 : A1IE (alarm-1 interrupt enable). = 0 to disable alarm-2 interrupts. buffer($0E-7) = %00100000 ' Initiate temperature & TCXO update (CONV = 1) ' Status byte ($0F) [ Originally = $8B , reset to $0B ] ' BIT7 : OSF (oscillator stop flag). Indicates timekeeping was interrupted (must reset time). ' BIT6,5,4 : 0. Unused. ' BIT3 : EN32kHz. Set to 1 to allow the 32768 Hz signal on the 32kHz pin. ' BIT2 : BUSY. Read-only (normally 0, except for brief temperature updates every 64 seconds). ' BIT1 : A2F (alarm 2 flag). Read-only. ' BIT0 : A1F (alarm 1 flag). Read-only. ' We clear the OSF only when the user has demonstrated s/he is aware of the condition, ' either by setting the time/date or performing a "30-second adjust" since the failure. temp = buffer($0F-7) | %00001000 ' Enable 32768 ouput osf VAR temp.BIT7 IF flag THEN osf = 0 ' If time has just been set or adjusted, clear failure flag dubious = osf ' Otherwise, time may or may not be dubious buffer($0F-7) = temp ' Aging register ($10) ' This 8-bit signed value (two's complement) slows down the oscillator by about ' 0.1 ppm per unit (at 25 degrees celsius). READ Aging, buffer($10) ' Overwrite with value stored in BS2 EEPROM temp = buffer($10) char = LCD_pos | 84 ' Beginning of fourth line (overwritten only in demo mode) GOSUB LCD_ctrl GOSUB Hexa2 ' Output in raw hexadecimal (TEMP is preserved) ' ----------------------- I2C_writebuffer writes back an updated buffer I2C_writebuffer: stream = 0 ' Use buffer GOTO I2C_writenew I2C_RTC: ' Get time & date FROM SCRATCH GOSUB I2C_setRTC I2C_time: I2C_register = 0 length = 7 I2C_readbuffer: stream = 0 ' Read the entire BUFFER and release the bus... ' I2C_readnew: flag = 1 : GOTO I2C_addr ' FLAG=1 for a so-called "write-read" operation ' ' I2C_writenew opens the bus for writing and first sends the declared target address. ' [ GOSUB level 2 ] I2C_writenew: flag = 0 ' FLAG=0 for a pure write to an I2C slave ' I2C_addr: ' Enter here only from the above, with flag set GOSUB I2C_writeraw ' Open device for writing ON I2C_size GOTO I2C_addr0, I2C_addr1, I2C_addr2, I2C_addr3, I2C_addr4 GOTO I2C_abort I2C_addr4: SHIFTOUT sda, scl, MSBFIRST,[i2c_volume] ' Highest byte of 32-bit address (if > 16 MB) PULSOUT scl,7 ' Ignore ack bit (if this fails, so will next ack) I2C_addr3: SHIFTOUT sda, scl, MSBFIRST, [i2c_page] PULSOUT scl,7 ' Ignore ack bit (if this fails, so will next ack) I2C_addr2: SHIFTOUT sda, scl, MSBFIRST, [i2c_address.HIGHBYTE] PULSOUT scl,7 ' Ignore ack bit (if this fails, so will next ack) I2C_addr1: SHIFTOUT sda, scl, MSBFIRST, [i2c_register] SHIFTIN sda, scl, LSBPRE, [ack\1] ' Fetch ack bit ' IF ack THEN ' DEBUG "Can't send address." ' GOTO I2C_abort ' ENDIF I2C_addr0: IF flag THEN I2C_get ' Branch for second part of a "write-read" IF stream THEN RETURN ' In stream mode, just leave the bus open (BUSY) IF length < 1 THEN I2C_close ' Abort if LENGTH isn't positive IF length > buffersize THEN I2C_close ' Abort if LENGTH is clearly erroneous FOR i = 0 TO length-1 ' 8-bit COUNTER SHIFTOUT sda, scl, MSBFIRST, [buffer(i)] SHIFTIN sda, scl, LSBPRE, [ack\1] ' Fetch ack bit IF ack THEN EXIT ' Slave didn't accept the byte, abort with ACK=1 NEXT GOTO I2C_close ' Return status in ACK (0 = OK) ' I2C_GET fetches a BUFFER (of declared LENGTH) from the declared pointer of the current device. ' [ GOSUB level 2 ] I2C_get: GOSUB I2C_readold ' (Re)START bus for reading from current pointer IF stream THEN RETURN ' In stream mode, just leave the bus open (BUSY) IF length < 1 THEN I2C_close ' Abort if LENGTH isn't positive IF length > buffersize THEN I2C_close ' Abort if BUFFER would overflow IF length = 1 THEN I2C_getlast ' Fetch just a single byte FOR i = 0 TO length-2 ' 8-bit COUNTER SHIFTIN sda, scl, MSBPRE, [buffer(i)] SHIFTOUT sda, scl, LSBFIRST, [0\1] ' Not last byte yet, so send ACK signal NEXT I2C_getlast: SHIFTIN sda, scl, MSBPRE, [buffer(length-1),ack\1] ' Receive 8 bits, send a NACK ' ' I2C CLOSE releases ("closes") the bus once a (multi-byte) transaction is complete. ' [ GOSUB level 1 ] I2C_close: LOW sda INPUT scl INPUT sda ' STOP signal (SDA raises while SCL is high) RETURN ' Transfer complete, release I2C bus ' ========================================== I2C primitives to use between START and STOP ' Suitable for "stream mode" (one byte at a time, without going through the BUFFER) ' I2C_READ reads into CHAR a byte which isn't last in a read sequence. ' For a last character, use : SHIFTIN sda,scl,MSPRE,[char,ack\1] (see I2C_getlast). ' [ GOSUB level 1 ] I2C_read: SHIFTIN sda, scl, MSBPRE, [char] ' Read data byte (NOT last in a read sequence) SHIFTOUT sda, scl, LSBFIRST, [0\1] ' ACK signal for every byte but the last one RETURN ' I2C_WRITE sends to the bus the 8-bit data in CHAR, returns ACK = 0 if reception OK ' [ GOSUB level 1 ] I2C_write: SHIFTOUT sda, scl, MSBFIRST, [char] ' Send 8 bits SHIFTIN sda, scl, LSBPRE, [ack\1] ' Fetch ACK bit from slave RETURN