Meter Stamp Box Main Program

Variables and Declarations

' MeterStampBox.bs2

' Box with Meter and speaker

' John Saunders 11/24/2009

' {$STAMP BS2}

' {$PBASIC 2.5}


' Separated the recording of digital inputs



'Stamp Ports

DS1302CE  PIN        0       'Output: DS1302 Chip Enable

DS1302IO  PIN        1       'Bi-directional:DS1302 Data

DIO1      PIN        2       'Input or Output:Digital I/O #1, D-25 pin 19

DIO2      PIN        3       'Input or Output:Digital I/O #2, D-25 pin 20

UPSW      PIN        4       'Input: UP Switch

DNSW      PIN        5       'Input: Down Switch

SDOut     PIN        6       'Output to SD Interface

SDIn      PIN        7       'Input from SD Interface

SelPort   VAR        OUTC    'Selector for 138 and 259 chips, pins 8,9,10

En138     PIN        11      'Output:High pulse to enable the 138

CLK       PIN        12      'Output:Clocks all

InPort    PIN        13      'Input:166 and ADC Data

En259     PIN        14      'Output:259 gate

OutPort   PIN        15      'Output:Display RS,74HC164 & 166 serial in,259 D


'Initialization

Dispinitlen      CON    3

DispInitAddr     CON    0

DispInitData     DATA   $38,$0C,$01,$06 'Need $38(2-line) and use cmd $C0 for second 8 char pos



'String Declarations

'These are used with DispBCDString to generate most of the display fields

DateString     DATA     $89,"/",$87,0

YearString     DATA     "/",$8D,0

YearAdjString  DATA     "20",$8D," ADJ",0

MinAdjString   DATA     "Min ",$83,0

HourAdjString  DATA     "Hour ",$85,0

TimeString     DATA     $85,":",$83,":",$81,0

RecordString   DATA     $89,"/",$87,"/",$8D,",",$85,":",$83,":",$81,",",0

VoltString     DATA     " V",0

MAString       DATA     " MA",0


'Display parameters

Char      VAR    Byte        'General use

DECdata   VAR    Word        'General use

UpperByte VAR    DECData.HIGHBYTE

LowerByte VAR    DECData.LOWBYTE

SignBit   VAR    DECdata.BIT15

div       VAR    Byte  'dividend used in writing numbers, and general use

rem       VAR    Word  'remainder used in writing multiple-digit numbers, and general use

upperrem  VAR    rem.HIGHBYTE

lowerrem  VAR    rem.LOWBYTE

Iter      VAR    Nib   'for loop variable

DispMode  VAR    Nib   'Controls the way a decimal value is to be displayed


'Control variables and External Digital Inputs

IOdata    VAR    Byte  'Data to be either input or output, also display R/S

Mode      VAR    Nib   'Bits 0-2 are the mode switch encoded

DigInputs VAR    Byte  'Two Input/Outputs, the Serial Input and the five digital inputs,

SERIO     VAR    DigInputs.LOWNIB.BIT0

DigIOA    VAR    DigInputs.LOWNIB.BIT1

DigIOB    VAR    DigInputs.LOWNIB.BIT2

DigInA    VAR    DigInputs.LOWNIB.BIT3

DigInB    VAR    DigInputs.HIGHNIB.BIT0

DigInC    VAR    DigInputs.HIGHNIB.BIT1

DigInD    VAR    DigInputs.HIGHNIB.BIT2

DigInE    VAR    DigInputs.HIGHNIB.BIT3

OldMode   VAR    Nib

SerialIn  CON    0     'Selects Serial In for InPort

BallFlag  VAR    Bit   'One selects Black


'138 select constants

DispEn    CON    0

SHLD      CON    9     'Also sets gate high since 166 has synchronous load

ADCICE    CON    2

ADCVCE    CON    3

Chime     CON    4

CLR259    CON    5

Beep      CON    6


'259 select constants

Black     CON    0

White     CON    1

PSEn      CON    3

DigAOut   CON    7

DigBOut   CON    5

