' ' Program: FOX Temperature Data Logger v1.05 ' Author: (c) 2008 by A Fox Consulting & Design ' http://www.afox-consulting.com ' ' Version Notes ' ------- ------------------------------------------------------- ' v1.05 * Re-designed to work with a PCF8583 instead of a DS1307 RTC ' * Re-designed to work with LCD AppMod ' * Fixed bug with displaying tenths digit of temp that's been ' around since day 1 (was actually showing hundredths) ' * Gave LightData one decimal place of precision (0.0 - 100.0) ' * Fixed & streamlined code for displaying date on LCD (10/02/08) ' * Swapped the MEMCLR and CLKRST buttoms ' v1.04 * added code to keep _SDA line as output when not in use ' (I THINK this helps to keep current sink through bs2 down) ' * Changed the PAUSE in the main loop to NAP to keep power down ' * Fixed bug where samples would be overwritten if left running ' for > 21.3 hours. ' * Sample freq now 6 minutes to allow 25.5 hours of operation ' v1.03 * Modified to write to a EE24LC32 EEPROM rather than the stamp ' * i2c pins changed in prep for migrating to a BS2p24 ' * Samples now dumped in FIFO order. ' * Fixed critical bug with handling current minutes value ' v1.02 * Modified to use common serial data I/O line for ' DS1620 AND TSL230R, thus freeing an I/O pin ' v1.01 * Modified to use slightly less RAM AND program space ' * Also added speaker support and status LEDs ' * Re-arranged all of the I/O pins for breadbord convenience ' v1.00 * Initial version ' ' Complex sensor logging setup using ' The PCF8583 RTC to log times ' A DS1620 to measure temperature ' A TSL230R to measure light (set to 10x sensitivity, 1x sacle) ' ' Data is logged to a 24LC32 EEPROM. ' Upon power-up, buttons can be pressed to activate: ' * Clock set ' * Dump of stored samples ' * Sample memory clear ' ' DS1620 code taken from StampWorks experiment #30 ' ' {$STAMP BS2} ' {$PBASIC 2.5} ' ' -----[ I/O Definitions ]------------------------------------------------- _LCDEnable PIN 1 ' LCD Enable (1 = enabled) _LCDRW PIN 2 ' Read/Write\ _LCDRS PIN 3 ' Reg Select (1 = char) _LCDData VAR OUTL _LCDDataDir VAR DIRB _LCDButtons VAR INB ' i2c communications For the PCF8583 and EE24LS32... _i2cSDA PIN 8 ' I2C serial data line _i2cSCL PIN 9 ' I2C serial clock line _StatusLED PIN 10 ' Serial data lines for the DS1620 and TSL320... _LiteCS PIN 11 ' TSL320.3 _SDA PIN 13 ' Data line for serial devices _TempClk PIN 14 ' DS1620.2 _TempCS PIN 15 ' DS1620.3 (oddly, active-low) ' -----[ Constants ]------------------------------------------------------- LcdCls CON $01 ' clear the LCD LcdHome CON $02 ' move cursor home LcdCrsrL CON $10 ' move cursor left LcdCrsrR CON $14 ' move cursor right LcdDispL CON $18 ' shift chars left LcdDispR CON $1C ' shift chars right LcdLine1 CON $80 ' DDRAM address of line 1 LcdLine2 CON $C0 ' DDRAM address of line 2 Ack CON 0 ' acknowledge bit Nak CON 1 ' no ack bit PCF8583 CON %10100010 EE24LC32 CON %10100000 ' For the DS1620... RdTmp CON $AA ' read temperature WrHi CON $01 ' write TH (high temp) WrLo CON $02 ' write TL (low temp) RdHi CON $A1 ' read TH RdLo CON $A2 ' read TL RdCntr CON $A0 ' read counter RdSlope CON $A9 ' read slope StartC CON $EE ' start conversion StopC CON $22 ' stop conversion WrCfg CON $0C ' write config register RdCfg CON $AC ' read config register ' -----[ Variables ]------------------------------------------------------- ' for LCD AppMod buttons VAR Nib btnA VAR buttons.BIT0 ' left-most button - Reset Clock btnB VAR buttons.BIT1 ' Clear Samples btnC VAR buttons.BIT2 ' Dump Samples btnD VAR buttons.BIT3 ' right-most - Force Cample ' For PCF8583 RTC secs VAR Byte ' PCF8583 timer registers mins VAR Byte hrs VAR Byte yeardate VAR Byte month VAR Byte ' For DS1620 Slope VAR Word tC VAR Word ' Celsius tF VAR Word ' Fahrenheit ' For TSL320 LightData VAR Word ' Scaled to a value 0 - 100.0 ' Misc i2cAck VAR Bit ' Ack bit from i2c devices idx VAR Byte ' loop control c VAR Nib ' used for indexing digits for display DataByte VAR Byte ' General purpose work register DataWord VAR Word ' General purpose work register saddr VAR Word ' EEPROM address of current sample scount VAR Byte ' Current sample # omins VAR Byte cmins VAR Byte DispFlash VAR Nib Msg1 DATA "*SYSRST*" Msg2 DATA "*CLKSET*" Msg3 DATA "*MEMCLR*" Msg4 DATA "*MEMDMP*" ' -----[ Initialization ]-------------------------------------------------- Setup: DIRL = %11111110 ' LCD pins are outputs HIGH _LiteCS LOW _TempCS omins = 99 ' Force first sample if it's time DispFlash = 0 GOSUB LCD_Get_Buttons NAP 5 ' let LCD self-initialize DEBUG "FoxTempLogger v1.05", CR LCD_Init: _LCDData = %00110000 ' 8-bit mode PULSOUT _LCDEnable, 1 PAUSE 5 PULSOUT _LCDEnable, 1 PULSOUT _LCDEnable, 1 _LCDData = %00100000 ' 4-bit mode PULSOUT _LCDEnable, 1 DataByte = %00101000 ' 2-line mode GOSUB LCD_Command DataByte = %00001100 ' On, no crsr, no blink GOSUB LCD_Command DataByte = %00000110 ' inc crsr, no disp shift GOSUB LCD_Command saddr = Msg1 GOSUB Print_Message GOSUB Read_Sample_Count ' Get current sample count DEBUG "SAMPLES IN MEMORY: ", DEC scount, CR DataByte = LcdLine2 GOSUB LCD_Command DataByte = "S" GOSUB LCD_Write_Char DataByte = "=" GOSUB LCD_Write_Char GOSUB Print_SCount PAUSE 1000 Clear_Memory: IF BtnA THEN saddr = Msg3 GOSUB Print_Message scount = 0 GOSUB Write_Sample_Count ENDIF Reset_Clock: IF BtnB THEN saddr = Msg2 GOSUB Print_Message ' 02/23/09 03:30 PM secs = $00 mins = $30 hrs = $15 yeardate = $63 month = $02 GOSUB Set_Clock ENDIF Dump_Memory: IF BtnC THEN saddr = Msg4 GOSUB Print_Message IF scount > 0 THEN idx = scount ' Save current sample count FOR scount = 1 TO idx GOSUB Read_Sample GOSUB Display_Sample GOSUB Print_Sample NEXT scount = idx ' Restore sample count ENDIF ENDIF Reset_DS1620: HIGH _TempCS ' alert the DS1620 SHIFTOUT _SDA, _TempClk, LSBFIRST, [WrCfg, %11] ' use with CPU; one-shot mode LOW _TempCS PAUSE 10 ' -----[ Program Code ]---------------------------------------------------- Main: GOSUB LCD_Get_Buttons GOSUB Get_Clock ' read DS1307 cmins = mins.NIB1*10+mins.NIB0 ' Get current minute value DispFlash = DispFlash + 1 ' Take a sample every 5 minutes or if the _ForceSample button is pressed IF ((cmins <> omins) AND ((cmins // 6) = 0)) OR (BtnD) THEN HIGH _StatusLED GOSUB Read_DS1620 ' Get current temp. reading GOSUB Read_TSL230R ' Get current light reading LOW _StatusLED scount = scount + 1 GOSUB Write_Sample ' Write sample to EEPROM GOSUB Write_Sample_Count ' Update sample count in EEPROM GOSUB Display_Sample ' Show sample on DEBUG line GOSUB Print_Sample ' Show sample on LCD screen IF scount = 255 THEN GOTO MemFull ELSE ' Alternate the last sample and current time on the display IF DispFlash = 0 THEN GOSUB Print_Sample IF DispFlash = 8 THEN GOSUB Print_Time ENDIF omins = cmins NAP 5 GOTO Main MemFull: PULSOUT _StatusLED, 100 ' Indicate mem is full NAP 7 GOTO MemFull ' -----[ Subroutines ]----------------------------------------------------- Display_Sample: DEBUG "SAMPLE ", DEC3 scount, " @ 0x", HEX4 saddr, " - ", HEX2 month,"/",HEX2 (yeardate & $3F),"/",DEC2 (8+((yeardate & $C0) >> 6))," ",HEX2 hrs, ":", HEX2 mins, ":", HEX2 secs, " ", (tF.BIT15 * 13 + " "), DEC (ABS tF / 100), ".", DEC1 (ABS tF * 10), "F ", DEC (LightData / 10), ".", DEC1 LightData, "L", CR RETURN ' We'll display the LightData as 0000 - 1000 instead of 000.0 - 100.0 ' just so it looks nicer on the LCD. Debug will still show 0.0 - 100.0. ' LCD Screen: ' 12345678 ' 1 LLLL XXX ' 2 TTT.T Print_Sample: DataByte = LcdCls ' clear the LCD GOSUB LCD_Command DataByte = LcdLine1 GOSUB LCD_Command ' Print LightData FOR c = 3 TO 0 DataByte = (LightData DIG c) + 48 GOSUB LCD_Write_Char NEXT DataByte = " " GOSUB LCD_Write_Char GOSUB Print_SCount DataByte = LcdLine2 GOSUB LCD_Command ' Print tF FOR c = 4 TO 1 DataByte = (tF DIG c) + 48 GOSUB LCD_Write_Char IF c = 2 THEN DataByte = "." GOSUB LCD_Write_Char ENDIF NEXT RETURN Print_Time: DataByte = LcdCls ' clear the LCD GOSUB LCD_Command DataByte = LcdLine1 GOSUB LCD_Command DataByte = month.NIB1 + 48 ' month tens GOSUB LCD_Write_Char DataByte = month.NIB0 + 48 ' month ones GOSUB LCD_Write_Char DataByte = "/" GOSUB LCD_Write_Char DataByte = (yeardate.NIB1 & $3) + 48 ' day GOSUB LCD_Write_Char DataByte = (yeardate.NIB0) + 48 GOSUB LCD_Write_Char DataByte = "/" GOSUB LCD_Write_Char DataByte = "0" GOSUB LCD_Write_Char DataByte = ((yeardate.NIB1 & $C) >> 2) + 48 + 8 ' year GOSUB LCD_Write_Char DataByte = LcdLine2 GOSUB LCD_Command FOR c = 5 TO 0 ' cute trick to quickly decode time DataByte = secs.NIB0(c) + 48 ' hours, minutes, seconds GOSUB LCD_Write_Char IF (c & %0001) = 0 THEN DataByte = ":" GOSUB LCD_Write_Char ENDIF NEXT RETURN Print_Message: DataByte = LcdCls ' clear the LCD GOSUB LCD_Command DataByte = LcdLine1 GOSUB LCD_Command FOR c = 0 TO 7 READ saddr+c, DataByte GOSUB LCD_Write_Char DEBUG DataByte NEXT DEBUG CR RETURN Print_SCount: FOR c = 2 TO 0 DataByte = (scount DIG c) + 48 GOSUB LCD_Write_Char NEXT RETURN ' Send command to LCD ' falls through to LCD_Write_Char LCD_Command: LOW _LCDRS ' enter command mode ' Write character to current cursor position ' but byte to write in 'char' LCD_Write_Char: _LCDData = _LCDData & %00001001 ' clear bus; save RS, P0 _LCDData = DataByte & $F0 | _LCDData ' -> high nib PULSOUT _LCDEnable, 1 ' strobe the E _LCDData = _LCDData & %00001001 _LCDData = DataByte << 4 | _LCDData ' -> low nib PULSOUT _LCDEnable, 1 HIGH _LCDRS ' return to character mode RETURN ' Read and debounce the LCD AppMod buttons LCD_Get_Buttons: _LCDDataDir = %0000 ' make LCD bus inputs buttons = %0000 ' assume all unpressed FOR idx = 1 TO 10 buttons = buttons | _LCDButtons ' make sure button held PAUSE 5 ' debounce 10 x 5 ms NEXT _LCDDataDir = %1111 ' return data lines to outputs RETURN ' Do a block write to clock registers Set_Clock: GOSUB I2C_Start ' send Start DataByte = PCF8583 & %11111110 ' send slave ID (write) GOSUB I2C_TX_Byte IF (i2cAck = Nak) THEN Set_Clock ' wait until not busy DataByte = 2 ' start with seconds reg GOSUB I2C_TX_Byte FOR idx = 0 TO 4 ' write time registers DataByte = secs(idx) GOSUB I2C_TX_Byte NEXT GOSUB I2C_Stop RETURN ' Do a block read from clock registers Get_Clock: GOSUB I2C_Start ' send Start DataByte = PCF8583 & %11111110 ' send slave ID (write) GOSUB I2C_TX_Byte IF (i2cAck = Nak) THEN Get_Clock ' wait until not busy DataByte = 2 ' point at secs register GOSUB I2C_TX_Byte GOSUB I2C_Start DataByte = PCF8583 | %00000001 ' send slave ID (read) GOSUB I2C_TX_Byte FOR idx = 0 TO 4 ' read time registers GOSUB I2C_RX_Byte secs(idx) = DataByte NEXT GOSUB I2C_RX_Byte_Nak ' read control into DataByte (we won't use) GOSUB I2C_Stop hrs = hrs & $3F ' Remove un-needed bits month = month & $1F ' Remove un-needed bits RETURN ' Get temperature reading - High-Resolution logic Read_DS1620: HIGH _TempCS ' alert the DS1620 SHIFTOUT _SDA, _TempClk, LSBFIRST, [StartC] ' start conversion LOW _TempCS ' release the DS1620 DO HIGH _TempCS SHIFTOUT _SDA, _TempClk, LSBFIRST, [RdCfg] ' read config register SHIFTIN _SDA, _TempClk, LSBPRE, [DataByte\8] LOW _TempCS LOOP UNTIL (DataByte.BIT7 = 1) ' wait for conversion HIGH _TempCS SHIFTOUT _SDA, _TempClk, LSBFIRST, [RdTmp] ' read raw temperature SHIFTIN _SDA, _TempClk, LSBPRE, [DataWord\9] LOW _TempCS OUTPUT _SDA tC = DataWord >> 1 ' remove half degree bit tC.BYTE1 = -tC.BIT7 ' extend sign bit tC = tC * 100 ' convert to 100ths HIGH _TempCS SHIFTOUT _SDA, _TempClk, LSBFIRST, [RdCntr] ' read counter SHIFTIN _SDA, _TempClk, LSBPRE, [DataWord\9] LOW _TempCS OUTPUT _SDA HIGH _TempCS SHIFTOUT _SDA, _TempClk, LSBFIRST, [RdSlope] ' read slope SHIFTIN _SDA, _TempClk, LSBPRE, [slope\9] LOW _TempCS OUTPUT _SDA tC = tC - 25 + (slope - DataWord * 100 / slope)' fix fractional temp IF (tC.BIT15 = 0) THEN tF = tC */ $01CC + 3200 ' convert pos C to Fahr ELSE tF = 3200 - ((ABS tC) */ $01CC) ' convert neg C to Fahr ENDIF RETURN Read_TSL230R: ' Get a sample LOW _LiteCS PAUSE 10 COUNT _SDA, 200, DataWord ' Count pulses for 200ms HIGH _LiteCS OUTPUT _SDA LightData = DataWord MAX 1000 RETURN ' Write sample data. Each sample follows this format: ' 0x0001 : ss mm hh dd mm ff ff - ll ll 00 00 00 00 00 00 ' We write 15 bytes at a time because the 24LC32 Page WRITE op works with ' pages boundaries of 8 bytes and this way our sample will end on ' a page boundary. Write_Sample: sAddr = ((scount-1) * 16) + 1 ' Determine address to write to IF sAddr < $3FF0 THEN ' Mem full check GOSUB I2C_Start ' send Start DataByte = EE24LC32 & %11111110 ' send slave ID (write) GOSUB I2C_TX_Byte IF (i2cAck = Nak) THEN Write_Sample' wait until not busy FOR idx = 0 TO 16 ' 2 address bytes + 15 data LOOKUP idx, [sAddr.BYTE1, sAddr.BYTE0, secs, mins, hrs, yeardate, month, tF.BYTE1, tf.BYTE0, LightData.BYTE1, LightData.BYTE0, 0, 0, 0, 0, 0, 0], DataByte GOSUB I2C_TX_Byte NEXT GOSUB I2C_Stop ENDIF PAUSE 20 RETURN ' Write sample count to addr 0x0000 Write_Sample_Count: GOSUB I2C_Start ' send Start DataByte = EE24LC32 & %11111110 ' send slave ID (write) GOSUB I2C_TX_Byte IF (i2cAck = Nak) THEN Write_Sample_Count ' wait until not busy DataByte = 0 ' Access address 0x0000 GOSUB I2C_TX_Byte GOSUB I2C_TX_Byte DataByte = scount ' Write scount GOSUB I2C_TX_Byte GOSUB I2C_Stop PAUSE 10 RETURN ' Read sample data. Each sample is 9 bytes. ' Format: 0x0001 : ss mm hh dd mm ff ff ll Read_Sample: sAddr = ((scount-1) * 16) + 1 ' Determine address to write to GOSUB I2C_Start ' send Start DataByte = EE24LC32 & %11111110 ' send slave ID (write) GOSUB I2C_TX_Byte IF (i2cAck = Nak) THEN Read_Sample ' wait until not busy DataByte = sAddr.BYTE1 ' high address of read GOSUB I2C_TX_Byte DataByte = sAddr.BYTE0 ' Low address of read GOSUB I2C_TX_Byte GOSUB I2C_Start ' Terminate write operation DataByte = EE24LC32 | %00000001 ' send slave ID (read) GOSUB I2C_TX_Byte FOR c = 0 TO 4 GOSUB I2C_RX_Byte secs(c) = DataByte NEXT GOSUB I2C_RX_Byte tF.BYTE1 = DataByte GOSUB I2C_RX_Byte tF.BYTE0 = DataByte GOSUB I2C_RX_Byte LightData.BYTE1 = DataByte GOSUB I2C_RX_Byte_Nak LightData.BYTE0 = DataByte GOSUB I2C_Stop RETURN ' Read sample count at 0x0000. Read_Sample_count: GOSUB I2C_Start ' send Start DataByte = EE24LC32 & %11111110 ' send slave ID (write) GOSUB I2C_TX_Byte IF (i2cAck = Nak) THEN Read_Sample_Count ' wait until not busy DataByte = 0 ' Access address 0x0000 GOSUB I2C_TX_Byte GOSUB I2C_TX_Byte GOSUB I2C_Start ' Terminate write operation DataByte = EE24LC32 | %00000001 ' send slave ID (read) GOSUB I2C_TX_Byte GOSUB I2C_RX_Byte_Nak scount = DataByte GOSUB I2C_Stop RETURN ' -----[ Low Level I2C Subroutines]---------------------------------------- ' *** Start Sequence *** I2C_Start: ' I2C start bit sequence INPUT _i2cSDA INPUT _i2cSCL LOW _i2cSDA Clock_Hold: DO : LOOP UNTIL (_i2cSCL = 1) ' wait for clock release RETURN ' *** Transmit Byte *** I2C_TX_Byte: SHIFTOUT _i2cSDA, _i2cSCL, MSBFIRST, [DataByte\8] ' send byte to device SHIFTIN _i2cSDA, _i2cSCL, MSBPRE, [i2cAck\1] ' get acknowledge bit RETURN ' *** Receive Byte *** I2C_RX_Byte_Nak: i2cAck = Nak ' no Ack = high GOTO I2C_RX I2C_RX_Byte: i2cAck = Ack ' Ack = low I2C_RX: SHIFTIN _i2cSDA, _i2cSCL, MSBPRE, [DataByte\8] ' get byte from device SHIFTOUT _i2cSDA, _i2cSCL, LSBFIRST, [i2cAck\1] ' send ack or nak RETURN ' *** Stop Sequence *** I2C_Stop: ' I2C stop bit sequence LOW _i2cSDA INPUT _i2cSCL INPUT _i2cSDA RETURN