Version 17 of Serial Port

Updated 2008-09-16 21:22:49 by Twylite

More Serial Port Samples and "How to read the serial port, and display in hexadezimal"


Open the serial port using open (port names are platform-dependent.)

Configure useful comms stuff with fconfigure.

Use fileevent to get readable/writable callbacks.

And use close when you are done.

IOW, pretty much as you would hope! :^)


Here is a simple example... The send_exp stuff is pretty generic and should work for any channel that supports non- blocking I/O.

 # simple serial port example to send AT to modem and 
 # wait for OK response in a fixed amount of time.  At the
 # bottom is a simple loop to do this 20x to check serial
 # handler reliability...
 #
 # Works well on Tcl 8.0 and up on Unix (Solaris/NT), poorly on 
 # the tclsh included with Tcl 8.1.1 on NT, but pretty well on 
 # the wish included with same.
 #
 # NOTE may need to set comPort appropriately for your
 # platform.  Must have a modem configured to respond
 # with "OK" to "AT" commands.
 #
 switch $tcl_platform(os) {
     {Linux}            {set comPort /dev/modem}
     {SunOS}            {set comPort /dev/cua/a}
     {Windows NT}       {set comPort COM2:}
     default            {error "Must configure comPort"}
 }
 set waitSecs 2
 set nTries   20

 #
 # A cheap version of expect.
 #
 # Set up a event-driven I/O reader on the channel, output the
 # string, and wait some number of seconds for the result.
 #
 # @param fh
 #        a channel opened in non-blocking mode for I/O
 #        with buffering turned off.
 #
 # @param outstr
 #        a string to send to the output channel -- note: end-
 #        of-line characters must be included in this string,
 #        if desired.
 #
 # @param regexp
 #        regular expression to match in the incoming data.
 #
 # @param seconds
 #        number of seconds to wait for above match
 #
 # @throws error
 #        if eof is detected on the channel while waiting
 #
 # @returns int
 #        1 if a match is found, 0 otherwise.
 #
 proc send_expect {fh outstr regexp seconds} {
     global send_exp

     # make sure global vars are initialized properly
     set send_exp($fh.matched)        0
     if {![info exists send_exp($fh.buffer)]} {
         set send_exp($fh.buffer) {}
     }

     # set up our Read handler before outputting the string.
     if {![info exists send_exp($fh.setReader)]} {
         fileevent $fh readable [list private_send_exp_reader \
                 $fh $regexp]
         set send_exp($fh.setReader) 1
     }

     # output the string to send
     puts -nonewline $fh $outstr
     flush $fh

     # set up a timer so that we wait a limited amt of seconds
     set afterId [after [expr {$seconds*1000}] \
             [list set send_exp($fh.matched) 0]]
     vwait send_exp($fh.matched)
     set matched $send_exp($fh.matched)
     unset send_exp($fh.matched)
     catch [list after cancel $afterId]

     # If we got an eof, then throw an error
     if {$matched < 0} {
         error "Channel EOF while waiting for data"
         return 0
     }
     return $matched
 }

 #
 # PRIVATE channel read event handler for send_expect.  Should
 # not be called by user.
 #
 proc private_send_exp_reader {fh regexp} { 
     global send_exp

     if {[eof $fh]} {
         close $fh
         set send_exp($fh.matched) -1
         return
     }
     append send_exp($fh.buffer) [read $fh]
     if {[regexp $regexp $send_exp($fh.buffer)]} {
         set send_exp($fh.matched) 1
     }
 }

 #
 # Return the current contents of the send_expect buffer
 #
 # @param fh
 #        channel identifier that was used with send_expect
 #
 # @returns string
 #        the current contents of the buffer for the channel
 #
 proc send_exp_getbuf {fh} {
     global send_exp
     return $send_exp($fh.buffer)
 }

 #
 # Reset the send_expect buffer, returning its contents
 #
 # @param fh
 #        channel identifier that was used with send_expect
 #
 # @returns string
 #        the current contents of the buffer for the channel
 #
 proc send_exp_resetbuf {fh} {
     global send_exp 

     set buf $send_exp($fh.buffer)
     set send_exp($fh.buffer) {}
     return $buf
 }

 #
 # Close out a send_expect session, closing I/O event handler
 #
 # @param fh
 #        channel identifier that was used with send_expect
 #
 # @returns
 #        the channel identifier passed as the fh parameter
 #
 proc send_exp_end {fh} {
     global send_exp

     fileevent $fh readable {}
     foreach v [array names send_exp $fh.*] {
         catch [list unset send_exp($v)]
     }
     return $fh
 }


 ##
 ## MAIN
 ##
 set fh [open $comPort RDWR]
 fconfigure $fh -blocking 0 -buffering none \
         -mode 9600,n,8,1 -translation binary -eofchar {}

 # Loop nTries times, sending AT to modem and expecting OK.
 set nMatches 0
 for {set i 0} {$i < $nTries} {incr i} {
     if {[send_expect $fh "AT\r" "OK" $waitSecs]} {
         incr nMatches
         regsub -all "\r" [send_exp_getbuf $fh] {\r} buf
         regsub -all "\n" $buf {\n} buf
         puts "GOT MATCH: <$buf>"
     } else {
         puts "NO MATCH IN $waitSecs SECONDS"
     }
     send_exp_resetbuf $fh
 }
 send_exp_end $fh
 close $fh
 puts "Matched $nMatches/$nTries\
         ([expr 100.0*$nMatches/$nTries]%)"

D. J. Hagberg mailto:[email protected]


[Did TCL-DP include special serial-line capabilities?]


RJM: Via the serial communication resources, one can also communicate via USB. However, this will surely work with one specific USB peripheral chip, namely from FTDI.

Resource: http://www.ftdichip.com/FTDriver.htm They provide a royalty-free USB driver. With Tcl apps the virtual com port driver can be used.

Advantages over RS-232:

  • USB protocol handles transmission errors, so protocol free communication is possible
  • high speed

I myself created a project with Tcl and USB: http://www.msp430web.de/msp430-usb.htm (note that downloadable software resources here may need an update).

MS 2005-01-09 If you want to use the ftdi-chip with a Linux system you can also use the RS-232-emulation of the Linux driver. The USB chip can then be accessed as if it were an RS-232 chip. You can look at http://www.enertex.de/linux/ftdi for some basic code examples.


Several Tcl-indifferent products sniff or otherwise help understand serial-line traffic, including Sysinternals' Portmon [L1 ], ...


Serial lines have many characteristics known to Tcl. fconfigure is their usual interface. Typical options include -ttycontrol, -ttystatus, -timeout, and more.

[Explain more.]


Twylite Note that on Windows you can use the device aliases "COM1" .. "COM9" (the trailing colon is not necessary), but to access higher numbered COM ports (quite common when using USB-to-serial converters or Bluetooth dongles) you must use the Win32 device name e.g. {\\.\COM14} .


Of related interest: Parallel port.