DigCOut   CON    2

DIGDOut   CON    4


'Dallas DS1302 RTC addresses and variables

WRProtData      CON     0

WRProtAddr      CON     $8E

OSCEnAddr       CON     $80

DSRangeAddr     CON     $D0

DSIntervalAddr  CON     $D2

TCHData         CON     %10100101

TCHAddr         CON     $90

ReadMonthAddr   CON     $89

ReadDateAddr    CON     $87

WriteHourAddr   CON     $84

WriteMinuteAddr CON     $82

DSAddr          VAR     Byte           'DS1302 register address

DSData          VAR     Byte           'DS1302 data is in BCD format

Tens            VAR     DSData.HIGHNIB

Units           VAR     DSData.LOWNIB

OldHour         VAR     Byte           'To sound the hour


'Rogue Secure Digital Interface variables

DoneFlag        VAR     Bit  'Recording has been completed

RecDig          VAR     Bit  'If 1 digital inputs are recorded, else displayed

Interval        VAR     Byte 'The number of iterations between recordings

RecordCount     VAR     Byte 'The countdown to record time

NoPrompt        VAR     Bit  'SD interface did not give a prompt

EnRecord        VAR     Bit  '1 = recording enabled - white ball

MinInterval     CON     10   'Minimum recording interval

MonTens   VAR    LowerByte.HIGHNIB  'used to grnerate filenames

MonUnits  VAR    LowerByte.LOWNIB

DayTens   VAR    lowerrem.HIGHNIB

DayUnits  VAR    lowerrem.LOWNIB


'Analog parameters

Current     VAR         Word

Voltage     VAR         Word

voltoffset  CON         4    'Correction for voltage amplifier offset, in counts

ampoffset   CON         4    'Correction for current amplifier offset, in counts

Range       VAR         Nib  'Index into AmpParams for the selected shunt

MaxRange    CON         10   'Display value,Value Display mode, multiplier,divider,Decimal Display mode

AmpParams   DATA   125,$A,4,2,$F, 25,1,4,1,$F, 38,1,14,2,$F, 50,1,4,0,$F, 63,1,24,2,$F,

                  75,1,14,1,$F, 100,2,9,0,$F,  125,2,4,2,$B, 150,2,3,1,$B, 250,2,4,1,$B, 1,3,9,0,$B

 Start and Main

Start:

DIRS  = %1101111101000001

HIGH En259                   'Establishes the idle level for the 259 command register

LOW  En138                   'Establishes the idle level for the 138 command decoder

Selport = CLR259             'Clear the 259

PULSOUT En138,10

LOW DS1302CE                 'Establishes the idle level for the DS1302 RTC

DECData = 0

GOSUB InitDisp

SelPort = PSen               'Turn on the isolation DC-DC converter for the current input

Outport = 1

PULSOUT En259, 10

Outport = 0

'Commented out because of lack of memory

'DsAddr = DSRangeAddr + 1      'Restore previous session range

'GOSUB DS1302OUT

'Range = Units

'IF Range > MaxRange THEN

  range = MaxRange

'ENDIF

DsAddr = DsIntervalAddr + 1   'Restore previous session interval

GOSUB DS1302OUT

Interval = DSData

DoneFlag = 1                  'Makes a wait before recording

RecordCount = Interval        'Make a delay on startup to allow the Rogue to boot

EnRecord = 0                  'Must restart recording manually

BallFlag = 1                  'Black

GOSUB MoveBall


Main:                        'Main loop

GOSUB GetInputData           'Mode and digital inputs

GOSUB MeasureVolt

GOSUB MeasureAmp

RecordCount = RecordCount - 1

'Check IF time to record

IF (RecordCount = $05) THEN

  DoneFlag = 0          'Permits a new recording

ELSEIF (RecordCount < $01) AND (DoneFlag = 0) AND (EnRecord = 1) THEN

  GOSUB Record               'Record date, time, current, voltage, external inputs

  RecordCount = Interval

ENDIF

' Erase display if mode changes or switch closed

