This is a tag page.

Synchronize Tcl and binary package versions

John Peck
Published

Update on 2024-Apr-13 -- Removed Gitlab references

Github makes it easier for me to provide binary package releases along with my source code.

Update on 2024-Mar-23 -- Moving to Github

Development has moved to Github.

feather<em>over</em>box

The first thing I realized when I started to use tcladu was the need for convenience functions. For example, combinations of write_device and read_device should be combined into a query command for convenience. And it would be nice for those kind of commands to be in the same tcladu package. So I started to write some Tcl, and I realized I didn't really know how to manage the package version with this high-level addition.

The tcladu.so binary already provides the tcladu namespace and package version. I set this version in a makefile that also drives testing, so I'd really like the makefile/binary package version to be the source of truth. So I need to extract that package version in the convenience code. It turns out that there's a package command for this. These links are to Tcl 9.0 documentation, but my usage doesn't care.

Adding these lines

load ./tcladu.so
set version [package present tcladu]
package provide tcladu $version

...to the tcladu.tcl source file synchronizes the Tcl and binary source versions. I can then package this up with

pkg_mkIndex -verbose . tcladu.so tcladu.tcl

...using the pkg_mkIndex command. This produces the pkgIndex.tcl script distributed with the package source files:

package ifneeded tcladu 1.1.0 [list load [file join $dir tcladu.so]]\n[list source [file join $dir tcladu.tcl]]

...where you can see the combination of sourcing and loading done to make the package available. These convenience functions are coming in tcladu version 1.1.0.

Image by Freepik

Links

A Tcl interface to the ADU100 via SWIG

John Peck
Published

Tcladu is a Tcl package providing an interface to the ADU100 from Ontrak Control Systems.

Scope shot
Figure 1: ADU100 image from Ontrak's manual.

Updates

2024-Mar-23

Development has moved to Github.

2024-Apr-4

Removed references to Tcladu at Sourceforge and Gitlab.

Demonstration

Let's say you've downloaded a release binary from Github, and you have a few (two) ADU100s connected. You also need permissions to access the device, but let's say you have those.


johnpeck@darkstar:~/Downloads $ tar xzvf tcladu-1.0.0-linux-x64.tar.gz
tcladu/
tcladu/pkgIndex.tcl
tcladu/tcladu.so
johnpeck@darkstar:~/Downloads $ cd ~
johnpeck@darkstar:~ $ tclsh
% lappend auto_path ~/Downloads
/usr/share/tcltk/tcl8.6 /usr/share/tcltk ... ~/Downloads
% puts [package require tcladu]
1.0.0
% puts [tcladu::discovered_devices]
2
% puts [tcladu::serial_number 0]
B02597
% puts [tcladu::write_device 0 "SK0" 200]
0
% puts [tcladu::write_device 0 "RPK0" 200]
0
% puts [tcladu::read_device 0 8 200]
0 1
% puts [tcladu::write_device 0 "RK0" 200]
0
% puts [tcladu::write_device 0 "RPK0" 200]
0
% puts [tcladu::read_device 0 8 200]
0 0

What did we just do?

Unpacked the TGZ

The package is just two files: pkgIndex.tcl, used by Tcl's package procedure, and tcladu.so, a binary produced from some c code.

Appended the package to Tcl's auto_path

The auto_path list tells Tcl where to look for packages.

Required the package

This both loads procedures into the tcladu namespace and initializes libusb.

Populated the connected device database

The discovered_devices command will populate a device database with things like device handles and serial numbers. This must be called before writing to or reading from devices.

Queried the device database for device 0

The serial_number command doesn't do anything with connected hardware -- it just returns a serial number populated by discovered_devices.

Sent the command to set/close the ADU100's relay

The write_device command takes a device index instead of some kind of handle to identify the targeted device. It then takes an ASCII command that you can find in the ADU100 manual to manipulate the hardware relay. The last argument is a timeout for libusb (in milliseconds), which will become more interesting when we get into reading from the hardware.

Sent the command to read the relay status

Reading the relay status starts with telling the ADU100 to read the status. It will prepare the result to be read by the next libusb read. The return value for the RPK0 command is just a success code -- not the relay status.

Read from the ADU100

The read_device command takes a device index, followed by the number of bytes we want to read. This payload size is a placeholder for now, although it has to be 8 bytes or larger. I want to keep it to handle larger payloads on other Ontrak devices this might support in the future.

