I created this page in case some people may have some questions or answers about using a serial port on Windows.
I just tried to communicate with com1 on a Windows 2000 machine. For some reason I could not receive any data using the following commands in Wish.
% proc rd_chid {chid} { set msg [gets $chid] puts $msg } % set com [open com1: r+] % fconfigure $com -mode 9600,n,8,1 -blocking 0 -translation auto -buffering line % fileevent $com readable [list rd_chid $com]
However, I was able to receive data by putting those last 3 lines into its own procedure...
% proc rd_chid {chid} { set msg [gets $chid] puts $msg } % proc open_com {} { global com set com [open com1: r+] fconfigure $com -mode 9600,n,8,1 -blocking 0 -translation auto -buffering line fileevent $com readable [list rd_chid $com] } % open_com # lots of data comes streaming :) % close $com
jg
Tcl 8.4 greatly improved serial port control across platforms. Refer to the updated fconfigure documentation for more info.
To retrieve details on input/output errors--which are, in general, frequent, when working with serial lines--interrogate
set details [fconfigure $serial_handle -lasterror]
8.3.4 introduced this capability.
How can I connect to a com port higher than 9? (I am using a terminal server, and its driver creates com ports upto 16 or so.) Would appreciate answer by mail - [email protected]
Thanks
25Aug2003 PS: You need to use the 'generic' name (I think it is actually the UNC path) of the com port. Use the file name \\.\com13 to open 'com13:' Mind you, windows wants to see those all those \ chars, so use [open \\\\.\\com13 r+]. I have not tested this myself... E_NO_ACCESS_TO_BIG_SERIAL_CARD. But it does work :)
AMG: You can use {brace quoting} to eliminate the need for doubled \backslashes.
20111014 tested with 8.5.9: open //./com13 definitely works (or \\\\.\\com13), shorter variants don't. Interesting detail: if you are using a tclkit app, you can open com5 etc only if you have the current directory NOT within tclkit VFS, but outside of this (com1...4 always open regardless current dir). But using the absolute path as proposed above, VFS or normal current directory does not matter.
The following code will enumerate the serial devices on a windows XP machine - including USB devices.
package require registry proc get_serial_ports { } { set serial_base "HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM" set values [ registry values $serial_base ] set result {} foreach valueName $values { lappend result [ registry get $serial_base $valueName ] } return $result }
Yep, the above works fine on XP. Try " return [lsort -dictionary $result]" to get the list sorted... -regards
ZB 20111012 - yes, it works on my Win XP - but I had the problem, that id did list also the "virtual serial ports" left by Bluetooth device (not connected). Trying to open such serial port resulted in very long pause (almost a minute), before "open" gave up.
enumerate serial devices on win2k-win7(Jee Labs version [L1 ])
# 2010-04-21 improved version, see http://talk.jeelabs.net/topic/208 # 2012-03-16 further improved, added detection for Brainboxes and ACPI # # List serial ports # # Identifies the following devices: # ftdi, acpi, usb, bbx (Brainbox) # # Resulting list: # <prefix>-<serial> COM<xy> ## proc RawListSerialPorts {} { set result {} set ccs {HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet} foreach {type prefix match} { Serenum ftdi {^FTDIBUS.*_6001.(\w+)} Serenum acpi {^ACPI.*(\w+)} usbser usb {^USB\B.*\B(.*)$} BbEnum20 bbx {^Ports\\BbUSer([0-9]*).*} } { # ignore registry access errors catch { set enum "$ccs\\Services\\$type\\Enum" set n [registry get $enum Count] for {set i 0} {$i < $n} {incr i} { set desc [registry get $enum $i] if {[regexp $match $desc - serial]} { set p [registry get "$ccs\\Enum\\$desc\\Device Parameters" PortName] # Log . {usb-$serial Port: $p\ Friendly: [registry get "$ccs\\Enum\\$desc" FriendlyName]} lappend result $prefix-$serial $p } } } } return $result }
Note that you most frequently see code something like this:
set serial [open "COM1:" "RDWR"] fconfigure $serial -mode 9600,n,8,1
where fconfigure sets the baud rate, etc. of the serial port.
ZB The above procedure didn't work at all on my Win XP.
PY As for me, I use next snippet to obtain virtual COM ports:
# List all ports as FTDIBUS and USB. Returns list of {PortName, FriendlyName, Auto?} # where Auto is flag - port for HMC6343 or not proc ls_virt {args} { set res "" foreach {type vid} {FTDIBUS VID_* USB V[iI][dD]_*} { set k0 "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\$type" catch { ;# catch case where e.g. FTDIBUS is not found set k0ch [registry keys "$k0" $vid] foreach k1 $k0ch { set found [regexp -nocase $args $k1] set k1ch "" catch {set k1ch [registry keys "$k0\\$k1"]} foreach k2 $k1ch { set Class "" catch { ;# nested catch to prevent total loop break set Class [registry get "$k0\\$k1\\$k2" Class] if {$Class == "Ports"} { # only when Class is defined, the driver is truly installed set FriendlyName [registry get "$k0\\$k1\\$k2" FriendlyName] if {$found || [regexp -nocase $args $FriendlyName]} { set PortName [registry get "$k0\\$k1\\$k2\\Device Parameters" PortName] set Service [registry get "$k0\\$k1\\$k2" Service] lappend res [list $PortName $FriendlyName $Service] } } } } } } } return $res }
RJM extended by scanning not only FTDI-based ports and case-tolerant VID/Vid string recognition as well as added robustness. Further, a result filter may be specified, used to filter according to FriendlyName substring or Pid&Vid key
Third item is a flag - is it our device (I'm looking for HMC6343 sensor, but it's possible to test instead of silabser any other driver/service type).
_abc_ - 2012-01-27 14:56:35
code to enumerate all serial ports, cross platform, tested to work well on various linuxes and windows xp and windows 7. Contributed by emiliano and tested by me on several machines.
package require platform namespace eval serial { variable platform [lindex [split [platform::generic] -] 0] } proc serial::listports {} { variable platform set result {} switch -- $platform { win32 { catch { package require registry set serial_base [join { HKEY_LOCAL_MACHINE HARDWARE DEVICEMAP SERIALCOMM} \\] set values [ registry values $serial_base ] foreach value $values { lappend result \\\\.\\[registry get $serial_base $value] } } } linux { set result [glob -nocomplain {/dev/ttyS[0-9]} {/dev/ttyUSB[0-9]} {/dev/ttyACM[0-9]}] } netbsd { set result [glob -nocomplain {/dev/tty0[0-9]} {/dev/ttyU[0-9]}] } openbsd { set result [glob -nocomplain {/dev/tty0[0-9]} {/dev/ttyU[0-9]}] } freebsd { # todo } default { # shouldn't happen } } return [lsort $result] }
aspenlogic 2014-04-30
All of the scripts above worked to locate the names of my Windows 7 (64-bit) COM ports. However, a simple [open COM2: r+] always failed. On a hunch I tried running wish with Administrator privileges. After that, open worked just fine.
HE 2014-07-13: I had the task to show the currently existing devices connected via serial interfaces and show them with their friendly name. The solution I found is a combination of the above mentioned registry keys. It provides a list of list with two values (COM port and friendly name):
proc getSerialInterfaces {} { set coms {} set res {} set key "HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM" if {[catch { # The keys only available if the driver is loaded. foreach name [registry values $key] { set value [registry get $key $name] lappend coms $value } } fault]} { # return a empty list in case no serial interface is found return $res } set key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum foreach el [registry keys $key] { foreach el1 [registry keys $key\\$el] { catch { foreach el2 [registry keys $key\\$el\\$el1] { set port [registry get "$key\\$el\\$el1\\$el2\\Device Parameters" PortName] if {[lsearch $coms $port] < 0} { continue } set friendlyName [registry get "$key\\$el\\$el1\\$el2" FriendlyName] lappend res [list $port $friendlyName] } } } } return [lsort $res] }
KL 2017-11-10 NOTE: This implementation does not work in WIN 10 can somebody provide a solution ??
JRP 2020-6-4: Here are some procs I use to find serial ports on Windows 10 using TWAPI. I use the vendor and product IDs to find specific devices.
package require twapi package require textutil proc get_windows_hwid_value {hwid delimiter} { # Return a value from a windows hardware ID # # Arguments: # hwid -- Windows hardware ID, like USB\VID_1FFB&PID_00BB&REV_0102&MI_01 # delimiter -- Substring like "VID_" or "PID_" # Prefix string will start with the value we want terminated in a & set prefix_string [lindex [textutil::splitx $hwid $delimiter] 1] set value [string trimleft [lindex [split $prefix_string &] 0] 0] return $value } proc detect_vcps {} { # Windows -- returns a dictionary of: # # (COM port): vendor_id, product_id, composite_index set vcp_dict [dict create] switch $::tcl_platform(os) { {Linux} { set nodelist "" try { # FTDI devices show up as ttyUSB* set nodelist [glob -nocomplain -directory /dev/ ttyUSB*] # Arduino devices show up as ttyACM* lappend nodelist [glob -nocomplain -directory /dev/ ttyACM*] } trap {} {message optdict} { puts $message set nodelist "" } foreach vcp [join $nodelist] { try { set udev_data [exec udevadm info --query=property --name $vcp] set id_serial "" set id_usb_interface_number "" foreach line [split $udev_data "\n"] { if {[string first "ID_SERIAL=" $line] >= 0} { set id_serial [lindex [split $line "= \n"] 1] } if {[string first "ID_USB_INTERFACE_NUM=" $line] >= 0} { set composite_index [string trimleft [lindex [split $line "= \n"] 1] 0] } if {[string first "ID_VENDOR_ID=" $line] >= 0} { set vendor_id [string trimleft [lindex [split $line "= \n"] 1] 0] } if {[string first "ID_MODEL_ID=" $line] >= 0} { set product_id [string trimleft [lindex [split $line "= \n"] 1] 0] } } } trap CHILDSTATUS {results options} { puts "oops -- $results" set status [lindex [dict get $options -errorcode] 2] continue } puts "ID_SERIAL is $id_serial" puts " Vendor ID: $vendor_id" puts " Product ID: $product_id" puts " COM port: $vcp" puts " Composite index: $composite_index" dict set vcp_dict $vcp vendor_id $vendor_id dict set vcp_dict $vcp product_id $product_id dict set vcp_dict $vcp composite_index $composite_index } } {Windows NT} { # Grab the device info set for present objects only set dfs [twapi::devinfoset -presentonly true] foreach element [twapi::devinfoset_elements $dfs] { catch {twapi::devinfoset_element_registry_property $dfs $element class} classname if {$classname eq "Ports"} { # USB/serial ports are part of the "ports" class catch {twapi::devinfoset_element_registry_property $dfs $element friendlyname} friendlyname puts "Friendlyname is $friendlyname" # Get the COMn number from the friendlyname. Friendlynames look like # # USB Serial Device (COM5) set vcp [lindex [split $friendlyname "()"] 1] catch {twapi::devinfoset_element_registry_property $dfs $element mfg} mfg puts " Manufacturer is $mfg" catch {twapi::devinfoset_element_registry_property $dfs $element hardwareid} hardwareid_list # There may be more than one entry in the hardare ID list. Just use the first one. set hwid [lindex $hardwareid_list 0] puts " Hardwareid is $hwid" set vendor_id [get_windows_hwid_value $hwid "VID_"] set product_id [get_windows_hwid_value $hwid "PID_"] set composite_index [get_windows_hwid_value $hwid "MI_"] puts " Vendor ID: $vendor_id" puts " Product ID: $product_id" puts " COM port: $vcp" puts " Composite index: $composite_index" dict set vcp_dict $vcp vendor_id $vendor_id dict set vcp_dict $vcp product_id $product_id dict set vcp_dict $vcp composite_index $composite_index } } twapi::devinfoset_close $dfs } default { # This should never happen puts "Failed to detect OS" } } return $vcp_dict } puts [detect_vcps]