IF (Mode <> OldMode) OR (UPSW = 0) OR (DNSW = 0) THEN

  GOSUB SoundBeep

  Char = 1

  GOSUB DispCmd

ENDIF

OldMode = Mode

Char = $80                   'Displays and actions for the left 8 character positions

GOSUB DispCmd

SELECT Mode

CASE 0                       '"A" Start or Stop recording

  GOSUB DispAmp

  IF (UPSW = 0)  THEN

    EnRecord = 1

    BallFlag = 0             'White

    GOSUB MoveBall

    RecordCount = Interval

  ENDIF

  IF (DNSW = 0)THEN

    EnRecord = 0

    BallFlag = 1             'Black

    GOSUB MoveBall

  ENDIF

CASE 1                       '"T"

  DECData = TimeString

  GOSUB DispBCDString

CASE 2                       '"R"

  GOSUB DispInputs

CASE 3                       '"I"

  GOSUB DispAmp

CASE ELSE                    '"M" and "H"

  DECData = YearAdjString

  GOSUB DispBCDString

ENDSELECT

Char = $C1                   'The actions and display for the right-hand field

GOSUB DispCmd

SELECT Mode

CASE 0                        '"A"

  Decdata = Voltage

  DispMode = $F

  GOSUB DispDec

  Decdata = VoltString

  GOSUB DispBCDString

CASE 1                        '"T"

  DECData = DateString

  GOSUB DispBCDString

CASE 2                        '"R"

  GOSUB DispInterval

CASE 3                        '"I"

  GOSUB DispRange

CASE 4                        '"M"

  GOSUB AdjMin

CASE 5                        '"H"

  GOSUB AdjHour

ENDSELECT

IF (EnRecord = 0) THEN main

PAUSE 850

GOTO main

STOP

Subroutines

'subroutines:


'subroutines for Samsung KS0066 LCD display controllers:


InitDisp:               'Set up no. of lines, display on, cursor off, write direction, erase

FOR Iter = 0 TO DispInitLen

  READ DispInitAddr + Iter,Char

  GOSUB DispCmd

  PAUSE 200

NEXT

RETURN


DispCmd:  'puts an instruction (Char) to control the display

SHIFTOUT Outport,CLK,1,[Char\8]   'clocks the instruction into the 164 shift register, MSB first

LOW outport                       'instruction function is low, outport does double duty

SelPort = DispEn

PULSOUT En138,10                  'clocks the instruction and the function into the display

LOW En138

PAUSE 1

RETURN


DispChar:                          'puts one character (Char)onto the display

SHIFTOUT Outport,CLK,1,[Char\8]    'clocks the  character into the shift register

HIGH outport                       'data function is high

SelPort = DispEn

PULSOUT En138,10                   'clocks the character into the display

LOW En138

RETURN


DispDec:                'Displays DECData at current cursor position in Digital

IF DispMode = 0 THEN OneDigit

IF DispMode = 1 THEN TwoDigit

IF (DispMode = 2) OR (DispMode = $C) OR (DispMode = $A) THEN ThreeDigit

'4-digit

div = DECData/1000

rem = DECData//1000

Char = div + "0"

GOSUB DispChar

DECData = rem

ThreeDigit:

div = DECData/100

rem = DECData//100

Char = div + "0"

GOSUB DispChar

IF (Dispmode = $F) OR (DispMode = $C) THEN

  Char = "."

  GOSUB DispChar

ENDIF

DecData = rem

TwoDigit:

div = DECData/10

rem = DECData//10

Char  = div + "0"

GOSUB DispChar

IF (Dispmode = $B) OR (DispMode = $A) THEN

  Char = "."

  GOSUB DispChar

ENDIF

DECData = rem

OneDigit:

Char = DECdata  + "0"

GOSUB DispChar

RETURN


'DECdata points to a DATA array which contains ASCII characters and DS1302 register addresses

DispBCDString:          'Display dates, times, and character strings, null-terminated

Iter = 0