The final argument is the familiar ms timeout. Libusb will throw a timeout error if the read takes longer than this value. But this error isn't fatal, and your code can catch this and simply try again. This gives your application a chance to stay active while you wait for a long hardware read.

The result is a Tcl list containing the success code and return value. In this case, a 1 shows us that the relay is set/closed.

Sent the command to reset/open the ADU100's relay

This is the opposite of the set command.

Sent the command to check the relay status again

We'll now expect the hardware to report 0 for the relay status.

Read from the ADU100

The returned list is now 0 0, telling us that the command succeeded and that the relay is reset/open.

Links

Adjust Xfig figure depths with figdepth

John Peck
Published

Xfig's depth settings can create some headaches when you use figure libraries. Library figures can use depths that conflict with your current drawing. Say I have this drawing of the Teensy Audio Board and a red circle,

Teensy audio board and a red circle

...and then I move the red circle over the board:

Overlapping objects without depth correction

This is clearly not what I want. The circle has a depth of 50, and the board has depths between 10 and 60. The circle ends up on top of some objects and not others. If you were using PowerPoint, you would just select Bring to Front or Send to Back to arrange the objects. But Xfig requires setting the depth of each member object individually, which you should really do with a script like figdepth.

Using figdepth

Figdepth has some options, the most important of which is the minimum depth argument

Figdepth command options

...which sets the depth of the topmost object in a fig file. The script simply increments all depths by some number to make this minimum depth to be your setting. You will, of course, need to have everything you want to modify in its own fig file. You can then run figdepth like

Figdepth use

...to write your new depth-adjusted figure. Use Xfig's merge command to bring the new figure into your old drawing. My new drawing looks like this:

Fixed overlap

...with all objects in the Teensy Audio Board at a deeper depth than the red circle.

How does it work?

The Fig file format is really simple, which is one reason why I like Xfig so much. You can figure out what kind of object each line describes by looking at the first character. You then figure out which number in the line is the object's depth and increment it. The switch statement below shows how one line is processed.

proc increase_object_depth { xfig_line depth_increase} {
    # Return a new xfig line with the depth increased
    #
    # Arguments:
    #   xfig_line -- Single line from an xfig file
    #   depth_increase -- Amount to increase the depth
    set entry_list [split $xfig_line]
    set first_character [lindex $entry_list 0]
    switch $first_character {
    "0" {
        # Color object -- nothing to do
        return $xfig_line
    }
    "1" {
        # Ellipse
        set depth_index 6
        set old_depth [get_object_depth $xfig_line]
        set new_depth [expr $old_depth + $depth_increase]
        set new_entry_list [lset entry_list $depth_index $new_depth]
        set new_line [join $new_entry_list]
        return $new_line
    }
    "2" {
        # Polyline (also imported picture bounding boxes)
        set depth_index 6
        set old_depth [get_object_depth $xfig_line]
        set new_depth [expr $old_depth + $depth_increase]
        set new_entry_list [lset entry_list $depth_index $new_depth]
        set new_line [join $new_entry_list]
        return $new_line
    }
    "3" {
        # Spline
        set depth_index 6
        set old_depth [get_object_depth $xfig_line]
        set new_depth [expr $old_depth + $depth_increase]
        set new_entry_list [lset entry_list $depth_index $new_depth]
        set new_line [join $new_entry_list]
        return $new_line
    }
    "4" {
        # Text
        set depth_index 3
        set old_depth [get_object_depth $xfig_line]
        set new_depth [expr $old_depth + $depth_increase]
        set new_entry_list [lset entry_list $depth_index $new_depth]
        set new_line [join $new_entry_list]
        return $new_line
    }
    "5" {
        # Arc
        set depth_index 6
        set old_depth [get_object_depth $xfig_line]
        set new_depth [expr $old_depth + $depth_increase]
        set new_entry_list [lset entry_list $depth_index $new_depth]
        set new_line [join $new_entry_list]
        return $new_line
    }
    "6" {
        # Compound -- nothing to do
        return $xfig_line
    }
    default {
        # Some non-object -- nothing to do
        return $xfig_line
    }
  }
}

Having trouble with cmdline?

Figdepth uses the cmdline package from tcllib to handle command line arguments. I usually run scripts out of Eshell in Emacs, so I have this in my .emacs:

eshell setup

...to set the TCLLIBPATH environment variable. This tells Tcl where to find Tcllib's packages.

