Blog

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

In-system calibration of a μA current sensor

John Peck
Published

One of my projects will be powered by a battery, and will sleep for long periods while it waits for user input. My battery life will depend on this sleep current, which might be about 10 µA. My handheld meter will measure this, but it suffers from mediocre common-mode rejection when I use it to measure high-side current. I really wanted to probe my sleep current and send it directly to an oscilloscope. These INA169 breakout boards seem like a convenient way to make this probe, but using them raises calibration, noise, and bandwidth questions. I want to describe how I put down a simple calibration circuit to give myself some confidence in this measurement.

Wiring the sensor

Figure 1 shows the current sensor, which is built around TI’s IN169 current sensor IC. Adafruit sells the breakout board I used, which builds in the IC, the current sensing resistor Rshunt, the current-to-voltage resistor Rload, and a bypass capacitor. It comes configured with a convenient gain of 1 V/A, and sells for about 10 USD. Sparkfun and others make similar breakout boards. These boards can be modified for different “burden voltages” and power configurations.

Probe wiring
Figure 1: Wiring the INA169 sensor and calibration circuit to measure 10 μA.

One of the modifications I make is to power the INA169 from the high side of my current measurement. This simplifies connecting the sensor, and there’s no advantage to increasing it above Vdd – the maximum output voltage is just below the Vin− input.

The second modification is to configure a gain of about 100 µA/V instead of 1 A/V. My quiescent currents will be between 1 µA and 100 µA, and I wanted less than 1 µA of noise with my Digilent oscilloscope. As shown in figure 1, I chose Rload to give me a voltage gain of 100, and Rshunt to give me about 300 µA full scale. This high voltage gain causes an offset voltage that can’t be neglected, which is why I built a calibration circuit into my prototype.

The calibration circuit is just a resistor, a header for the sensor, and a test point for the pin voltage. It’s not a lot of parts to add, and building the circuit right into my prototype gives me an easy way to make sure the sensor is still working correctly.

Calibration

Pulsing Vpin low captures the sensor’s slope and offset in one oscilloscope trigger. Figure 2 shows the raw sensor output during the pulse. The current source resistor, Rcs , sees Vdd − Ical Rshunt on one side, and Vpin on the other during the pulse. The current sensing resistor, Rshunt , is less than 1% of Rcs and I’ll neglect it. This gives a pulsed current of 3.3 V/330 kΩ = 10 µA. Looking at the ATmega328pb datasheet and making some measurements with a meter confirms this current.

Scope shot
Figure 2: Raw and calibrated sensor output.

The ATmega328pb datasheet shows Vpin = Vdd for no output current, and I confirmed that with a meter. Figure 3 shows the ATmega328pb’s pulldown resistance when it sinks current. Vpin will be within 1 mV of 0 V when sinking 10 µA. My measured Vdd = 3.314 V, and my measured Rcs = 328.8 kΩ. This makes a calibration current of 10.1 µΩ. These numbers suggest that we can calibrate the sensor to about 1%, but I don’t want to push things past about 5%. Still, I’ll keep about three significant digits when I enter numbers into the Waveforms software for calibration.

Pin pulldown
Figure 3: ATmega328pb pin pulldown strength at Vdd = 3V.

The bottom right corner of figure 2 shows the calibration formula I used to make the calibrated data trace. I subtract off the 54.078 mV of offset, then I multiply the result by a slope of 10.1 µA/95.093 mV = 106 µA/V. The Waveforms software oscilloscope trace then shows a nicely calibrated sensor output.

Is it fast enough?

Figure 2 shows rise and fall times of about 29 µs for the sensor output. This gives a first-order bandwidth of 0.35/29 µs = 12 kHz. This is consistent with the INA169’s datasheet, which predicts a bandwidth of between 10 kHz and 20 kHz for a 100 kΩ gain resistor. This bandwidth is just fine for measuring my quiescent current when systems come in and out of sleep. I worry about missing high frequency spikes though, and I’ll make a low-side current measurement to confirm I’m not missing anything. It’s also not expensive to configure the sensor for something like 10 mA full scale, which would give you two orders of magnitude more bandwidth.

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.

Making Vpp and Vrms units in LaTeX with siunitx

John Peck
Published

The siunitx package for LaTeX makes it easy to handle units in both normal and math modes. I end up needing units like Vrms when talking about electronics, so I add this in my preamble:

\usepackage{siunitx}

\DeclareSIUnit \vrms {\ensuremath{\mathrm{V_{rms}}}}
\DeclareSIUnit \vpp {\ensuremath{\mathrm{V_{pp}}}}

...to define these units to siunitx. I can then invoke the units with something like

free air.  I drove them to about \SI{9}{\vpp}.

where I know siunitx will then make the proper space between the number and the unit. This all renders like this:

Vpp rendered to pdf

...in the PDF output.

Mounting the Teensy 4.0 on its Audio Adaptor Board

John Peck
Published

I've been wanting to experiment with audio signal processing on the Teensy, so I bought a Teensy 4.0 and an Audio Adaptor Board. I had to do a bit of research to find the right headers to plug one board into the other. I ended up with the headers listed below.

Samtec part Digi-Key part
SSQ-114-03-T-S SAM1206-14-ND

I used these headers in the photo shown below.

Teensy 4.0 on Audio Adaptor

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

Prevent ATMega328P crashes with a stack monitor

John Peck
Published

The ATmega328P is used on the Arduino UNO, the Nano, and on many other mass-produced microcontroller boards. But it has only 2048 bytes of SRAM, which is shared between the stack and storage for things like local variables. Bizarre behavior can happen if the SRAM needed for variable storage overlaps that needed for the stack, but it's hard to understand how much space these variables will use until the program is actually running. This is where Michael McTernan's stack monitor is useful.

The stack monitor "paints" SRAM storage registers with a magic value before application code executes. You can then check for unused stack memory during execution by counting up these magic values. Here's some debug output from my Diamond firmware running on the EVL-510:

debug trace

The unused SRAM is initially 200 bytes, but falls to 179 after turning the volume knob. Changing the volume calls a few functions that allocate local variables in SRAM. These get cleaned up when the function exits, but the stack monitor can see that the memory was used. The idea is to exercise as much of the system as you can and then make sure you still have some unused memory.

I call the function with a scheduler made from avr-simple-scheduler. The code block below shows adding the stack reporting function to the scheduler with a period of 5s. I could have made this even longer, since I only need the report to run after I've excercised as much as I can.

if (strcmp( LOG_LEVEL, "debug" ) == 0) {
    // Start the task to monitor free memory
    OS_TaskCreate(&system_report_stack_free, 5000, BLOCKED);
}

EVL-510: First look

John Peck
Published

This desktop speaker turned out well, and I asked Jack at Prime Images Photography to take some real product photos. There's lots of interesting stuff here, including the volume knob's state machine and some handy prototyping tools from Mikroe. I'll get into these in some later posts.

EVL-510 hero shot

EVL-510 rear panel