DO                      'Decode the instructions in the control string until a null character

  READ (DECData + Iter),DSAddr

  IF DSAddr = 0 THEN RETURN

  IF DSAddr > $80 THEN  'This must be an address in the DS1302, no ASCII character used here is this high

      GOSUB DS1302OUT

      Char = Tens + "0"            'Converts BCD to ascii, this is the upper nibble of DSData

      GOSUB DispChar

      Char = Units + "0"           'Converts BCD to ascii, this is the lower nibble of DSData

      GOSUB DispChar

  ELSE                             'Already a literal ASCII character (less than $80)

      Char = DSAddr

      GOSUB DispChar

  ENDIF

  Iter = Iter+1                    'Get next DATA array value

LOOP

RETURN


DispAmp:

  Decdata = Current

  READ AmpParams + 4 + (5*range),DispMode   'The location of the decimal point varies by range

  GOSUB DispDec

  Decdata = MAString

  GOSUB DispBCDString

RETURN


'subroutines for the Dallas DS1302 real-time clock with battery backup


DS1302IN:               'Put the byte in DSData into the DsAddr location

HIGH DS1302CE           'Enable the DS1302

SHIFTOUT  DS1302IO,CLK,0,[DSAddr\8] 'Put the register address (read) into the chip

SHIFTOUT  DS1302IO,CLK,0,[DSData\8] 'The chip responds on the same line

LOW DS1302CE

RETURN


DS1302OUT:              'Read the contents of DSAddr into DsData

HIGH DS1302CE           'Enable the DS1302

SHIFTOUT  DS1302IO,CLK,0,[DSAddr\8] 'Put the register address (write) into the chip

SHIFTIN  DS1302IO,CLK,1,[DSdata\8]  'Put the new value into the chip

LOW DS1302CE

RETURN


'Due to lack of program space, a separate program is loaded (SetTimeDate.bs2) to load the RTC

'from ASCII files on a SD chip

'This should not be required again, since the RTC has its own long-term battery

AdjMin:                      'To correct for drift

Decdata = MinAdjString

GOSUB DispBCDString

DsAddr = WriteMinuteAddr + 1

GOSUB DS1302OUT

IF (UPSW = 0) AND (DsData < 58) THEN

  DsData = DsData + 1

  DsAddr = WriteMinuteAddr

  GOSUB DS1302IN

  PAUSE 500

ENDIF

IF (DNSW = 0)AND (DsData > 0) THEN

  DsData = DsData - 1

  DsAddr = WriteMinuteAddr

  GOSUB DS1302IN

  PAUSE 500

ENDIF

RETURN


ADjHour:                     'For Daylight Savings

Decdata = HourAdjString

GOSUB DispBCDString

DsAddr = WriteHourAddr + 1

GOSUB DS1302OUT

IF OldHour <> Dsdata THEN    'The chime only sounds in "H" mode

  GOSUB SoundChime

ENDIF

OldHour = Dsdata

IF (UPSW = 0) AND (DsData < 23) THEN

  DsData = DsData + 1

  DsAddr = WriteHourAddr

  GOSUB DS1302IN

  PAUSE 500

ENDIF

IF (DNSW = 0)AND (DsData > 0) THEN

  DsData = DsData - 1

  DsAddr = WriteHourAddr

  GOSUB DS1302IN

  PAUSE 500

ENDIF

RETURN


'subroutines for the front panel switch, ball, loudspeaker and and external digital inputs


GetInputData:             'Rotary Switch  and external inputs

SelPort = SHLD            'Selects the 74LS166 enable output of the 74HCT138

HIGH En138                'Enable Load Mode

PULSOUT CLK,10            'The 74LS166 uses a synchronous load

LOW En138                 'Shift mode

SHIFTIN InPort,CLK,0,[IOdata\8]          'Shift inputs from the 166 shift register, MSB first

Mode = IOdata & $07

DigInputs = (IOData & $F8)

'These three are not connected to the 166: they replace the mode bits in DigInputs

'They could be used for an external serial device

IF DIO1 = 1 THEN           'A direct Stamp port with pullup

  DigInputs = DigInputs | $02

