Unique Teensy Serial Number: using getTeensySerial() from TeensyID.h

Status
Not open for further replies.

Davidelvig

Well-known member
I'm using a Teensy 3.2-type board with a PJRC boot loader.

Stefan Staub's Teensy ID library returns the following:

From my Teensy 3.2-type board with PJRC boot loader
USB Serialnumber: 4294967295
Code:
Array Serialnumber: FF-FF-FF-FF
String Serialnumber: ff-ff-ff-ff
Array MAC Address: 04:E9:E5:FF:FF:FF
String MAC Address: 04:e9:e5:ff:ff:ff
Array 128-bit UniqueID from chip: 95710000-E0CD001A-00385009-49634E45
String 128-bit UniqueID from chip: 95710000-e0cd001a-00385009-49634e45
Array 128-bit UUID RFC4122: 00385009-4963-404E-8045-04E9E5FFFFFF
String 128-bit UUID RFC4122: 00385009-4963-404e-8045-04e9e5ffffff

From off-the-shelf Teensy 3.2
Code:
USB Serialnumber: 1484710
Array Serialnumber: 00-02-43-F7
String Serialnumber: 00-02-43-f7
Array MAC Address: 04:E9:E5:02:43:F7
String MAC Address: 04:e9:e5:02:43:f7
Array 128-bit UniqueID from chip: E2710000-879A0021-000F0010-32304E45
String 128-bit UniqueID from chip: e2710000-879a0021-000f0010-32304e45
Array 128-bit UUID RFC4122: 000F0010-3230-404E-8045-04E9E50243F7
String 128-bit UUID RFC4122: 000f0010-3230-404e-8045-04e9e50243f7
(These are outputs from the example code that comes with the TeensyID library).

For no particular reason, I had thought the boot loader would install some serial number.
What steps do I take after flashing my boards to give them a unique SN in that first slot?

Dave
 
You can set it with this code from the WIKI (https://github.com/TeensyUser/doc/wiki/Custom-Serial-Number)

Code:
extern "C"
{
    struct usb_string_descriptor_struct
    {
        uint8_t bLength;
        uint8_t bDescriptorType;
        uint16_t wString[10];
    };

    usb_string_descriptor_struct usb_string_serial_number =
    {
         22,  // 2 + 2*length of the sn string
         3,
         {'M','Y', 'S','N', '0', '0', '0','0', '1', 0},
    };
}

//-----------------
void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  delay(200);
}
 
Thanks!
Are these stored in flash?
Is this modifiable at run-time?
i.e. could I put a default in here, that after flashing, run a method to accept an input (from Serial, let's say) and set the values in usb_string_serial_number?
 
Looks like it is copied at runtime from a pre-programmed Flash region. Probably needs to happen before USB is up. Here the code from the core:
https://github.com/PaulStoffregen/c...2e9215d2c84cbfcd4a57/teensy3/usb_desc.c#L1806

Don't know if it works, but it might be possible to change the linked code to read out the SN from the EEPROM instead of the FLASH. You could then set the value after flashing the firmware. But these are wild guesses only. Might be a fun project however :). Maybe Paul reads this, he will know what's possible and what not...
 
...could I put a default in here, that after flashing, run a method to accept an input (from Serial, let's say) and set the values in usb_string_serial_number?

Yes, that seems to work. I played a bit with setting serial numbers and found two solutions for setting it from the EEPROM. I.e., you only need to write a new value into the EEPROM to set a new s/n. As always, both solutions have advantages and disadvantages.

First a simple sketch to write a s/n (here "MY_COOL_SN") to the EEPROM. It is important that the s/n contains exactly 10 chars and starts at address 0:
Code:
#include "EEPROM.h"

void setup(){
  pinMode(LED_BUILTIN, OUTPUT);
  EEPROM.put(0,"MY_COOL_SN");     // only needed once
}

void loop(){
  digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  delay(500);
}

Solution 1 (no need to change core files)

Generate a file "eepromSN.c" (*.c is important, won't work with *.cpp) and place it in your sketch folder
Code:
#include "Arduino.h"

struct usb_string_descriptor_struct{
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint16_t wString[];
};

struct usb_string_descriptor_struct usb_string_serial_number ={
    22, // 2 + 2*length of the sn string
    3,
    {'1', '0', '0', '0', '0', '0', '0', '0', '0', 0},  // dummy data
};

void startup_late_hook()
{
    uint8_t *p = (uint8_t *)0;
    for (int i = 0; i < 10; i++)
    {
        uint8_t c = eeprom_read_byte(p++);
        usb_string_serial_number.wString[i] = c;
    }
}

This will exchange the serial number in the usb string descriptor by the value it finds in the EEPROM. The exchange takes place in startup_late_hook() which will be called before your sketch but after the usb stack is initialized. Therefore, this only works if the computer enumerates the device after the exchange took place. To make this work we need to shorten the time between the initialization of the USB stack and the call to startup_late_hook(). This can easily be done by adding -DTEENSY_INIT_USB_DELAY_AFTER=1 to the compiler flags. Without this setting the timing might be borderline and the solution might not work for all PCs (worked here, but slow PC).

ADVANTAGE: No change of core files required
DISADVANTAGE: Ugly hack which relies on timing and needs a change in the build system (additional flag -DTEENSY_INIT_USB_DELAY_AFTER=1)


Solution 2 (requires a change in usb_desc.c)

Replace the code in usb_init_serialnumber(void) (see around line 1630 in usb_desc.c) by this:
Code:
 void usb_init_serialnumber(void)
 {
    const unsigned snLen = 10;
    uint8_t *p = (uint8_t *)0;
    for (int i = 0; i < snLen; i++)
    {
       uint8_t chr = eeprom_read_byte(p++);
       usb_string_serial_number_default.wString[i] = chr;
    }
    usb_string_serial_number_default.bLength = snLen * 2 + 2;
}

This will read the s/n chars from the eeprom instead of the PJRC pre programmed flash section.
ADVANTAGE: Sets the s/n before the usb stack is up => no timing issues
DISADVANTAGE: Needs a change in the core files, need to redo this on every Teensyduino upgrade

Both solutions generate this:

Anmerkung 2020-12-07 193631.jpg


REMARK
Since the board reports the s/n stored in the bootloader during programming, uploaders might get confused if the s/n changes during a programming cycle. Might be best to first read out the bootloader s/n and use this to program your s/n. I assume you need this in a production environment? If so, the complete process can be fully automated:
  1. Switch board to bootloader mode
  2. Read out s/n
  3. upload some setup FW which reads s/n from serial and stores it in eeprom
  4. upload final FW which will then use the same s/n as stored in the bootloader chip
 
Thanks so much for explaining these options!
I'm using a solution like #1 for setting my device's USB-MIDI name.
Now that I think of it, I wonder if it has the same timing limitations - that it might not reliably set the name if USB connection is made before program execution.
I have seen seemingly random failures to name the device.

I like the more deterministic result of Solution 2, and will consider the downside of every-Arduino-update code maintenance.

Both of these options are a bit closer-to-the-metal than my typical programming comfort level.
I could "programmer-up", I suppose.

Thanks again!
 
Status
Not open for further replies.
Back
Top