Where is the figdepth script?

Figdepth is part of my xfigart repository.

A cheap 4-channel 0 → 10V output with Tcl and MODBUS

John Peck
Published

I've been interested in fieldbusses lately, and MODBUS in particular is used by some pretty cheap hardware modules. It also offers "driverless" communication — you don't need a .dll or .so file to talk to your hardware. The hardware I've been experiementing with is shown below:

Hardware overview

...and can be bought from eBay for around 50 USD. I found some very useful code on the Tcl wiki and was able to write and read from registers with a USB/RS-485 adaptor. The RTU-307C user's manual implies that you can change the RS-485 baud from 9600, but I wasn't able to figure out how that works.

After some more help from The Tcler's Wiki, I put together a GUI to demonstrate MODBUS communication with the RTU-307C module. The animation below shows the GUI next to an oscilloscope (yelloscope) display. The wiki helped me get the mouse wheel to move those sliders.

Elliot with Yellowscope

Links

Source: Repository on Gitlab

Yelloscope: Fork of Syscomp Design's CGR-201 software

John Peck
Published

I have a few devices from Syscomp Electronic Design that I use for experimenting and debugging. The CGR-201, shown below, is one of these.

CGR-201 ISO

Syscomp sadly lost one of its founders, Peter Hiscocks, and won't be releasing any new products. I forked their CircuitGear software (screenshot below) to make some of the changes I wanted, and to start packaging the software as single-file executables for Linux. My fork is here:

https://gitlab.com/eventuallabs/yelloscope

...and you can download single-file executable releases here:

https://gitlab.com/eventuallabs/yelloscope/-/releases

screenshot

A note about running the software

You can always run from source with

tclsh main.tcl

in the src directory. But you'll need Tcl, Tk, and some helper packages. The single-file executable release contains all of these.

Once you download the executable, you'll have to give yourself permission to run it with something like

chmod a+x yelloscope-1.0.0-linux-x86_64

and your user will have to be in the right group for the software to access the hardware. For me, on Ubuntu, this group is dialout. I added myself to the group with

sudo adduser john dialout

...and then I had to reboot.

A note on making Starpacks

Starkits and Starpacks are ways of packaging Tcl programs to reduce or eliminate dependencies. The makefile in the Yelloscope repository shows how I automate creating Linux starpacks from Tcl sources. It's customized for my environment though, and you'll have to change some paths to make it work. I'll be happy to help if you reach out.

Findcoms: Find the COM port of Arduinos and use it in a makefile

John Peck
Published

My AVR-based Arduino workflow is automated by GNU makefiles, which call avrdude to interact with the hardware. It's nice to also have the makefile find the Virtual COM Port (VCP) assigned to the hardware programmer or USB/UART device. Findcoms is a Tcl script that finds the device and writes the VCP name to a file. You can then access this file from your makefile to get the name to avrdude.

From the command line

Findcoms uses a -d parameter to decide which device to detect. Use -d? to get a list of available d parameters.

list of d params

Use the pololu_isp parameter to detect the Pololu USB AVR Programmer v2 — a very nice device that can program the flash on an ATmega2560.

pololu detection

Take a look at the GitLab repo for more details.

Fakestream simulates streaming data to a file

John Peck
Published , updated

I've been trying to figure out a good way to plot some sensor data as it comes in, and I needed a way to simulate that data. I ultimately wanted to get timestamped data into gnuplot, and I knew gnuplot would take time data formatted as Unix Timestamps. I thought, maybe I can use Unix Timestamps with millisecond precision and avoid all the Day/Month/Year formatting. I found out it wasn't going to be that easy.

Gnuplot can accept floating point seconds, but only with the right formatting

Gnuplot won't accept Unix Timestamps with millisecond precision -- the number has to come in as an integer. But it will accept times formatted as %H:%M:%S with floating point seconds. So then I needed to create these millisecond precision stamps. Tcl will give you the milliseconds since 1970, but its clock format command will neither consume this number directly, nor will it consume this number in floating point seconds. The fix, as pointed out by Donal Fellows, is to bolt the seconds and milliseconds together with a format string.

Fakestream creates these stamps to simulate real-time data

I wrote fakestream to continuously write data stamped with these millisecond-precision stamps to a file. The plot below shows gnuplot periodically scanning this file and updating a plot.

gnuplot plot

The fakestream repository has the Tcl source for this script as well as single file executables for easy distribution. These Starkits and Starpacks are what keep me coming back to Tcl.

