Teensyduino multiple devices flash at the same time

Dogbone06

Well-known member
Hello all!

I know I've seen somewhere a long time ago that Teensyduino in some mode can flash multiple devices at once?
Might be that CLI version of it. I am unsure.

What I'm after is to flash X devices at once at one computer. Let's just say 20 devices at once or something. Or if it's fully dynamic, meaning that if we have 20 devices plugged in, at any time I unplug X devices and plug in new ones and the flash tool would just flash any devices that's in boot mode.

The latter is probably not possible, but let's see what is.

Thanks!
 
Using CLI this is possible, as the first step is to find one or more Teensies. You can determine in which state it is: Bootmode has an own PID code. Standard CLI code must, however, be extended to loop through all Teensies on PC.
 
Teensy Loader's "Auto" mode is meant to make programming many boards easy. You just load the HEX file once and set Auto mode. If using Arduino IDE, it does this after every successful Verify. Then you plug in each Teensy or custom PCB with T3/T4 bootloader chip and press the Program button.

It only programs 1 at a time. But the loading process is only a matter of seconds. As a practical matter, you'll spend more time physically moving the boards and cable than the programming time takes.

Gang programming (where many boards are programmed in parallel) is needed when the programming time is long. Those types of systems are also ultimately limited by the time taken for operators to physically move the hardware in and out of the programmer. For a device that takes 1 minute to program, you would probably want 5 to 10 in parallel. Doing 20 in parallel just doesn't make sense unless the operator can physically move 19 in and out of the programmer during the time 1 is writing.

Of course at some incredibly high volume you might want to fully automate the physical handling of products. But that sort of robotic system is very expensive, both physical hardware and initial setup costs. Unless you're manufacturing an amazing volume of products, it just doesn't make financial sense for an operation you can pay a human to perform in a matter of seconds. Consider if a human takes a total of 20 seconds (a few of those waiting for the actual programming) per product, they process 180 per hour. Over an 8 hour shift, just 1 person can program 1440 units, though in practice you would probably assign at least an hour each shift to other tasks like arranging and counting inventory, setup, cleanup, etc. Still you can pretty easily get 1000 products programmed per employee on each 8 hour shift. If you need more than 1000 per workday, you can just hire a 2nd or 3rd person and install a 2nd and 3rd computer. Of course there is some daily volume where the cost and effort to set up a fully robotic system is less than simply paying human workers, but for this sort of work where where you handle a product and plug in a cable, human labor is the way to go if your production volume is under a few thousand per day.

But if you really want to try optimizing anyway, the simple thing to try would be using 2 computers per person, both of course running Teensy Loader in auto mode. Then they could plug in a board to the 2nd cable (to 2nd computer) and press the button while a board on the 1st cable (to 1st computer) is programming the first. Or even 3 computers.... but I'm pretty sure you'll discover the point of diminishing returns is between 1 to 2 computers. Gang programming many devices only makes sense if the time taken for programming allows a human to physically move that many others in and out of the programmer during the programming time.
 
Thanks Paul for your input. 5 - 10 makes sense. Programming is quite long, file is 10MB. How can I "gang program" ?
Is it fully dynamic so that as soon as a new device with bootmode active, appers in the computer, it'll program it?

I would very much appreciate if you can tell me what program to use and if there's some instructions to get "gang programming" going.
 
OK, 8 years ago I modified the PJRC CLI code (not used is for years now), but nowadays I would write a python script that first scans all com-ports and hid-devices for a teensy. I would then identify the teensy type (only needed if mixed models are present). Finally I would execute a command that takes the right teensy port from the list and download the hex code.
 
I'm trying to see if there is such solution available already, it'll take me far to much time to set something like that up.
 
Programming is quite long, file is 10MB. How can I "gang program" ?

Simplest way is to use several small form factor PCs or even Raspberry Pi. Yeah, it may be "inefficient" hardware-wise, but it's easy to do. A good quality KVM switch for keyboard and mouse might make maintenance easier. Main decision if whether you want a screen for each, or just watch the red LED on each Teensy to indicate programming is in progress and when it completes.
 
You're right. I have small 7" touch screen windows PC's from China (GOLE1), they're fast and simple, and cheap. I'll use a bunch of them!
 
If you are using windows you can use TeensySharp for such things. I did a quick proof of principle using this c# script:

C#:
using libTeensySharp;

var watcher = new TeensyWatcher(); // provides a list of connected Teensies

foreach (var teensy in watcher.ConnectedTeensies)  // print the list
{
    Console.WriteLine(teensy.ToString());
}

string filename = "test2.hex";
Console.WriteLine("\nStart programming...");
if (File.Exists(filename))
{
    var tasks = new List<Task>();  // generate an uploading task for each teensy
    foreach(var teensy in watcher.ConnectedTeensies)
    {
        Console.WriteLine($"Starting uploading to {teensy.ToString()}");
        tasks.Add(teensy.UploadAsync(filename));
    }
    await Task.WhenAll(tasks);  // wait until all tasks are finished   
    Console.WriteLine("...done");
}
else { Console.WriteLine("File not found"); }

Console.WriteLine("\npress any key to quit");
while (!Console.KeyAvailable) ;

I tested parallel uploading of a 220kB sketch to two connected teensies which worked without issues. Extending this code to start a new upload whenever a Teensy is connected (or switched to bootloader if this is prefered) should be straight forward.

(Please not that this code is just a quick hack and lacks any error handling which you would certainly need for production code.)
 
Last edited:
If you are using windows you can use TeensySharp for such things. I did a quick proof of principle using this c# script:

C#:
using libTeensySharp;