ENDIF

IF DIO2 = 1 THEN           'A direct Stamp port with pullup

  DigInputs = DigInputs | $04

ENDIF

'Commented out because of lack of memory

SelPort = SerialIn         'A floating unused input of the 74HC153 data selector

IF InPort = 1 THEN

  DigInputs = DigInputs | $01

ENDIF

RETURN


DispInputs:                'Displays or records: DIOA,DIOB,DIGIn A ... DigIn E

IOData = $01

FOR Iter = 0 TO 7

  IF (DigInputs &  IOData) > 0 THEN

    Char = "1"

  ELSE

    Char = "0"

  ENDIF

  IF RecDig = 0 THEN

    GOSUB DispChar

  ELSE

    SEROUT SDOut,84,[",",Char]

  ENDIF

  IOData = IOData << 1

NEXT

RETURN


SoundChime:                'This is a kit for several fancy door chimes on a tiny board

Selport = Chime            'They are played in fixed sequence

PULSOUT En138, 30          'It only needs a short low pulse to trigger

RETURN


MoveBall:                  'This is a mechanical military fault indicator with 2 coils

SelPort = BallFlag         'Controls which 259 output is used

LOW Outport                'Needs a positive pulse on the selected port, left negative

LOW En259                  'This lets the Outport signal pass through the register

PULSOUT Outport, 10000     'Length arrived at by experiment

HIGH En259                 'Disable the 259 while negative, will hold this value

RETURN


SoundBeep:                 'This driver is wired-OR with the Chime to the loudspeaker.

SelPort = Beep             'Use sparingly as it has a very high current draw.

FREQOUT En138,15,1000      'Pulsing the enable will make the sound

LOW En138

RETURN


'Subroutines for the Rogue Electronics MMC - Secure Digital serial interface


DispInterval:                'The interval between recordings, in cycles of main

DecData = Interval

DispMode = 2

GOSUB DispDec

Char = " "

GOSUB DispChar

IF EnRecord = 0 THEN NoCountDisplay

DecData = RecordCount

DispMode = 2

GOSUB DispDec

NoCountDisplay:

DSAddr = DSIntervalAddr

IF (UPSW = 0)  THEN

  Interval = Interval + 1

  DsData = Interval          'Save this value in the DS1302

  GOSUB DS1302IN

Range = Units

ENDIF

IF (DNSW = 0)AND (Interval > MinInterval) THEN

  Interval = Interval - 1

  DsData = Interval          'Save this value in the DS1302

  GOSUB DS1302IN

ENDIF

RETURN


'Generates one record of a file readable by EXCEL or NUMBERS

Record:                 'Writes the time and date and the analog values into the SD chip

SEROUT SDOut,84,2,["S 1 8",CR]              '80 ms timeout for missing bytes

SERIN SDIn,84,1000,closerecord,[Char]       'Wait for the ">" prompt

HIGH DS1302CE                               'Making a daily filename

SHIFTOUT  DS1302IO,CLK,0,[ReadMonthAddr\8]

SHIFTIN  DS1302IO,CLK,1,[DECdata\8]

LOW DS1302CE

HIGH DS1302CE

SHIFTOUT  DS1302IO,CLK,0,[ReadDateAddr\8]

SHIFTIN  DS1302IO,CLK,1,[rem\8]

LOW DS1302CE

'Filenames for this device must start with a "/",names require 8.3 format, alpha-numeric only

'Files are created if non-existant, but directories must exist, separate with a "/"

SEROUT SDOut,84,["O 1 A /M",(MonTens + "0"),(MonUnits + "0"),(DayTens + "0"),(DayUnits + "0"),".CSV",CR]

SERIN SDIn,84,1000,closerecord,[Char]   'Wait for the ">" prompt, can be a long time for an open

IF Char <> ">" THEN closerecord         'An error message is returned, probably no SD chip inserted

DecData = RecordString                  'Defines the date and time fields

Iter = 0

