copyright 2005 TAPR http://tapr.org/kits_tuc52.html TBAS52-TUC52 DATA LOGGING PROGRAM SKELETON Included below is a TBAS52 data logging program developed by Dan Karmann (dankarmann@lucent.com) that can be used as a skeleton for a more extensive data logging program. Hopefully this is somewhat like you had in mind when you asked for something to log data into TUC52 RAM. The program, in a nutshell, has a simple command-driven interface that allows the user to log timestamped data to RAM at fixed intervals (1 second to 18.2 hours - currently 1 second), dump the space-delimited formatted data out to the serial port, and clear the data log. As a skeleton program, this program consists of several generic modules (subroutines), some initial customizable configuration parameters/ variables, and customizable subroutines that actually accumulate the data to be logged. The program also makes use of several of the TBAS52 enhancements (over BASIC-52) and demonstrates possible usage of some of the "post-assembly patch table" values. The data that is logged at each interval is considered a record and consists of a timestamp (in seconds since Jan. 1, 1996) and a customizable number of data values. To set the number of data values accumulated at each interval, the variable "lgitem" in line 50 should be modified to indicate how many data items are in each record (the example is 2). There are 4 common log configurations that could be used for this program: 1) The program is running in low XRAM (below 4000h) and the data is stored in high XRAM (above 4000h) 2) The program is running in low XRAM (below 4000h) and the data is stored in BBRAM (starting at 8000h) 3) The program is running in low BBRAM and the data is stored in high BBRAM (above any stored BASIC programs) 4) The program is running in low BBRAM and the data is stored in high XRAM (above 4000h) The boundaries of the RAM where the data is to be logged to is set by two variables ("lgstrt" and "lgstp") in lines 70 and 80. These two lines should be modified as shown below for the desired configuration: 1) Program = low XRAM, Log = XRAM above MTOP 70 lgstrt = MTOP + 1 : REM Start of log memory 80 lgstp = CBY(2006H) * 256 + CBY(2007H) : REM End of log (MTOP_MAX) 2) Program = low XRAM, Log = BBRAM 70 lgstrt = 8000h : REM Start of log memory 80 lgstp = CBY(2008H) * 256 + CBY(2009H) : REM End of log (BBRAM_LIMIT) 3) Program = low BBRAM, Log = BBRAM above BBRAM Program(s) 70 GOSUB 13000 : POP lgstrt : REM Start of log (after BBRAM programs) 80 lgstp = CBY(2008H) * 256 + CBY(2009H) : REM End of log (BBRAM_LIMIT) 4) Program = low BBRAM, Log = XRAM above MTOP 70 lgstrt = MTOP + 1 : REM Start of log memory 80 lgstp = CBY(2006H) * 256 + CBY(2007H) : REM End of log (MTOP_MAX) In configuration 1, the RAM log buffer is from just above MTOP to the MTOP_MAX "post-assembly patch table" value (currently 7EFFh). In configuration 2, the RAM log buffer is from the start of the BBRAM (8000h) to the BBRAM_LIMIT "post-assembly patch table" value (currently CFFFh). In configuration 3, the RAM log buffer is from just above the last BASIC program in BBRAM to the BBRAM_LIMIT "post-assembly patch table" value (currently CFFFh). In configuration 4, the RAM log buffer is from just above MTOP to the MTOP_MAX "post-assembly patch table" value (currently 7EFFh). The actual capture and storage of the data items is in the TIMER interrupt subroutine at line 1100. Lines 1130 to 1150 need to be modified to get the actual data into the "item" variable. The time interval is currently hard-coded at 1 second in lines 1030 and 1230. -------- The following is a tutorial on the detailed operation of the program: The use of global variables for parameter passing between routines is kept to a minimum. Instead, the BASIC "argument stack" is used for most information transfer. Lines 10 through 95 are the initialization. Most of the initialization has been described above except for lines 90 and 95 which configure the 82C55 driving the LED on the TUC-52 board. This LED is used as an indicator (assuming a jumper is installed on header H23) when logging is in progress. Lines 100 through 160 display the command menu that tells the user the available commands and the RAM log buffer address limits. Lines 200 through 260 is the command parser and task dispenser that calls the appropriate subroutines for each command. Lines 1000 through 1080 are the subroutine for the command to start logging data. In line 1020, the system TIME variable is cleared and the BASIC timer is started (the DBIT instruction is the enhanced feature of TBAS52 -- the CLOCK1 command is not used but is enabled by default). In line 1030, the timer is configured to interrupt in 1 second and execute the service routine at line 1100. In lines 1040 and 1050, while logging data, the program looks for any character from the user to abort the logging operation. Also checked is variable "lgfull" to determine if the RAM log is full. If so, the logging operation is terminated. When the logging is aborted by the user or a full log occurs, line 1060 stops the BASIC timer while allowing the frequency and pulse counter and state change detector to continue to operate. If the log is full, the user is notified by line 1070. The routine starting at line 1100 is the place where the data capture and logging occurs. Lines 1120 through 1170 capture each data item in the record one at a time and put its value on the argument stack. Currently, for now, at line 1140, a random value is used as the data to be logged. In a real data logging application, the "item" variable would be assigned the actual data to be logged. After all the data values in each record are captured, line 1180 calls a subroutine (11000) that gets a timestamp from the clock/calendar device and puts that on the argument stack. Then line 1190 calls a subroutine (12000) that takes all of the values off the argument stack and stores them in the RAM log. Line 1200 gets the status return value from the logging operation and terminates the logging if the log is full. Lines 1210 and 1220 provide an indication visually on the LED and on the attached PC/terminal that the logging is in progress. The LED toggles state as each record is logged. A period '.' is output to the serial port as each record is logged. Finally, line 1230 reconfigures the timer for the next interval (currently 1 second) for logging to occur. Lines 2000 through 2160 are the subroutine for the command to dump the log of data to the serial port in a space-delimited format (there are spaces between each item value in a record and a separate line for each record). Lines 2040 through 2060 get the timestamp from the log (stored as a 32-bit value of the seconds since Jan. 1, 1996) and split it into hours and seconds values and send them to the serial port. Lines 2070 through 2120 get each item from a log record from the log and format them and send them to the serial port. Note the use of the DBY instruction in line 2090. TBAS52 (and BASIC-52) has a feature/quirk in that when sending output to the serial port, any values after column 72 are put onto a new line. This would mess up the record and the data would not be able to be properly imported into another application (i.e. spreadsheet or database). Therefore, to bypass this behavior, line 2090 resets the internal column counter before each logged item is sent to the serial port. Line 2140 is used to abort the dumping of the RAM log if the user sends any character. Lines 3000 through 3030 are the subroutine for the command to clear the log of data in the RAM. Actually, the data itself is not cleared, only the counter of the number of stored records is cleared. Also, the log status variable ("lgfull") is also cleared. The subroutine at lines 10000 to 10040 is the I2C error handler that is called when errors occur when communicating over the I2C bus (the clock/ calendar device in this program). This subroutine outputs a message of the error type and returns. Lines 11000 through 11060 are the subroutine to get the timestamp and put its value onto the argument stack. Two CALLs to the clock/calendar device are used to generate the timestamp. The timestamp is actually the elapsed time since a reference date of Jan. 1, 1996. To get this value, the first CALL obtains the days since Jan. 1, 1996 and the second call obtains the number of seconds since the beginning of the current day. These two values are combined and put onto the argument stack. Lines 12000 through 12130 are the subroutine take the timestamped data off the argument stack and put it into the RAM log. The format of the information stored in the RAM log is the timestamp is stored as a 32-bit value and each data item is stored as a 6-byte BASIC floating point value. Line 12020 verifies that there is enough room in the log for the data. Even if the log is full, this routine must still remove the data off the argument stack. Lines 12030 through 12070 get the timestamp off the argument stack and convert it to a 32-bit value and store it in the RAM log if the log is not full. If the log is full, the timestamp value is discarded. Lines 12080 through 12100 remove the data values off the argument stack and put them into the RAM log if the log is not full. If the log is full, these data values are also discarded. Also, if the log is not full, the record counter variable "lgcnt" is incremented in line 12110 to track the amount of data in the RAM log. On line 12120, the log status is put onto the argument stack to be returned to the calling routine. This value is set to a non-zero value if the log is full. The subroutine at lines 13000 through 13120 is not needed for the logging operation, but is included in order to determine where to store data in the BBRAM above any existing BASIC programs already stored in BBRAM. It works by walking through a BASIC program a line at a time until it sees that there are no more programs or it reaches the "post-assembly patch table" value of BBRAM_LIMIT (currently CFFFh). Line 13030 checks for an indication that a BASIC program is actually stored in BBRAM. Line 13060 checks for the end of a BASIC program and line 13070 checks for another BASIC program start. Line 13110 puts the location available to start the RAM log onto the argument stack. This location is one of the following: - The address of the first byte after the end of the last BASIC program in BBRAM. - The address of the beginning of the BBRAM (8000h) if no BASIC programs are stored in BBRAM. - The address of the beginning of the BBRAM (8000h) if the BASIC program(s) in BBRAM appear to be corrupted (could not find a valid end before the address in BBRAM_LIMIT). ------------------ FILE: DATALOG.BAS 10 REM Skeleton Data Logger Program for TUC-52 20 REM - Logs TimeStamped Data in RAM 30 REM - Dumps Logged Data Records in Space-Delimited Format 40 lgcnt = 0 : REM Running counter of records in log (0 = empty log) 50 lgitem = 2 : REM Number of data items in each log record 60 recsize = 4 + (lgitem * 6) : REM Size of log record (timestamp + items) REM Set the beginning and end of the RAM data log buffer 70 lgstrt = MTOP + 1 : REM Start of log memory 80 lgstp = CBY(2006H) * 256 + CBY(2007H) : REM End of log memory (MTOP_MAX) 90 aport = 0F8FCh : ctrl = 0F8FFh : REM IC13 (82C55) addresses 95 led = 0 : XBY(ctrl) = 80h : REM Make IC13 Port A all outputs 100 PRINT " TUC-52 Data Logger" 110 PRINT "---------------------------------------" 120 PRINT " 1) Log Data into RAM", 130 PH0. " (", lgstrt, " -", lgstp, " )" 140 PRINT " 2) Dump Logged Data - Space Delimited" 150 PRINT " 3) Clear Logged Data" 160 PRINT "Input Selection: ", 200 DO : x = GET : WHILE x = 0 : REM Wait for keypress 210 IF x < 31h PRINT : GOTO 100 : REM Verify keypress 220 IF x > 33h PRINT : GOTO 100 230 PRINT CHR(x) : PRINT : x = x - 31h 240 REM Item: 1 2 3 250 ON x GOSUB 1000,2000,3000 : REM Process command 260 PRINT: GOTO 160 1000 REM 1) Log Data into RAM (Timer driven) 1010 PRINT "Logging Data - Press any key to stop" 1020 TIME = 0 : t = TIME : DBIT(29h,7) = 1 : REM Init and Start BASIC Timer 1030 ONTIME t + 1, 1100 : REM Set logging timer to interrupt in 1 second 1040 DO : z = GET : REM Abort logging if char received 1050 WHILE (z.OR.lgfull) = 0 : REM Otherwise, loop unless log is full 1060 DBIT(29h,7) = 0 : REM Stop BASIC Timer, letting freq/pulse cntr run 1070 PRINT : IF lgfull <> 0 PRINT "Log is full!" 1080 RETURN 1100 REM Timer Interrupt for Logging Data 1110 t = TIME : REM Save current time 1120 FOR i = 1 TO lgitem : REM All data items in each log record 1130 REM ********************************************************* 1140 item = RND * 100 : REM Get the data to be logged here ... 1150 REM ********************************************************* 1160 PUSH item : REM put data item onto argument stack 1170 NEXT i 1180 GOSUB 11000 : REM Get timestamp onto argument stack 1190 GOSUB 12000 : REM Put timestamped data into RAM data log 1200 POP lgfull : IF lgfull <> 0 GOTO 1240 : REM Quit if log is full 1210 XBY(aport) = led : led = NOT(led).AND.0FFh : REM Visual indicator 1220 PRINT ".", : REM Indicate something happening 1230 ONTIME t + 1, 1100 : REM Set logging timer to interrupt in 1 second 1240 RETI : REM Timer interrupt completed 2000 REM 2) Dump Logged Data - Space Delimited 2010 IF lgcnt < 1 THEN RETURN : REM Check for logged data 2020 FOR j = 0 TO lgcnt - 1 : REM All stored records 2030 currloc = lgstrt + (j * recsize) 2040 ts = (XBYW(currloc) * 65536) + XBYW(currloc + 2) : REM Get timestamp 2050 hrs = INT(ts / 3600) : secs = INT(ts - (hrs * 3600)) 2060 PRINT hrs, TAB(10), secs, TAB(20), : REM Output timestamp 2070 FOR i = 1 TO lgitem : REM All items in each log record 2080 LD@(currloc + 3 + (i * 6)) : POP item : REM Get logged data item 2090 DBY(16h) = 0 : REM **BUG-FIX** Stop auto-CR/LF at column 72 2100 PRINT item, : REM Output data item 2110 IF i <> lgitem PRINT TAB(15), : REM Output space delimiter 2120 NEXT i 2130 PRINT : REM New line 2140 x = GET : IF x <> 0 j = lgcnt : REM Abort dump if char received 2150 NEXT j 2160 RETURN 3000 REM 3) Clear Logged Data 3010 lgcnt = 0 : REM Indicate log is cleared by zeroing its record counter 3020 lgfull = 0 : REM Clear full indicator 3030 RETURN : REM The data is not actually cleared! 10000 REM I2C Error Handler 10010 IF rslt = 1 THEN PRINT "I2C No ACKnowledge Error" : RETURN 10020 IF rslt = 2 THEN PRINT "I2C Bus Fault Error" : RETURN 10030 PRINT "Unknown Error:", rslt 10040 RETURN 11000 REM Put current timestamp (seconds since 1/1/1996) onto argument stack 11010 CALL 210EH, days, rslt : REM Get days since Jan. 1, 1996 11020 IF rslt <> 0 GOTO 10000 11030 CALL 2100H, secs, rslt : REM Get seconds since midnight 11040 IF rslt <> 0 GOTO 10000 11050 PUSH days * 24 * 3600 + secs : REM Save seconds since Jan. 1, 1996 11060 RETURN 12000 REM Put timestamped data (on argument stack) into RAM data log 12010 rslt = 0 : currloc = lgstrt + (lgcnt * recsize) 12020 IF currloc > (lgstp - 4 - (lgitem * 6)) THEN rslt = 1 : REM Check room 12030 POP ts : REM Get timestamp 12040 IF rslt <> 0 GOTO 12080 : REM Log full, so don't save timestamp 12050 XBYW(currloc) = INT(ts / 65536) : REM Store timestamp hi word 12060 ts = ts - (INT(ts / 65536) * 65536) 12070 XBYW(currloc + 2) = ts : REM Store timestamp lo word 12080 FOR i = lgitem TO 1 STEP -1 12090 IF rslt <> 0 POP dt ELSE ST@(currloc + 3 + (i * 6)) : REM Store data 12100 NEXT i 12110 IF rslt = 0 lgcnt = lgcnt + 1 : REM Bump log record counter 12120 PUSH rslt : REM Return log status on argument stack (full = non-zero) 12130 RETURN 13000 REM Determine end of BASIC program(s) in BBRAM 13010 BBRMSTRT = 8000h : BBRMPGM = 8011h 13020 z = BBRMSTRT : x = BBRMPGM : REM BBRAM RAM and program start 13030 IF XBY(x - 1) <> 55h GOTO 13110 : REM Check for BBRAM program 13040 y = CBY(2008H) * 256 + CBY(2009H) : REM End of avail BBRAM (BBRAM_LIMIT) 13050 DO 13060 IF XBY(x) <> 1 THEN x = x + XBY(x) : GOTO 13090 : REM Next line 13070 IF XBY(x + 1) = 55h THEN x = x + 2 : GOTO 13090 : REM Next program 13080 z = x + 1 : x = y : REM End of program(s) 13090 WHILE x < y 13100 IF z = BBRMSTRT THEN z = y : REM BBRAM end reached without program end 13110 PUSH z : REM Set return value on argument stack 13120 RETURN ---ooOoo---