(Feb 6, '05).
Introduction
This application note discusses Serial RS232 communication between a PC running Windows and a TM #125 temperature module. In fact, this could be extended to virtually any of my temperature and control modules, or indeed to any processor. Typical applications include measurement and perhaps in the case of a thermostat or alarm, acting on the measurement, performing calculations and logging data.
I opted to develop this material using
A demo copy of Liberty Basic is free but there is an annoying time delay each time you desire to compile. There is a $29 version which removes this delay and a $49 version which provides for making executables. Factors in selecting Liberty Basic were ease of use in implementing the serial communications, it widely used and well supported and relatively inexpensive.
I will confess that my knowledge of programming in Basic is limited and it seems as if each day I make some new discovery. However, if I wait to post material I am proud to post, I fear nothing will ever be posted. So, please take this as a possible starting point for your own creative being.
Please note that Liberty Basic is one approach. I make no claim that it is better or worse than any other approach. There are simply so many platforms that I decided to simply implement some samples using Liberty. I decided some time ago that I could not be good at all things and decided to strive to be good in developing the interfacing processors. Programming on the PC side is not one of my peaks of excellence.
The following Liberty Basic programs were tested using Windows 98SE.
Format of the TM #125 Measurement Data
Recall that when the TM #125 unit receives any character, it performs a temperature measurement on each Dallas 1-W sensor found on the single run. A sample;
00 21.43 106A0B4C 00080003 01 21.37 109DD748 00080085 02 21.87 225BFC06 000000CD >where the first field is a sequentially assigned number, the second is the temperature and the third and fourth are the Dallas assigned serial number.
Note that the prompt > may be interpretted as the end of the sequence.
Program TM125_1.Bas.
This program initiates a sequence of temperature by the TM #125 each minute. The results are fetched and displayed and also logged to a file. When the user enters any character in the text window, the measurement is halted and the content of the log file is displayed to the window text box.
In the following, I first attempt to discuss each portion of the program. This is followed by the program in its entirety.
Text Box
I found that the only way I could interrupt the process to stop logging and display the logged results was to use the INKEY$ function which required a text box. My work is adapted from an example in the Liberty Basic archive.
' define a box nomainwin WindowWidth = 400 WindowHeight = 300 texteditor #window.te, 0, 0, 391, 254 'The handle for our texteditor is #window.te graphicbox #window.gb, 800, 1, 10, 10 open "kb" for window as #window 'The handle for our window is #window print #window.gb, "when characterInput [getChar]" 'When the user presses a key go to [getChar] print #window, "trapclose [quit]" 'When the user closes our terminal window, go to [quit] print #window.te, "!autoresize"; 'Tell the texteditor to resize with the terminal window print #window, "font courier_new 9";
Note particularly that when a character in input, the program branches to [getChar].
Data is then written to the text box;
print #window.te, DataLine$ ' display the string
The character input condition is checked;
print #window.gb, "setfocus" ' I am a bit confused with this scan ' used for check if activity from the keyboard
When a character has been input, the program branches to [getChar] where the character is read and the appropriate action is taken. Note that this could any number of different actions based on the key input. In my case, I directed the program to [display] when any key was input.
[getChar] 'Whenever the user presses a key, we go here to process it. c$ = Inkey$ ' print #window.te, "at getChar" goto [display]
Com Port
The COM Port is opened;
open "com2:9600,n,8,1, cs0, ds0" for random as #commhandle
I spent a considerable amount of time trying to deal with a COM port error such as the COM port already being open. I don't think I was successful.
Reading from the COM port is a matter of determining if there are characters in the buffer and if so, fetching each character in turn;
if lof(#commhandle) <>0 then c$ = input$(#commhandle, 1)Writing to the COM port is simply;
print #commhandle, "!" ' send any characterNote that with the TM #125, any character initiates a measurement sequence.
Timing.
Liberty Basic permits one to read the time and date off the system clock and I used this too implement the periodic measurements and also to implement a pause.
Commands include time$("milliseconds") and time$("seconds"). The time functions return to number of ms or secs which have elapsed since midnight. Thus, a timeout is a matter of adding the desired interval to a base and continually reading the current time to determine if the current time is greater than the timeout. However, if in adding the interval, the timeout is greater than the number of ms (86,400,000) or secs (8,6400) in a day, the timeout will never be reached. If spent quite a bit of time fiddling with this and ultimately settled on using the date$("days") function as well. Thus, each base time consists of a time and day. If, in adding the interval, the timeout exceeds the number of ms or seconds in a day, the timeout time is adjusted and the timeout date is the next day.
[again] TimeOutTime = TimeOutTime + 60 ' every minute if (TimeOutTime >= 86400000) then TimeOutDate = TimeOutDate + 1 TimeOutTime = TimeOutTime - 86400000 end if ... ...The program then does its task and then loops until both the current time and current date are greater than the time outs.
do print #window.gb, "setfocus" scan ' used for check if activity from the keyboard CurrentDate = date$("days") CurrentTime = time$("seconds") loop until (CurrentTime >= TimeOutTime) AND (CurrentDate >= TimeOutDate) goto [again]Function GetLine$
This function waits up to three seconds to fetch a string from the Com port. If this timeout should occur, the StatusFlag is set to -30000 and returned by reference to the calling process. If the fetched character is the prompt '>', this is returned and the StatusFlag is set to the number of characters in the string. In this case, one. If a fetched character is ASCII 10 (line feed), it is ignored. If it is ASCII 13 (CR), the string is returned with the Status Flag equal to the number of characters. If the fetched character is any other character, it appended to the DataLine$ and the number of characters is incremented.
function GetLine$(ByRef StatusFlag) TimeOutTime = time$("milliseconds") TimeOutDate = date$("days") TimeOutTime = TimeOutTime + 3000 ' three seconds if (TimeOutTime >= 86400000) then TimeOutDate = TimeOutDate + 1 TimeOutTime = TimeOutTime - 86400000 end if DataLine$ = "" StatusFlag = 0 NumChars = 0 x=0 while x=0 if lof(#commhandle) <>0 then c$ = input$(#commhandle, 1) select case case c$ = ">" DataLine$ = c$ StatusFlag = 1 ' prompt exit while case c$ = chr$(13) StatusFlag = NumChars exit while case c$ = chr$(10) ' ignore it case else DataLine$ = DataLine$ + c$ NumChars = NumChars + 1 end select else CurrentDate = date$("days") CurrentTime = time$("milliseconds") If (CurrentTime >= TimeOutTime) AND (CurrentDate >= TimeOutDate) Then StatusFlag = -30000 exit while end if end if wend GetLine$ = DataLine$ End FunctionWriting and Reading from a File.
At the beginning of the program, a file is opened for writing and immediately closed. This either creates the file, in my case "d:\data.txt", or if it already exists, it resets it. This is, it deletes whatever is in the file.
open "d:\data.txt" for output as #log ' open the file close #logData is written to the file by opening the file for appending, the data is immediately written and the file is then closed.
open "d:\data.txt" for append as #log print #log, DataLine$ ' write to file close #logThe reason for opening the file for appending, quickly writing the data and then closing it to minimize the probability of losing the file is windows should freeze, requiring a reboot or power is lost to the PC. If the file is open, all data is lost.
This might be improved further by copying the current file to a backup file, prior to opening it for appending. Thus, at the worst, one will have the backup file.
Data is read by opening the file for reading and continually testing if there is more lines in the file and if so, reading them.
open "d:\data.txt" for input as #log while eof(#log) = 0 line input #log, datain$ print #window.te, datain$ wend
The Entire Program.
' TM125_1.Bas (Liberty Basic) ' ' Illustrates an interface with temperature module TM #125 ' ' Opens a text box. Opens a logging file d:\data.txt ' ' Every minute sends a character to the TM #125, causing the TM #125 to perform ' temperature measurements on each of the Dallas temperature devices on the single run. ' This program fetches each string from the TM #125 using function GetLine$. ' ' The string is then displayed on the terminal. In addition, the file is briefly opened, the ' string is written to the file and the file is closed. ' ' This continues indefinitely, until any character is entered from the terminal. The file is ' then opened and displayed to the terminal. ' ' Illustrates developing a text box, and the use of scan to read a character from the keyboard. ' ' Useful for developing data logger applications. ' ' Uses Com2, 9600 baud. ' ' copyright, Peter H Anderson, Baltimore, MD, Feb, '05 Com = 16384 ' size of buffer ' define a box nomainwin WindowWidth = 400 WindowHeight = 300 texteditor #window.te, 0, 0, 391, 254 'The handle for our texteditor is #window.te graphicbox #window.gb, 800, 1, 10, 10 open "kb" for window as #window 'The handle for our window is #window print #window.gb, "when characterInput [getChar]" 'When the user presses a key go to [getChar] print #window, "trapclose [quit]" 'When the user closes our terminal window, go to [quit] print #window.te, "!autoresize"; 'Tell the texteditor to resize with the terminal window print #window, "font courier_new 9"; [top] oncomerror [closecomm] ' not sure if this works open "com2:9600,n,8,1, cs0, ds0" for random as #commhandle call pause 500 ' wait for unit to settle open "d:\data.txt" for output as #log ' open the file close #log TimeOutTime = time$("seconds") TimeOutDate = date$("days") [again] TimeOutTime = TimeOutTime + 60 ' every minute if (TimeOutTime >= 86400000) then TimeOutDate = TimeOutDate + 1 TimeOutTime = TimeOutTime - 86400000 end if print #commhandle, "!" ' send any character x = 0 while x = 0 ' loop until either a string is received or timeout DataLine$ = GetLine$(NumChars) ' NuChars is passed by reference select case case NumChars = -30000 print #window.te, "timeout" exit while case (NumChars = 1) print #window.te, DataLine$ exit while case (NumChars > 2) ' probably a valid string print DataLine$ open "d:\data.txt" for append as #log print #window.te, DataLine$ ' display the string print #log, DataLine$ ' and write it to the file close #log case else print "Unknown error" ' probably an overkill exit while end select wend do print #window.gb, "setfocus" scan ' used for check if activity from the keyboard CurrentDate = date$("days") CurrentTime = time$("seconds") loop until (CurrentTime >= TimeOutTime) AND (CurrentDate >= TimeOutDate) goto [again] [display] ' now open the file display the data open "d:\data.txt" for input as #log while eof(#log) = 0 line input #log, datain$ print #window.te, datain$ wend close #log [Done] Goto [Done] [getChar] 'Whenever the user presses a key, we go here to process it. c$ = Inkey$ ' print #window.te, "at getChar" goto [display] [closecomm] print "Error code: ";ComErrorNumber close #commhandle goto [top] function GetLine$(ByRef StatusFlag) TimeOutTime = time$("milliseconds") TimeOutDate = date$("days") TimeOutTime = TimeOutTime + 3000 ' three seconds if (TimeOutTime >= 86400000) then TimeOutDate = TimeOutDate + 1 TimeOutTime = TimeOutTime - 86400000 end if DataLine$ = "" StatusFlag = 0 NumChars = 0 x=0 while x=0 if lof(#commhandle) <>0 then c$ = input$(#commhandle, 1) select case case c$ = ">" DataLine$ = c$ StatusFlag = 1 ' prompt exit while case c$ = chr$(13) StatusFlag = NumChars exit while case c$ = chr$(10) ' ignore it case else DataLine$ = DataLine$ + c$ NumChars = NumChars + 1 end select else CurrentDate = date$("days") CurrentTime = time$("milliseconds") If (CurrentTime >= TimeOutTime) AND (CurrentDate >= TimeOutDate) Then StatusFlag = -30000 exit while end if end if wend GetLine$ = DataLine$ End Function sub pause mil tcurrent = time$("milliseconds") timeout = tcurrent + mil if timeout > 86400000 then ' roll over at midnight timeout = timeout - 86400000 do tcurrent = time$("milliseconds") loop until (86400000 - tcurrent) > 1000000 end if do tcurrent = time$("milliseconds") loop until (tcurrent >= timeout) end sub end
Program TM125_2.Bas
This program extends the above to illustrate fetching the date and time off the system in the form of "mm/dd/yy" and "hh:mm:ss". It also illustrates how to extract a field from a string and convert the field to a number. This might be used to determine if a temperature is in an alarm state. Function ExtractField
With the TM #125, the format of each measurement from the TM #125 is of the form;
00 24.25 106A0B4C 00080003where the fields are a sequential number, the temperature and the unique Dallas serial number (two fields). If it is desired to extract only the temperature;
Delimiter$ = " " TCelcius$ = ExtractField$(DataLine$, Delimiter$, 2)The function ExtractString searches for the second occurrence of the delimiter " " and returns the field immediately to the left of this space. This, in this example, Var$ is "00 24.25 106A0B4C 00080003". The index of the first space is found and the string is then cut such that Left$ is "00" and Var$ is "24.25 106A0B4C 00080003". The index of the next space is found and the string is again cut such that Left$ is "24.25" and Var$ is "106A0B4C 00080003". Left$ is then the extracted field which is passed by to the calling procedure.
Strings are passed by reference to a function, that is, the address of the beginning of the variable is actually passed. Thus, as Var$ is modified in the function ExtractField$, variable DataLine$ is also modified. In this program, this was not important as DataLine$ was never again used. Also, variable Delimiter$ must be declared as a variable as one cannot pass the address of the character space.
function ExtractField$(Var$, Delimiter$, NumField) for Num = 1 to NumField Index = FindDelimiter(Var$, Delimiter$) LeftStr$ = Left$(Var$, Index - 1) ' everything to the left of the space Var$ = Mid$(Var$, Index+1) ' to the right of the space If Num = NumField Then exit for End If next ExtractField$ = LeftStr$ end functionNote that TCelcius$ is a string. If one desires to monitor for an alarm condition, this must be converted to a number using the Val function.
TCelcius$ = ExtractField$(DataLine$, Delimiter$, 2) SerNum$ = ExtractField$(DataLine$, Delimiter$, 3) TCelcius = Val(TCelcius$) print #window.te, DevNum; " "; TCelcius if (SerNum$ = "109DD748" AND TCelcius > 25.0) Then print #window.te, SerNum$; " is at High Alarm" end if if (SerNum$ = "109DD748" AND TCelcius < 23.0) Then print #window.te, SerNum$; " is at Low Alarm" end if
Timing.
Program TM125_2.Bas uses a somewhat different technique for periodically performing a temperature measurement sequence in that it reads the time from the system clock in "hh:mm:ss" format, extracts the minutes field and converts it to a number. Clearly this technique will only work for an interval of minutes in the range of 1 to 59, but it may be reworked to each hour or every four hours.
T$ = time$() Delimiter$ = ":" TimeoutMinute = Val(ExtractField$(T$, Delimiter$, 2)) ' extract the minutes fieldThe interval is then added and adjusted if the result is greater than or equal to sixty.
TimeoutMinute = TimeoutMinute + 1 If TimeoutMinute >= 60 Then TimeoutMinute = TimeoutMinute - 60 End IfLater in the program, the clock is continually read, the minute field extracted and converted to a number CurrentMinute which is compared with TimeoutMinute.
do T$ = time$() Delimiter$ = ":" CurrentMinute = Val(ExtractField$(T$, Delimiter$, 2)) loop until (CurrentMinute = TimeoutMinute)The Entire Program
' TM125_2.Bas (Liberty Basic) ' ' Illustrates an interface with temperature module TM #125 ' ' Initiates a temperature measurement sequence each minute and displays the date and ' time along with the device number, temperature and serial number to the terminal. ' ' The temperature on one of the temperature sensors is evaluated as to whether it above ' or below a threshold. ' ' Uses Com2, 9600 baud. ' ' copyright, Peter H Anderson, Baltimore, MD, Feb, '05 Com = 16384 ' size of buffer ' define a box nomainwin WindowWidth = 400 WindowHeight = 300 texteditor #window.te, 0, 0, 391, 254 'The handle for our texteditor is #window.te graphicbox #window.gb, 800, 1, 10, 10 open "kb" for window as #window 'The handle for our window is #window print #window.gb, "when characterInput [getChar]" 'When the user presses a key go to [getChar] print #window, "trapclose [quit]" 'When the user closes our terminal window, go to [quit] print #window.te, "!autoresize"; 'Tell the texteditor to resize with the terminal window print #window, "font courier_new 9"; [top] oncomerror [closecomm] ' not sure if this works open "com2:9600,n,8,1, cs0, ds0" for random as #commhandle call pause 500 ' wait for unit to settle open "d:\data.txt" for output as #log ' open the file close #log T$ = time$() Delimiter$ = ":" TimeoutMinute = Val(ExtractField$(T$, Delimiter$, 2)) [again] TimeoutMinute = TimeoutMinute + 1 If TimeoutMinute >= 60 Then TimeoutMinute = TimeoutMinute - 60 End If print #commhandle, "!" ' send any character x = 0 while x = 0 ' loop until either a string is received or timeout DataLine$ = GetLine$(NumChars) ' NuChars is passed by reference select case case NumChars = -30000 print #window.te, "timeout" exit while case (NumChars = 1) print #window.te, DataLine$ exit while case (NumChars > 2) ' probably a valid string d$ = date$("mm/dd/yy") t$ = time$() open "d:\data.txt" for append as #log print #window.te, d$; " "; t$; " "; DataLine$ ' display the string print #log, d$; " "; t$; " "; DataLine$ ' and write it to the file close #log Delimiter$ = Chr$(32) DevNum$ = ExtractField$(DataLine$, Delimiter$, 1) TCelcius$ = ExtractField$(DataLine$, Delimiter$, 2) SerNum$ = ExtractField$(DataLine$, Delimiter$, 3) DevNum = Val(DevNum$) TCelcius = Val(TCelcius$) print #window.te, DevNum; " "; TCelcius if (SerNum$ = "109DD748" AND TCelcius > 25.0) Then print #window.te, SerNum$; " is at High Alarm" end if if (SerNum$ = "109DD748" AND TCelcius < 23.0) Then print #window.te, SerNum$; " is at Low Alarm" end if case else print "Unknown error" ' probably an overkill exit while end select wend do print #window.gb, "setfocus" scan ' used for check if activity from the keyboard T$ = time$() Delimiter$ = ":" CurrentMinute = Val(ExtractField$(T$, Delimiter$, 2)) loop until (CurrentMinute = TimeoutMinute) goto [again] [display] ' now open the file display the data open "d:\data.txt" for input as #log while eof(#log) = 0 line input #log, datain$ print #window.te, datain$ wend close #log [Done] Goto [Done] [getChar] 'Whenever the user presses a key, we go here to process it. c$ = Inkey$ ' print #window.te, "at getChar" goto [display] [closecomm] print "Error code: ";ComErrorNumber close #commhandle goto [top] function GetLine$(ByRef StatusFlag) TimeOutTime = time$("milliseconds") TimeOutDate = date$("days") TimeOutTime = TimeOutTime + 3000 ' three seconds if (TimeOutTime >= 86400000) then TimeOutDate = TimeOutDate + 1 TimeOutTime = TimeOutTime - 86400000 end if DataLine$ = "" StatusFlag = 0 NumChars = 0 x=0 while x=0 if lof(#commhandle) <>0 then c$ = input$(#commhandle, 1) select case case c$ = ">" DataLine$ = c$ StatusFlag = 1 ' prompt exit while case c$ = chr$(13) StatusFlag = NumChars exit while case c$ = chr$(10) ' ignore it case else DataLine$ = DataLine$ + c$ NumChars = NumChars + 1 end select else CurrentDate = date$("days") CurrentTime = time$("milliseconds") If (CurrentTime >= TimeOutTime) AND (CurrentDate >= TimeOutDate) Then StatusFlag = -30000 exit while end if end if wend GetLine$ = DataLine$ End Function function ExtractField$(Var$, Delimiter$, NumField) for Num = 1 to NumField Index = FindDelimiter(Var$, Delimiter$) LeftStr$ = Left$(Var$, Index - 1) ' everything to the left of the space Var$ = Mid$(Var$, Index+1) ' to the right of the space If Num = NumField Then exit for End If next ExtractField$ = LeftStr$ end function function FindDelimiter(Var$, Delimiter$) for n = 1 to len(Var$) ch$ = Mid$(Var$, n, 1) If ch$ = Delimiter$ then exit for End If If ch$ = Chr$(13) then exit for End If If ch$ = Chr$(10) then exit for End If next FindDelimiter = n end function sub pause mil tcurrent = time$("milliseconds") timeout = tcurrent + mil if timeout > 86400000 then ' roll over at midnight timeout = timeout - 86400000 do tcurrent = time$("milliseconds") loop until (86400000 - tcurrent) > 1000000 end if do tcurrent = time$("milliseconds") loop until (tcurrent >= timeout) end sub end