var watcher = new TeensyWatcher();  // provides an auto updated list of connected Teensies

foreach (var teensy in watcher.ConnectedTeensies)  // print the current list
{
    Console.WriteLine(teensy.ToString());
}

Console.WriteLine("Start programming...");
string filename = "test2.hex";
if (File.Exists(filename))
{
    await Task.WhenAll(watcher.ConnectedTeensies.Select(t => t.UploadAsync(filename)));   // start an uploading task for each teensy in the list
    Console.WriteLine("...done");
}
else { Console.WriteLine("File not found"); }

Console.WriteLine("press any key to quit");
while (!Console.KeyAvailable) ;

I tested parallel uploading of a 220kB sketch to two connected teensies which worked without issues. Extending this code to start a new upload whenever a Teensy is connected (or switched to bootloader if this is prefered) should be straight forward.

(Please not that this code is just a quick hack and lacks any error handling which you would certainly need for production code.)
That's cool, thank you!
 
BTW: for easy installation the library can be found on nuget. Documentation is a bit out of date but the examples in the gitHub repo should show how to use it.
 
As I mentioned a python script, I just did a quick and dirty one and tested it on two teensies hooked up to a USB hub
Code:
import subprocess
import time
import serial
from serial.tools.list_ports import comports
sx=comports(True)
for s in sx:
    #
    with serial.Serial(s.device,134) as ser:
        print(s.device,"0x{0:0{w}x}".format(s.vid,w=4),"0x{0:0{w}x}".format(s.pid,w=4),s.serial_number)
    #
    time.sleep(1)

    cmd="C:/Program Files/PowerShell/7/pwsh.exe -C "+"teensy_loader_cli.exe "+"--mcu=TEENSY41 -w "+ "blink_slow.ino.hex"
    print(cmd)
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    for line in p.stdout.readlines():
        print(line)
    retval = p.wait()
    time.sleep(1)
the sleeps were inserted to give usb/teensy/PC sufficient time to react.
The 134 baud trick to put Teensy into programming mode only works with usb-serial enabled. for hid(Ser-emu) another command is required
 
Last edited:
I'm not very fluent in python. So, I wonder if this is really programming the boards in parallel or one after the other?
 
I'm not very fluent in python. So, I wonder if this is really programming the boards in parallel or one after the other?
One after the other. Essentially, it first gets a list of all teensies on the PC (com ports), then loops through the list and puts each teensy into programming mode and uses the standard cli program to upload the program. The idea was to use the existing programs and it worked. The PJRC CLI-program only handles one Teensy at a time.

Now, as you know, for seremu, where there is no com port, some bytes have to be send. This is not implemented in the adhoc program, but could be done. Also, the case that the teensy is already in programming mode, i.e. checking for PID=0x0478, needs to be done, as then the download part can be executed immediately.
 
Stimulated by the OP I spent some time to script the uploading of a hex file to multiple teensies. The uplad is sequentially and uses standard CLI uploader. I tested it in all three modes program-button pressed, seremu and serial. Maybe it may help someone.
Python:
# hexfile to be downloaded
fname="blink_slow.ino.hex"


# PIDs from usb_desc.h
list_seremu=(0x0482, # rawhid-seremu
                0x0485, # midi-seremu
                0x0486, # rawhid-seremu
                0x0488, # flightsimulator-seremu
                0x04D1, # MTP-seremu
                0x04D2, # audio-seremu
                0x04D0, # keyboard-seremu
                0x04D3, # touchscreen-seremu
                0x04D4 # hid-touchscreen-seremu
                )
list_serial=(0x0476, # everything-serial
                0x0476, # mtp-serial (WMXZ:Hopefully Paul assigns a unique pid to mtp-serial)
                0x0483, # serial
                0x0487, # rawhid-serial
                0x0489, # midi-serial
                0x048A, # midi-audio-serial
                0x048B, # dual serial
                0x048C, # tripple serial
                )   # list_serial is not used but added for completeness

import hid          # "pip install hid" needs for windows also hidap.lib, hidapi.dll (I have them in local directory)
import time
import subprocess

def setProgram(path):
    hidraw = hid.device()
    hidraw.open_path(path)
    hidraw.send_feature_report([0, 0xA9, 0x45, 0xC2, 0x6B])
    time.sleep(1)
    return

def loadProgram(filename):  
    time.sleep(1)
    cmd="C:/Program Files/PowerShell/7/pwsh.exe -C "+"teensy_loader_cli.exe "+"--mcu=TEENSY41 -w "+ filename
    print(cmd)
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    time.sleep(1)
    return

hid_list=hid.enumerate(0x16C0)
for device_dict in hid_list:
    pid=device_dict['product_id']
    if pid==0x478:
        # programming mode
        loadProgram(fname)
    if pid in list_seremu:
        if device_dict['usage']==4:
            print("0x{0:0{w}x}".format(device_dict['vendor_id'],w=4),"0x{0:0{w}x}".format(device_dict['product_id'],w=4),
                    device_dict['serial_number'],device_dict['interface_number']);
            #
            setProgram(device_dict['path'])
            loadProgram(fname)
#
import serial
from serial.tools.list_ports import comports
#
sx=comports(True)
for s in sx:
    #
    if s.vid==0x16C0:
        with serial.Serial(s.device,134) as ser:
            print(s.device,"0x{0:0{w}x}".format(s.vid,w=4),"0x{0:0{w}x}".format(s.pid,w=4),s.serial_number)
        #
        time.sleep(1)

        loadProgram(fname)

Obviously, the need to adapt to users needs is up to users, but the code should give an idea on how it can be done.
 
Back
Top