Set TCLLIBPATH on Windows with Emacs

John Peck
Published , updated

I do electronic design for a living, and the CAD package I use determines the operating system I spend most of my time in. This means Windows during the day, and Linux at home. Using Emacs on both platforms gives me a consistent look and feel. And Eshell is a very nice command shell for Windows.

I also need Tcl on both platforms, and Emacs gives me a nice way to set the TCLLIBPATH environment variable. This tells Tcl where to look for packages you bring in with package require. The TCLLIBPATH variable is a list of paths, and Tcl needs to be able to make a list variable out of whatever it finds in TCLLIBPATH. A space-separated list works very nicely. I have this in my .emacs:

;; Get extra emacs packages -- including some useful
;; string-manipulating functions like string-join
(require 'subr-x)

;; Add my local package directory to auto_path.  This will be a Tcl
;; list, so the entries should be separated by spaces.
;;
;; Get tklib from https://github.com/tcltk/tklib
(setenv "TCLLIBPATH"
    (string-join '("c:/Tcl/lib/local"
               "c:/Tcl/lib/local/tklib/modules")
             " "))

Why do I need to (require 'subr-x)? The string-join command is supposed to be part of Emacs 25, but my Emacs 25.2.2 still chokes on it at startup without the subr-x package. After evaluating .emacs with M-x eval-buffer:

eshell_output

...you can see that the TCLLIBPATH variable is set, and that Tcl uses it in its auto_path list. I originally installed Tcl using ActiveState's installer, which makes its own changes to auto_path.

I have the very interesting Gub package saved here:

gub_path

and I can now require it with

gub_invoke

...to show that auto_path allowed Tcl to find the local package.

Connecting to the Bus Pirate

John Peck
Published , updated

The Bus Pirate is a cheap way to talk to I2C (and a lot of other serial protocol) devices from your laptop. I use it to bring up new boards -- making sure everything works before they get integrated into a bigger platform. Specifically, I've been using the Bus Pirate version 4 (BPv4) shown below.

bpv4

Dangerous Prototypes has built the Bus Pirate's API into the hardware with its binary bitbang firmware mode. This is great, since it means you don't need a separate piece of software running on your laptop to run the API -- you can use whatever scripting language you want.

Connecting to the BPv4 is just like connecting to any other USB CDC (ACM) device. The version 3 Bus Pirate has an FTDI device to handle the communication, while the BPv4 moves the USB stack into a Microchip PIC. You'll have the usual first-time problems: Windows wants an INF file, and Ubuntu wants your user to be in the same group as the device node. I linked the INF file I used below.

Once those basics are in place, the details of communicating over the channel are determined by your choice of scripting language. I use Tcl for things like this, so I need to use commands like chan configure. The debug log shown below shows the script doing a brute-force search of all connected devices.

bpv4<em>connection</em>trace

I'll go into what this potset script is later. For right now, notice that the script identified two potential device nodes: ttyACM0 and ttyACM1. The script will connect with the first device that reponds to a carriage return with HiZ>. The busbridge repository shows how I did things. The BPv4 actually has an eeprom that could be used to "tag" different Bus Pirates if you wanted to work with more than one at once.

Going forward

The next step is to activate the BPv4's API to set up SPI or I2C communication. I'll talk about that later.

References

Busbridge

BPv4 Windows INF file

Create pipe tables from PCB's drill files

John Peck
Published , updated

I use PCB to design PCBs, and I recently needed to embed a list of drills and their uses in a plain text file. PCB's drill file looks like this:

M48
INCH
T72C0.010
T71C0.015
T70C0.067
T69C0.039
T68C0.030
T67C0.046
T66C0.138
%
T72
X010249Y009035
X010249Y005886
X010268Y008256
X010268Y007075
X010268Y005107

...and I wrote drilltable.tcl to generate tables like this:

|------------+----------------------+------------|
| Tool       | Hole size (inch)     | Count      | 
|------------+----------------------+------------|
| T72        | 0.010                | 218        | 
| T71        | 0.015                | 95         | 
| T68        | 0.030                | 3          | 
| T69        | 0.039                | 10         | 
| T67        | 0.046                | 15         | 
| T70        | 0.067                | 1          | 
| T66        | 0.138                | 4          | 
|------------+----------------------+------------|

This borrows from an excellent usage example of Tcl's format command at the Tclers Wiki.