The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Software

General-purpose computers doing something specific

  • TC4Server: Eagle HAL Device

    Dan Newman’s TC4Server turns the TC4 thermocouple board into a USB HID input device that’s compatible with HAL’s hal_input module:

    TC4 on ProtoScrewShield on Leonardo
    TC4 on ProtoScrewShield on Leonardo

    For simplicity (i.e., to avoid writing a special driver), TC4Server misrepresents itself as a nine-axis joystick-like device suited for RC airplane control:

    halrun
    halcmd: loadusr -W hal_input +A Leonardo
    halcmd: show
    ... snippage ...
    Component Pins:
    Owner   Type  Dir         Value  Name
         5  s32   OUT          2941  input.0.abs-rudder-counts
         5  s32   IN           4095  input.0.abs-rudder-flat
         5  s32   IN            255  input.0.abs-rudder-fuzz
         5  bit   OUT          TRUE  input.0.abs-rudder-is-neg
         5  bit   OUT         FALSE  input.0.abs-rudder-is-pos
         5  float IN        32767.5  input.0.abs-rudder-offset
         5  float OUT    -0.9102464  input.0.abs-rudder-position
         5  float IN        32767.5  input.0.abs-rudder-scale
         5  s32   OUT          2947  input.0.abs-rx-counts
         5  s32   IN           4095  input.0.abs-rx-flat
         5  s32   IN            255  input.0.abs-rx-fuzz
         5  bit   OUT          TRUE  input.0.abs-rx-is-neg
         5  bit   OUT         FALSE  input.0.abs-rx-is-pos
         5  float IN        32767.5  input.0.abs-rx-offset
         5  float OUT    -0.9100633  input.0.abs-rx-position
         5  float IN        32767.5  input.0.abs-rx-scale
         5  s32   OUT         65535  input.0.abs-ry-counts
         5  s32   IN           4095  input.0.abs-ry-flat
         5  s32   IN            255  input.0.abs-ry-fuzz
         5  bit   OUT         FALSE  input.0.abs-ry-is-neg
         5  bit   OUT          TRUE  input.0.abs-ry-is-pos
         5  float IN        32767.5  input.0.abs-ry-offset
         5  float OUT             1  input.0.abs-ry-position
         5  float IN        32767.5  input.0.abs-ry-scale
         5  s32   OUT         65535  input.0.abs-rz-counts
         5  s32   IN           4095  input.0.abs-rz-flat
         5  s32   IN            255  input.0.abs-rz-fuzz
         5  bit   OUT         FALSE  input.0.abs-rz-is-neg
         5  bit   OUT          TRUE  input.0.abs-rz-is-pos
         5  float IN        32767.5  input.0.abs-rz-offset
         5  float OUT             1  input.0.abs-rz-position
         5  float IN        32767.5  input.0.abs-rz-scale
         5  s32   OUT         65535  input.0.abs-throttle-counts
         5  s32   IN           4095  input.0.abs-throttle-flat
         5  s32   IN            255  input.0.abs-throttle-fuzz
         5  bit   OUT         FALSE  input.0.abs-throttle-is-neg
         5  bit   OUT          TRUE  input.0.abs-throttle-is-pos
         5  float IN        32767.5  input.0.abs-throttle-offset
         5  float OUT             1  input.0.abs-throttle-position
         5  float IN        32767.5  input.0.abs-throttle-scale
         5  s32   OUT          2957  input.0.abs-wheel-counts
         5  s32   IN           4095  input.0.abs-wheel-flat
         5  s32   IN            255  input.0.abs-wheel-fuzz
         5  bit   OUT          TRUE  input.0.abs-wheel-is-neg
         5  bit   OUT         FALSE  input.0.abs-wheel-is-pos
         5  float IN        32767.5  input.0.abs-wheel-offset
         5  float OUT    -0.9097581  input.0.abs-wheel-position
         5  float IN        32767.5  input.0.abs-wheel-scale
         5  s32   OUT          2942  input.0.abs-x-counts
         5  s32   IN           4095  input.0.abs-x-flat
         5  s32   IN            255  input.0.abs-x-fuzz
         5  bit   OUT          TRUE  input.0.abs-x-is-neg
         5  bit   OUT         FALSE  input.0.abs-x-is-pos
         5  float IN        32767.5  input.0.abs-x-offset
         5  float OUT    -0.9102159  input.0.abs-x-position
         5  float IN        32767.5  input.0.abs-x-scale
         5  s32   OUT          2942  input.0.abs-y-counts
         5  s32   IN           4095  input.0.abs-y-flat
         5  s32   IN            255  input.0.abs-y-fuzz
         5  bit   OUT          TRUE  input.0.abs-y-is-neg
         5  bit   OUT         FALSE  input.0.abs-y-is-pos
         5  float IN        32767.5  input.0.abs-y-offset
         5  float OUT    -0.9102159  input.0.abs-y-position
         5  float IN        32767.5  input.0.abs-y-scale
         5  s32   OUT          2940  input.0.abs-z-counts
         5  s32   IN           4095  input.0.abs-z-flat
         5  s32   IN            255  input.0.abs-z-fuzz
         5  bit   OUT          TRUE  input.0.abs-z-is-neg
         5  bit   OUT         FALSE  input.0.abs-z-is-pos
         5  float IN        32767.5  input.0.abs-z-offset
         5  float OUT     -0.910277  input.0.abs-z-position
         5  float IN        32767.5  input.0.abs-z-scale
         5  s32   OUT          2941  input.1.abs-rudder-counts
         5  s32   IN           4095  input.1.abs-rudder-flat
         5  s32   IN            255  input.1.abs-rudder-fuzz
         5  bit   OUT          TRUE  input.1.abs-rudder-is-neg
         5  bit   OUT         FALSE  input.1.abs-rudder-is-pos
         5  float IN        32767.5  input.1.abs-rudder-offset
         5  float OUT    -0.9102464  input.1.abs-rudder-position
         5  float IN        32767.5  input.1.abs-rudder-scale
         5  s32   OUT          2947  input.1.abs-rx-counts
         5  s32   IN           4095  input.1.abs-rx-flat
         5  s32   IN            255  input.1.abs-rx-fuzz
         5  bit   OUT          TRUE  input.1.abs-rx-is-neg
         5  bit   OUT         FALSE  input.1.abs-rx-is-pos
         5  float IN        32767.5  input.1.abs-rx-offset
         5  float OUT    -0.9100633  input.1.abs-rx-position
         5  float IN        32767.5  input.1.abs-rx-scale
         5  s32   OUT         65535  input.1.abs-ry-counts
         5  s32   IN           4095  input.1.abs-ry-flat
         5  s32   IN            255  input.1.abs-ry-fuzz
         5  bit   OUT         FALSE  input.1.abs-ry-is-neg
         5  bit   OUT          TRUE  input.1.abs-ry-is-pos
         5  float IN        32767.5  input.1.abs-ry-offset
         5  float OUT             1  input.1.abs-ry-position
         5  float IN        32767.5  input.1.abs-ry-scale
         5  s32   OUT         65535  input.1.abs-rz-counts
         5  s32   IN           4095  input.1.abs-rz-flat
         5  s32   IN            255  input.1.abs-rz-fuzz
         5  bit   OUT         FALSE  input.1.abs-rz-is-neg
         5  bit   OUT          TRUE  input.1.abs-rz-is-pos
         5  float IN        32767.5  input.1.abs-rz-offset
         5  float OUT             1  input.1.abs-rz-position
         5  float IN        32767.5  input.1.abs-rz-scale
         5  s32   OUT         65535  input.1.abs-throttle-counts
         5  s32   IN           4095  input.1.abs-throttle-flat
         5  s32   IN            255  input.1.abs-throttle-fuzz
         5  bit   OUT         FALSE  input.1.abs-throttle-is-neg
         5  bit   OUT          TRUE  input.1.abs-throttle-is-pos
         5  float IN        32767.5  input.1.abs-throttle-offset
         5  float OUT             1  input.1.abs-throttle-position
         5  float IN        32767.5  input.1.abs-throttle-scale
         5  s32   OUT          2957  input.1.abs-wheel-counts
         5  s32   IN           4095  input.1.abs-wheel-flat
         5  s32   IN            255  input.1.abs-wheel-fuzz
         5  bit   OUT          TRUE  input.1.abs-wheel-is-neg
         5  bit   OUT         FALSE  input.1.abs-wheel-is-pos
         5  float IN        32767.5  input.1.abs-wheel-offset
         5  float OUT    -0.9097581  input.1.abs-wheel-position
         5  float IN        32767.5  input.1.abs-wheel-scale
         5  s32   OUT          2942  input.1.abs-x-counts
         5  s32   IN           4095  input.1.abs-x-flat
         5  s32   IN            255  input.1.abs-x-fuzz
         5  bit   OUT          TRUE  input.1.abs-x-is-neg
         5  bit   OUT         FALSE  input.1.abs-x-is-pos
         5  float IN        32767.5  input.1.abs-x-offset
         5  float OUT    -0.9102159  input.1.abs-x-position
         5  float IN        32767.5  input.1.abs-x-scale
         5  s32   OUT          2942  input.1.abs-y-counts
         5  s32   IN           4095  input.1.abs-y-flat
         5  s32   IN            255  input.1.abs-y-fuzz
         5  bit   OUT          TRUE  input.1.abs-y-is-neg
         5  bit   OUT         FALSE  input.1.abs-y-is-pos
         5  float IN        32767.5  input.1.abs-y-offset
         5  float OUT    -0.9102159  input.1.abs-y-position
         5  float IN        32767.5  input.1.abs-y-scale
         5  s32   OUT          2940  input.1.abs-z-counts
         5  s32   IN           4095  input.1.abs-z-flat
         5  s32   IN            255  input.1.abs-z-fuzz
         5  bit   OUT          TRUE  input.1.abs-z-is-neg
         5  bit   OUT         FALSE  input.1.abs-z-is-pos
         5  float IN        32767.5  input.1.abs-z-offset
         5  float OUT     -0.910277  input.1.abs-z-position
         5  float IN        32767.5  input.1.abs-z-scale
    
    ... snippage ...
    Parameters:
    Owner   Type  Dir         Value  Name
         5  s32   RO          65535  input.0.abs-rudder-max
         5  s32   RO              0  input.0.abs-rudder-min
         5  s32   RO          65535  input.0.abs-rx-max
         5  s32   RO              0  input.0.abs-rx-min
         5  s32   RO          65535  input.0.abs-ry-max
         5  s32   RO              0  input.0.abs-ry-min
         5  s32   RO          65535  input.0.abs-rz-max
         5  s32   RO              0  input.0.abs-rz-min
         5  s32   RO          65535  input.0.abs-throttle-max
         5  s32   RO              0  input.0.abs-throttle-min
         5  s32   RO          65535  input.0.abs-wheel-max
         5  s32   RO              0  input.0.abs-wheel-min
         5  s32   RO          65535  input.0.abs-x-max
         5  s32   RO              0  input.0.abs-x-min
         5  s32   RO          65535  input.0.abs-y-max
         5  s32   RO              0  input.0.abs-y-min
         5  s32   RO          65535  input.0.abs-z-max
         5  s32   RO              0  input.0.abs-z-min
         5  s32   RO          65535  input.1.abs-rudder-max
         5  s32   RO              0  input.1.abs-rudder-min
         5  s32   RO          65535  input.1.abs-rx-max
         5  s32   RO              0  input.1.abs-rx-min
         5  s32   RO          65535  input.1.abs-ry-max
         5  s32   RO              0  input.1.abs-ry-min
         5  s32   RO          65535  input.1.abs-rz-max
         5  s32   RO              0  input.1.abs-rz-min
         5  s32   RO          65535  input.1.abs-throttle-max
         5  s32   RO              0  input.1.abs-throttle-min
         5  s32   RO          65535  input.1.abs-wheel-max
         5  s32   RO              0  input.1.abs-wheel-min
         5  s32   RO          65535  input.1.abs-x-max
         5  s32   RO              0  input.1.abs-x-min
         5  s32   RO          65535  input.1.abs-y-max
         5  s32   RO              0  input.1.abs-y-min
         5  s32   RO          65535  input.1.abs-z-max
         5  s32   RO              0  input.1.abs-z-min
    ... snippage ...
    

    Dan’s program assigns the outputs thusly:

    • Wheel – ambient temperature as measured on TC4 board
    • X Y Z Rudder – thermocouples – channels 1 through 4
    • RX RY RZ  Throttle – thermistors – channels 5 through 8

    I created a huge Eagle device that encapsulates the whole thing. A simple demo schematic includes the constants that make the temperatures come out in °C:

    TC4Server - Eagle Schematic
    TC4Server – Eagle Schematic

    That picture produces this HAL file:

    # HAL config file automatically generated by Eagle-CAD ULP:
    # [/mnt/bulkdata/Project Files/eagle/ulp/hal-write-2.5.ulp]
    # (C) Martin Schoeneck.de 2008
    # Charalampos Alexopoulos 2011
    # Mods Ed Nisley KE4ZNU 2010 2013
    # Path        [/mnt/bulkdata/Project Files/eagle/projects/LinuxCNC for M2/]
    # ProjectName [LinuxCNC M2 - TC4Server Test]
    # File name   [/mnt/bulkdata/Project Files/eagle/projects/LinuxCNC for M2/TC4Server.hal]
    # Created     [20:03:16 03-Jun-2013]
    
    ####################################################
    # Load realtime and userspace modules
    loadusr -W hal_input -A +Leonardo
    loadrt threads name1=servo-thread period1=1000000
    loadrt constant        count=4
    loadrt conv_float_s32        count=2
    
    ####################################################
    # Hook functions into threads
    addf constant.0        servo-thread
    addf constant.1        servo-thread
    addf constant.2        servo-thread
    addf constant.3        servo-thread
    addf conv-float-s32.0        servo-thread
    addf conv-float-s32.1        servo-thread
    
    ####################################################
    # Set parameters
    
    ####################################################
    # Set constants
    setp constant.0.value    10
    setp constant.1.value    2732
    setp constant.2.value    0
    setp constant.3.value    0
    
    ####################################################
    # Connect Modules with nets
    net n_2 constant.2.out conv-float-s32.1.in
    net n_3 constant.3.out conv-float-s32.0.in
    net tc4-ambient input.0.abs-wheel-position
    net tc4-flat input.0.abs-wheel-flat input.0.abs-x-flat input.0.abs-y-flat input.0.abs-z-flat input.0.abs-rudder-flat input.0.abs-rx-flat input.0.abs-ry-flat input.0.abs-rz-flat input.0.abs-throttle-flat conv-float-s32.1.out
    net tc4-fuzz input.0.abs-throttle-fuzz input.0.abs-rz-fuzz input.0.abs-ry-fuzz input.0.abs-rx-fuzz input.0.abs-rudder-fuzz input.0.abs-z-fuzz input.0.abs-y-fuzz input.0.abs-x-fuzz input.0.abs-wheel-fuzz conv-float-s32.0.out
    net tc4-offset input.0.abs-wheel-offset input.0.abs-x-offset input.0.abs-y-offset input.0.abs-z-offset input.0.abs-rudder-offset input.0.abs-rx-offset input.0.abs-ry-offset input.0.abs-rz-offset input.0.abs-throttle-offset constant.1.out
    net tc4-scale input.0.abs-wheel-scale input.0.abs-x-scale input.0.abs-y-scale input.0.abs-z-scale input.0.abs-rudder-scale input.0.abs-rx-scale input.0.abs-ry-scale input.0.abs-rz-scale input.0.abs-throttle-scale constant.0.out
    net tcouple-1 input.0.abs-x-position
    net tcouple-2 input.0.abs-y-position
    net tcouple-3 input.0.abs-z-position
    net tcouple-4 input.0.abs-rudder-position
    net tmistor-5 input.0.abs-rx-position
    net tmistor-6 input.0.abs-ry-position
    net tmistor-7 input.0.abs-rz-position
    net tmistor-8 input.0.abs-throttle-position
    

    Fire it up with halrun to see the temperatures (alphabetically by the pin name):

    halrun -I -f TC4Server.hal
    halcmd: start
    halcmd: show pin *position
    Component Pins:
    Owner   Type  Dir         Value  Name
         5  float OUT          20.9  input.0.abs-rudder-position ==> tcouple-4
         5  float OUT          21.5  input.0.abs-rx-position ==> tmistor-5
         5  float OUT        6280.3  input.0.abs-ry-position ==> tmistor-6
         5  float OUT        6280.3  input.0.abs-rz-position ==> tmistor-7
         5  float OUT        6280.3  input.0.abs-throttle-position ==> tmistor-8
         5  float OUT          22.5  input.0.abs-wheel-position ==> tc4-ambient
         5  float OUT            21  input.0.abs-x-position ==> tcouple-1
         5  float OUT            21  input.0.abs-y-position ==> tcouple-2
         5  float OUT          20.8  input.0.abs-z-position ==> tcouple-3
    

    The sensors do not correspond to the picture at the top: only the first thermocouple and first thermistor are connected ; the ADC returns bogus data for disconnected inputs, which means you must be careful about tightening the wires and checking the result. Dan’s firmware has the ability to disable unused sensors, in which case you get a huge value; when used for heater control, a sensor failing high means the heater will turn off, but, should you use this gadget in a freezer, you might want them to fail low (so modify the code for your own use).

    The ambient temperature reported for the board runs 1 or 2 °C higher than the actual ambient air temperature, probably because of all those components doing useful things up close to the sensor chip. That particular ambient temperature serves as the cold junction reference for the thermocouples; the other temperatures don’t change very much as the board warms up, so it’s all good.

    Remember to issue the start command in halrun, because otherwise nothing changes.

    Also remember that you must configure TC4Server with the thermistor characteristics before you use it as a hal_input device.

  • UDEV Rules for M2’s HAL Devices

    Rather than have a bunch of separate rule files for each USB HID device I’ll be using with LinuxCNC on the M2, here they are in one lump:

    cat /etc/udev/rules.d/hal-input.rules
    # Rules to configure input device permissions for hal_input
    
    ATTRS{product}=="Nostromo SpeedPad2",GROUP="plugdev",MODE="0660"
    ATTRS{product}=="Arduino Leonardo",GROUP="plugdev",MODE="0660"
    ATTRS{product}=="Logitech Dual Action",GROUP="plugdev",MODE="0660"
    

    The strings are case-sensitive and must match exactly. That post (among others) describes the whole dance required to get all the information.

    Remember to add yourself to the plugdev group, too.

  • Floppy Drive Support in System Rescue CD

    For obscure reasons, I’m kibitzing on a project to rehabilitate an ancient Brother industrial sewing machine. It has a floppy disk drive that stored various custom stitch patterns, but it now crashes / jams / stalls after loading any of the patterns.

    I booted an old PC that had a floppy drive using System Rescue CD, only to discover that /dev/fd0 didn’t exist. A bit of search-fu revealed that the floppy kernel module isn’t automagically loaded: a simple modprobe floppy did the trick, after which mount -o ro /mnt/floppy worked fine (it’s in fstab, even if the kernel module isn’t loaded).

    The floppy was in IBM PC-DOS format, as you might expect in a system with ICs date-coded in the early 90s and an 8085 CPU (not an 8088 or 8086). Applying dd bs=512 if=/dev/fd0 of=/tmp/floppy.bin produced a measly 12 kB file containing the boot sector, many binary zeos, a line or two of pinball panic, and more binary zeros up to the 0x3000 file size, where it ended due to a hard read error.

    So now we know there’s no point in trying to run from the floppy, because there’s nothing to run. According to the instructions, the sewing machine can write to the floppy, so we can examine some of those results to see what the data structures should be.

    A new-to-me off-lease Dell Optiplex 760 that I just picked up (for the M2’s LinuxCNC controller) has a floppy drive, so I can let that old hulk go to the recycler. I don’t see a big duty cycle for the floppy, but ya gotta have stuff…

  • Monthly Science: Basement Safe Humidity

    A plot of the temperature and humidity inside the basement safe over the last year-and-a-half:

    Basement Safe
    Basement Safe

    The tray of silica gel (or whatever those granules might be) holds the humidity firmly at 14 to 15 %, at least with a simple masking tape seal around the door opening that dates back to early 2012. Less water vapor gets through the door during the winter, due to the lower basement humidity when it’s cold outside, but it looks like four regenerations per year with just under a kilogram of desiccant in the tray.

    Ordinarily, it’d be time for those granules to endure another oven session, but I just picked up a bunch of real silica gel beads and must conjure up some porous bags.

    The Bash / Gnuplot script that cleaned the CSV files and produced the plot:

    #!/bin/sh
    #-- overhead
    export GDFONTPATH="/usr/share/fonts/truetype/"
    base="${1%.*}"
    echo Base name: ${base}
    ofile=${base}.png
    tfile=$(tempfile)
    echo Input file: $1
    echo Temporary file: ${tfile}
    echo Output file: ${ofile}
    #-- prepare csv Hobo logger file
    sed 's/^\"/#&/' "$1" | sed 's/^.*Logged/#&/' | sed 's/ ,/,/' | sed 's/\/\([0-9][0-9]\) /\/20\1 /' > ${tfile}
    #-- do it
    gnuplot << EOF
    #set term x11
    set term png font "arialbd.ttf" 18 size 950,600
    set output "${ofile}"
    set title "${base}"
    set key noautotitles
    unset mouse
    set bmargin 4
    set grid xtics ytics
    set timefmt "%m/%d/%Y %H:%M:%S"
    set xdata time
    set xlabel "Date"
    set format x "%y-%m"
    #set xrange [1.8:2.2]
    set xtics font "arial,12"
    #set mxtics 2
    #set logscale y
    set ytics nomirror autofreq
    set ylabel "Temperature - F"
    set format y "%3.0f"
    set yrange [40:90]
    set mytics 2
    set y2label "Relative Humidity - %"
    set y2tics nomirror autofreq
    set format y2 "%3.0f"
    set y2range [10:60]
    #set y2tics 32
    #set rmargin 9
    set datafile separator ","
    #set label 1 "label text" at 2.100,110 right font "arialbd,18"
    #set arrow from 2.100,110 to 2.105,103 lt 1 lw 2 lc 0
    plot    \
        "${tfile}" using 2:3 axes x1y1 with lines lt 3 title "Temperature",\
        "${tfile}" using 2:4 axes x1y2 with lines lt 4 title "Humidity"
    EOF
    
  • Arduino Leonardo + TC4 Thermocouple Shield + TC4 Server

    Because I want to measure several fairly high temperatures in and around the extruder with the LinuxCNC controller for the M2, I need a multi-channel thermocouple input. LinuxCNC’s hal_input interface module exposes the values produced by USB HID peripherals as HAL pins, which seems to be a nice way to add devices. A bit of searching revealed the TC4 Arduino Shield four-channel thermocouple + four-channel thermistor + assorted I/O board produced by the homeroasters.org folks as part of their DIY coffee roast controller.

    The gotcha: ordinary Arduino boards cannot (without extraordinary effort) become USB HID peripherals, as their USB interface works only as a serial data device. The solution: the new-ish Arduino Leonardo has an on-chip USB interface that can act as a USB HID peripheral as well as the usual serial device.

    Dan Newman, of Sailfish firmware fame, conjured up an a Arduino program (which, IMO, is far more complex than a mere sketch) that provides both a human-readable terminal interface and a LinuxCNC HAL-compatible USB HID interface using the Leonardo’s USB capabilities; the TC4Server project repository is on my GitHub account to keep it close to all the other stuff that should appear over the course of this project. His firmware builds on the libraries for the homeroaster’s tc4-shield Google Code project, but is intended for use with LinuxCNC, rather than as part of an Arduino controller.

    It’s worth noting that the Leonardo has a mere 32 kB of program storage, so the extensive help documentation built into the program helped prevent feature creep.

    Although I’m not yet using LinuxCNC with the M2, I can use TC4-Server’s serial terminal interface to read four channels of thermocouple data to help figure out what’s going on with the M2’s extruder thermistor. The TC4 shield has screw terminals for the thermocouples, but I also added a Proto ScrewShield board for thermistor resistors and easier connections:

    TC4 on ProtoScrewShield on Leonardo
    TC4 on ProtoScrewShield on Leonardo

    The TC4 Shield PCB layout assumes it’s being used with the original Arduino series of boards that bring the I2C (aka I2C, IIC, etc) SDA and SCL signals out on the A4 and A5 analog input pins, respectively. The newer Leonardo board brings SDA and SCL out in a separate header, so you must hotwire them across the board. The green and blue wires (stripped from a ribbon cable) accomplish that purpose: they’re plugged into the new Leonardo header through bent male header pins and clamped into the ScrewShield terminals. This assumes the Leonardo’s A4 and A5 pins remain as inputs, which is true for Dan’s firmware. If you actually need those pins for analog inputs, then you must remove the header pins that interconnect the boards and hotwire directly to the TC4 headers.

    The TC4 shield includes an on-board temperature sensor that serves as the thermocouple cold-junction compensation reference. In my simple tests, the board has about 1 °C of self-heating, so I also use it to report the ambient temperature.

    With all that in hand, I connected the thermocouple epoxied to the M2’s nozzle to the TC4 and re-ran the previous test with the modified thermistor table:

    Rescaled extruder - thermocouple in TC4 board
    Rescaled extruder – thermocouple in TC4 board

    The TC4 shield produces the same result as the Fluke meter with the same thermocouple, so we know those results will be consistent.

    The modified thermistor table produces results that overlay thermocouple data. The questions come down to the accuracy of the thermocouple and whether bending the thermistor table actually represents an inherent property of the thermistor or just compensates for another problem.

    Part of my motivation for using thermocouples, rather than thermistors, is that thermocouples avoid the whole dance of matching a given thermistor with a set of properties. Given the uncertain provenance of most thermistors, I have no reason to believe any of them match their alleged datasheets…

  • Makergear M2: Thermistor Tables

    The Marlin firmware used by the M2 has these thermistor selections in Configuration.h:

    //// Temperature sensor settings:
    // -2 is thermocouple with MAX6675 (only for sensor 0)
    // -1 is thermocouple with AD595
    // 0 is not used
    // 1 is 100k thermistor
    // 2 is 200k thermistor
    // 3 is mendel-parts thermistor
    // 4 is 10k thermistor !! do not use it for a hotend. It gives bad resolution at high temp. !!
    // 5 is ParCan supplied 104GT-2 100K
    // 6 is EPCOS 100k
    // 7 is 100k Honeywell thermistor 135-104LAG-J01
    
    #define TEMP_SENSOR_0 1
    #define TEMP_SENSOR_1 1
    #define TEMP_SENSOR_2 0
    #define TEMP_SENSOR_BED 1
    

    The first table in thermistortables.h looks like this:

    #define OVERSAMPLENR 16
    
    #if (THERMISTORHEATER_0 == 1) || (THERMISTORHEATER_1 == 1)  || (THERMISTORHEATER_2 == 1) || (THERMISTORBED == 1) //100k bed thermistor
    
    const short temptable_1[][2] PROGMEM = {
    {       23*OVERSAMPLENR ,       300     },
    {       25*OVERSAMPLENR ,       295     },
    {       27*OVERSAMPLENR ,       290     },
    {       28*OVERSAMPLENR ,       285     },
    {       31*OVERSAMPLENR ,       280     },
    {       33*OVERSAMPLENR ,       275     },
    {       35*OVERSAMPLENR ,       270     },
    {       38*OVERSAMPLENR ,       265     },
    {       41*OVERSAMPLENR ,       260     },
    {       44*OVERSAMPLENR ,       255     },
    {       48*OVERSAMPLENR ,       250     },
    {       52*OVERSAMPLENR ,       245     },
    {       56*OVERSAMPLENR ,       240     },
    {       61*OVERSAMPLENR ,       235     },
    {       66*OVERSAMPLENR ,       230     },
    {       71*OVERSAMPLENR ,       225     },
    {       78*OVERSAMPLENR ,       220     },
    {       84*OVERSAMPLENR ,       215     },
    {       92*OVERSAMPLENR ,       210     },
    {       100*OVERSAMPLENR        ,       205     },
    {       109*OVERSAMPLENR        ,       200     },
    {       120*OVERSAMPLENR        ,       195     },
    {       131*OVERSAMPLENR        ,       190     },
    {       143*OVERSAMPLENR        ,       185     },
    {       156*OVERSAMPLENR        ,       180     },
    {       171*OVERSAMPLENR        ,       175     },
    {       187*OVERSAMPLENR        ,       170     },
    {       205*OVERSAMPLENR        ,       165     },
    {       224*OVERSAMPLENR        ,       160     },
    {       245*OVERSAMPLENR        ,       155     },
    {       268*OVERSAMPLENR        ,       150     },
    {       293*OVERSAMPLENR        ,       145     },
    {       320*OVERSAMPLENR        ,       140     },
    {       348*OVERSAMPLENR        ,       135     },
    {       379*OVERSAMPLENR        ,       130     },
    {       411*OVERSAMPLENR        ,       125     },
    {       445*OVERSAMPLENR        ,       120     },
    {       480*OVERSAMPLENR        ,       115     },
    {       516*OVERSAMPLENR        ,       110     },
    {       553*OVERSAMPLENR        ,       105     },
    {       591*OVERSAMPLENR        ,       100     },
    {       628*OVERSAMPLENR        ,       95      },
    {       665*OVERSAMPLENR        ,       90      },
    {       702*OVERSAMPLENR        ,       85      },
    {       737*OVERSAMPLENR        ,       80      },
    {       770*OVERSAMPLENR        ,       75      },
    {       801*OVERSAMPLENR        ,       70      },
    {       830*OVERSAMPLENR        ,       65      },
    {       857*OVERSAMPLENR        ,       60      },
    {       881*OVERSAMPLENR        ,       55      },
    {       903*OVERSAMPLENR        ,       50      },
    {       922*OVERSAMPLENR        ,       45      },
    {       939*OVERSAMPLENR        ,       40      },
    {       954*OVERSAMPLENR        ,       35      },
    {       966*OVERSAMPLENR        ,       30      },
    {       977*OVERSAMPLENR        ,       25      },
    {       985*OVERSAMPLENR        ,       20      },
    {       993*OVERSAMPLENR        ,       15      },
    {       999*OVERSAMPLENR        ,       10      },
    {       1004*OVERSAMPLENR       ,       5       },
    {       1008*OVERSAMPLENR       ,       0       } //safety
    };
    #endif
    

    The OVERSAMPLENR constant determines the number of successive ADC samples added together into a single value, which is then used to search the table for the corresponding entry. The table entries are pairs of:
    {nominal ADC value * number of samples, temperature in C}
    which means that if we know the temperature, we can work backwards to find the ADC value and then compute the actual thermistor resistance.

    However, before doing that, I created a modified version of the thermistor table that simply scales the temperatures down by 0.878:

    #if (THERMISTORHEATER_0 == 8) || (THERMISTORHEATER_1 == 8) || (THERMISTORHEATER_2 == 8) || (THERMISTORBED == 8) // M2 thermistors on RAMBO
    const short temptable_8[][2] PROGMEM = {
    	{23*OVERSAMPLENR, 263.51},
    	{25*OVERSAMPLENR, 259.12},
    	{27*OVERSAMPLENR, 254.73},
    	{28*OVERSAMPLENR, 250.34},
    	{31*OVERSAMPLENR, 245.94},
    	{33*OVERSAMPLENR, 241.55},
    	{35*OVERSAMPLENR, 237.16},
    	{38*OVERSAMPLENR, 232.77},
    	{41*OVERSAMPLENR, 228.38},
    	{44*OVERSAMPLENR, 223.98},
    	{48*OVERSAMPLENR, 219.59},
    	{52*OVERSAMPLENR, 215.2},
    	{56*OVERSAMPLENR, 210.81},
    	{61*OVERSAMPLENR, 206.42},
    	{66*OVERSAMPLENR, 202.03},
    	{71*OVERSAMPLENR, 197.63},
    	{78*OVERSAMPLENR, 193.24},
    	{84*OVERSAMPLENR, 188.85},
    	{92*OVERSAMPLENR, 184.46},
    	{100*OVERSAMPLENR, 180.07},
    	{109*OVERSAMPLENR, 175.67},
    	{120*OVERSAMPLENR, 171.28},
    	{131*OVERSAMPLENR, 166.89},
    	{143*OVERSAMPLENR, 162.5},
    	{156*OVERSAMPLENR, 158.11},
    	{171*OVERSAMPLENR, 153.71},
    	{187*OVERSAMPLENR, 149.32},
    	{205*OVERSAMPLENR, 144.93},
    	{224*OVERSAMPLENR, 140.54},
    	{245*OVERSAMPLENR, 136.15},
    	{268*OVERSAMPLENR, 131.76},
    	{293*OVERSAMPLENR, 127.36},
    	{320*OVERSAMPLENR, 122.97},
    	{348*OVERSAMPLENR, 118.58},
    	{379*OVERSAMPLENR, 114.19},
    	{411*OVERSAMPLENR, 109.8},
    	{445*OVERSAMPLENR, 105.4},
    	{480*OVERSAMPLENR, 101.01},
    	{516*OVERSAMPLENR, 96.62},
    	{553*OVERSAMPLENR, 92.23},
    	{591*OVERSAMPLENR, 87.84},
    	{628*OVERSAMPLENR, 83.45},
    	{665*OVERSAMPLENR, 79.05},
    	{702*OVERSAMPLENR, 74.66},
    	{737*OVERSAMPLENR, 70.27},
    	{770*OVERSAMPLENR, 65.88},
    	{801*OVERSAMPLENR, 61.49},
    	{830*OVERSAMPLENR, 57.09},
    	{857*OVERSAMPLENR, 52.7},
    	{881*OVERSAMPLENR, 48.31},
    	{903*OVERSAMPLENR, 43.92},
    	{922*OVERSAMPLENR, 39.53},
    	{939*OVERSAMPLENR, 35.13},
    	{954*OVERSAMPLENR, 30.74},
    	{966*OVERSAMPLENR, 26.35},
    	{977*OVERSAMPLENR, 21.96},
    	{985*OVERSAMPLENR, 17.57},
    	{993*OVERSAMPLENR, 13.18},
    	{999*OVERSAMPLENR, 8.78},
    	{1004*OVERSAMPLENR, 4.39},
    	{1008*OVERSAMPLENR, 0}
    };
    #endif
    

    I inserted that table, changed the thermistor selection, reloaded the firmware, and ran the same test as before, which produced this result:

    Rescaled extruder thermocouple
    Rescaled extruder thermocouple

    The stock thermistor and the thermocouple now report essentially the same values, which is entirely due to the new table. The two additional lines come from two more thermocouples taped to the nozzle and dangling downward toward the platform:

    M2 - Hot end with additional thermocouples
    M2 – Hot end with additional thermocouples

    Given that I simply taped those thermistors in place, they don’t contact the nozzle nearly as well as the epoxied sensors. The fact that one reads a bit higher and the other much lower could be explained by handwaving, but one possibility is that the various thermocouples don’t quite agree with each other.

    Time for some calibration along those lines, methinks…

  • Logitech Dual Action Gamepads: Mac vs. PC

    Turns out that there’s no difference between the Mac and PC versions of the Logitech Dual Action Gamepad:

    Logitech Dual Action Gamepads - Mac vs PC
    Logitech Dual Action Gamepads – Mac vs PC

    I picked up a Mac version cheap from the usual eBay seller and discovered that LinuxCNC / HAL was perfectly happy. That wasn’t too surprising; they have the same model and part numbers. Most likely, the only difference was the CD and maybe the Quick Start Guide that I didn’t get in the opened retail box…

    So now I have either a hot backup for the Joggy Thing or one for a different box.

    Most likely, it was cheap because nobody wants a blue-and-black peripheral next to their shiny white Mac…