DO

  READ (DECData + Iter),DSAddr

  IF DSAddr = 0 THEN EndDateTime

  IF DSAddr > $80 THEN  'This is an address in the DS1302, get the value from the RTC

      HIGH DS1302CE

      SHIFTOUT  DS1302IO,CLK,0,[DSAddr\8]

      SHIFTIN  DS1302IO,CLK,1,[DSdata\8]

      LOW DS1302CE

      upperrem = Tens + "0"

      lowerrem = Units + "0"

      SEROUT SDOut,84,["W 1 2",CR,upperrem,lowerrem]

      SERIN SDIn,84,1000,closerecord,[Char]        'Wait for the ">" prompt, no need to check it

  ELSE

      SEROUT SDOut,84,["W 1 1",CR,DSAddr]          'An ASCII character ("/",":" or ",")

      SERIN SDIn,84,1000,closerecord,[Char]        'Wait for the ">" prompt

  ENDIF

  Iter = Iter+1

LOOP

EndDateTime:

'It's better to get the count right, if not, error on the high side and timeout

SEROUT SDOut,84,["W 1 29",CR,DEC4 Current,",",DEC4 Voltage]

RecDig = 1

GOSUB DispInputs

RecDig = 0

SEROUT SDOut,84,[CR,LF]

SERIN SDIn,84,1000,closerecord,[Char]        'Wait for the ">" prompt

NoPrompt = 0

GOSUB SoundBeep

GOTO RecOK

closerecord:

NoPrompt = 1

RecOK:

SEROUT SDOut,84,["C 1",CR]                   'Close the file

DoneFlag = 1

RETURN


'Subroutines for the analog inputs


'Measures the current input and converts for display or recording according to the range

MeasureAmp:

READ AmpParams + 2 + (5*Range),Div        'The multiplier for this range, less 1

SelPort = ADCICE

DecData = 0

FOR Char = 0 TO Div                       'Multiplication is performed by adding readings

  HIGH En138                              'Enable the TLC1549 Serial 10-bit ADC

  rem = 0

  SHIFTIN INPort,CLK,MSBPRE,[rem\10]

  LOW En138

  Decdata = rem + Decdata - ampoffset     'Accumulate readings and compensate for zero error

NEXT

IF SignBit = 1 THEN                       'Avoids confusing display for zero input volts

  DecData = 0

ENDIF

READ AmpParams + 3 +(5*range),Div         'The power of 2 to divide the accumulated value

Decdata = Decdata >> Div                  'Divide by 2 to the power div

Current = Decdata                         'Save for recording and display

RETURN


'Display the full-scale current value and set the range according to the external shunt

DispRange:

'First display the current full-scale value

READ AmpParams + 1 + (5*Range),DispMode  'How to display the full-scale value

READ AmpParams + (5*range),DECData       'The full-scale value

IF Decdata < 10 THEN

  Decdata = 1000 * DECdata

ENDIF

GOSUB DispDec

Decdata = MAString

GOSUB DispBCDString

'Adjust the value as required by the external shunt resistor

DsAddr = DSRangeAddr

IF (UPSW = 0) AND (Range < MaxRange) THEN

  Range = Range + 1

  PAUSE 500

  DsData = Range             'Save this value in the DS1302

  GOSUB DS1302IN

ENDIF

IF (DNSW = 0) AND (Range > 0) THEN

  Range = Range - 1

  PAUSE 500

  DsData = Range             'Save this value in the DS1302

  GOSUB DS1302IN

ENDIF

RETURN


MeasureVolt:                 'Measures and stores the voltage input

SelPort = ADCVCE

DecData = 0

FOR Iter = 0 TO 1            'Multiply by 2 the 10-bit value by summing 2 readings

  HIGH En138                 'Enable the TLC1549 Serial 10-bit ADC

  rem = 0

  SHIFTIN INPort,CLK,MSBPRE,[rem\10]

  LOW En138

  Decdata = rem + Decdata - voltoffset

NEXT

IF SignBit = 1 THEN            'Avoids confusing display for zero input volts

  DecData = 0

ENDIF

Voltage = Decdata              'Save for recording and display

RETURN