Production serial number, Teensy 4

DrM

Well-known member
Hi,

Is there a production compatible way to set serial numbers for the Teensy 4?

I think the most convenient solution would be if we could write over the serial number in the image flashed to the board.

Another, might be if there is a command-line build. If so, we could use a define.

Is there any hope for any of these idea?

Thank you
 
Hi,

Is there a production compatible way to set serial numbers for the Teensy 4?

I think the most convenient solution would be if we could write over the serial number in the image flashed to the board.

Another, might be if there is a command-line build. If so, we could use a define.

Is there any hope for any of these idea?

Thank you

Can't tell for sure which of the following you are looking to do:

1) access an existing unique serial number for each Teensy

2) overwrite any existing unique serial number for each Teensy with your own unique serial number

3) create your own unique serial number storage for each Teensy

Also, which T4 ??

For #1, each T4.1 has a unique MAC address. Would that work as a unique serial number for your intended use ??

For #3, would a simple #define be sufficient for your intended use ??

Also for #3, you could just program your unique serial number into EEPROM. Would that work for your intended use ??

Also for #3, there are a few non-volatile bytes which can be utilized in the RTC (may require the attachment of the backup battery). Would that work for your intended use ??

Hope that helps . . .

Mark J Culross
KD5RXT
 
Teensy Serial number is derived from a fixed read only MAC value on each chip set by NXP at the factory: ...\hardware\teensy\avr\cores\teensy4\usb_desc.c
Code:
void usb_init_serialnumber(void)
{
	char buf[11];
	uint32_t i, num;

	num = HW_OCOTP_MAC0 & 0xFFFFFF;
	// add extra zero to work around OS-X CDC-ACM driver bug
	if (num < 10000000) num = num * 10;
	ultoa(num, buf, 10);
	for (i=0; i<10; i++) {
		char c = buf[i];
		if (!c) break;
		usb_string_serial_number_default.wString[i] = c;
	}
	usb_string_serial_number_default.bLength = i * 2 + 2;
}

With a CORES edit the stored/reported serial number could be anything code can do.
 
So, turning on verbose, I managed to capture the following from the IDE. It seems informative, although there are more than a few mysteries, for example why it repeats the compilation three times each time adding one more library. (I am guessing the ide created the directory /tmp/arduino_build_5983 and that it hangs around until it is closed). But, at any rate, it seems to produce and load an image that boots.

Is it feasible to simply run this to prepare boards and just add another define, something like -DSN=<serialnumber> ?



Code:
/home/nelson/Arduino/arduino-1.8.19/arduino-builder \
    -dump-prefs \
    -logger=machine \
    -hardware /home/nelson/Arduino/arduino-1.8.19/hardware \
    -tools /home/nelson/Arduino/arduino-1.8.19/tools-builder \
    -tools /home/nelson/Arduino/arduino-1.8.19/hardware/tools/avr \
    -built-in-libraries /home/nelson/Arduino/arduino-1.8.19/libraries \
    -libraries /home/nelson/Arduino/Sketchbook/libraries \
    -fqbn=teensy:avr:teensy40:usb=serial,speed=600,opt=o2std,keys=en-us \
    -vid-pid=16C0_0483 \
    -ide-version=10819 \
    -build-path /tmp/arduino_build_598263 \
    -warnings=none \
    -build-cache /tmp/arduino_cache_936807 \
    -verbose \
    /home/nelson/Projects/TeensySpectrometer/Firmware/SpectrometerILX511_SPI/SpectrometerILX511_SPI.ino

/home/nelson/Arduino/arduino-1.8.19/arduino-builder \
    -compile \
    -logger=machine \
    -hardware /home/nelson/Arduino/arduino-1.8.19/hardware \
    -tools /home/nelson/Arduino/arduino-1.8.19/tools-builder \
    -tools /home/nelson/Arduino/arduino-1.8.19/hardware/tools/avr \
    -built-in-libraries /home/nelson/Arduino/arduino-1.8.19/libraries \
    -libraries /home/nelson/Arduino/Sketchbook/libraries \
    -fqbn=teensy:avr:teensy40:usb=serial,speed=600,opt=o2std,keys=en-us \
    -vid-pid=16C0_0483 \
    -ide-version=10819 \
    -build-path /tmp/arduino_build_598263 \
    -warnings=none \
    -build-cache /tmp/arduino_cache_936807 \
    -verbose \
    /home/nelson/Projects/TeensySpectrometer/Firmware/SpectrometerILX511_SPI/SpectrometerILX511_SPI.ino

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++ \
    -E -CC -x c++ -w -g -Wall -ffunction-sections -fdata-sections -nostdlib -std=gnu++14 -fno-exceptions \
    -fpermissive -fno-rtti -fno-threadsafe-statics -felide-constructors -Wno-error=narrowing -mthumb \
    -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 \
    -D__IMXRT1062__ -DTEENSYDUINO=157 -DARDUINO=10819 -DARDUINO_TEENSY40 -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 \
    /tmp/arduino_build_598263/sketch/SpectrometerILX511_SPI.ino.cpp \
    -o /dev/null

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++ \
    -E -CC -x c++ -w -g -Wall -ffunction-sections -fdata-sections -nostdlib -std=gnu++14 -fno-exceptions \
    -fpermissive -fno-rtti -fno-threadsafe-statics -felide-constructors -Wno-error=narrowing -mthumb \
    -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 \
    -D__IMXRT1062__ -DTEENSYDUINO=157 -DARDUINO=10819 -DARDUINO_TEENSY40 -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/libraries/ADC \
    /tmp/arduino_build_598263/sketch/SpectrometerILX511_SPI.ino.cpp \
    -o /dev/null

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++ \
    -E -CC -x c++ -w -g -Wall -ffunction-sections -fdata-sections -nostdlib -std=gnu++14 -fno-exceptions \
    -fpermissive -fno-rtti -fno-threadsafe-statics -felide-constructors -Wno-error=narrowing -mthumb \
    -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 \
    -D__IMXRT1062__ -DTEENSYDUINO=157 -DARDUINO=10819 -DARDUINO_TEENSY40 -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/libraries/ADC \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/libraries/EEPROM \
    /tmp/arduino_build_598263/sketch/SpectrometerILX511_SPI.ino.cpp \
    -o /dev/null

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++ \
    -E -CC -x c++ -w -g -Wall -ffunction-sections -fdata-sections -nostdlib -std=gnu++14 -fno-exceptions \
    -fpermissive -fno-rtti -fno-threadsafe-statics -felide-constructors -Wno-error=narrowing -mthumb \
    -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 \
    -D__IMXRT1062__ -DTEENSYDUINO=157 -DARDUINO=10819 -DARDUINO_TEENSY40 -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/libraries/ADC \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/libraries/EEPROM \
    /tmp/arduino_build_598263/sketch/SpectrometerILX511_SPI.ino.cpp \
    -o /tmp/arduino_build_598263/preproc/ctags_target_for_gcc_minus_e.cpp

/home/nelson/Arduino/arduino-1.8.19/tools-builder/ctags/5.8-arduino11/ctags \
    -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives \
    /tmp/arduino_build_598263/preproc/ctags_target_for_gcc_minus_e.cpp

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/precompile_helper \
    /home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 \
    /tmp/arduino_build_598263 /home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++ \
    -x c++-header -O2 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -std=gnu++14 -fno-exceptions \
    -fpermissive -fno-rtti -fno-threadsafe-statics -felide-constructors -Wno-error=narrowing -mthumb \
    -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 \
    -D__IMXRT1062__ -DTEENSYDUINO=157 -DARDUINO=10819 -DARDUINO_TEENSY40 -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 \
    /tmp/arduino_build_598263/pch/Arduino.h \
    -o /tmp/arduino_build_598263/pch/Arduino.h.gch

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++ \
    -c -O2 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -std=gnu++14 -fno-exceptions \
    -fpermissive -fno-rtti -fno-threadsafe-statics -felide-constructors -Wno-error=narrowing -mthumb \
    -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 \
    -D__IMXRT1062__ -DTEENSYDUINO=157 -DARDUINO=10819 -DARDUINO_TEENSY40 -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH \
    -I/tmp/arduino_build_598263/pch \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/libraries/ADC \
    -I/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/libraries/EEPROM \
    /tmp/arduino_build_598263/sketch/SpectrometerILX511_SPI.ino.cpp \
    -o /tmp/arduino_build_598263/sketch/SpectrometerILX511_SPI.ino.cpp.o

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-gcc \
    -O2 -Wl,--gc-sections,--relax \
    -T/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/avr/cores/teensy4/imxrt1062.ld -mthumb \
    -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 \
    -o /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.elf \
    /tmp/arduino_build_598263/sketch/SpectrometerILX511_SPI.ino.cpp.o \
    /tmp/arduino_build_598263/libraries/ADC/ADC.cpp.o \
    /tmp/arduino_build_598263/libraries/ADC/ADC_Module.cpp.o \
    /tmp/arduino_build_598263/libraries/ADC/AnalogBufferDMA.cpp.o \
    /tmp/arduino_build_598263/libraries/EEPROM/EEPROM.cpp.o \
    /tmp/arduino_build_598263/../arduino_cache_936807/core/core_52ce2e9d62f4f696f626411e3c70716d.a \
    -L/tmp/arduino_build_598263 -larm_cortexM7lfsp_math -lm -lstdc++

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-objcopy \
    -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings \
    --change-section-lma .eeprom=0 \
    /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.elf \
    /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.eep

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-objcopy \
    -O ihex -R .eeprom \
    /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.elf /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.hex

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/teensy_secure encrypthex TEENSY40 /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.hex

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/teensy_post_compile \
    -file=SpectrometerILX511_SPI.ino -path=/tmp/arduino_build_598263 \
    -tools=/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/ \
    -board=TEENSY40

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/stdout_redirect \
    /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.sym \
    /home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-objdump -t \
    -C /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.elf

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/teensy_size \
    /tmp/arduino_build_598263/SpectrometerILX511_SPI.ino.elf

/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools/teensy_post_compile \
    -file=SpectrometerILX511_SPI.ino \
    -path=/tmp/arduino_build_598263 \
    -tools=/home/nelson/Arduino/arduino-1.8.19/hardware/teensy/../tools \
    -board=TEENSY40 \
    -reboot \
    -port=/dev/ttyACM0 -portlabel=/dev/ttyACM0 -portprotocol=serial
 
@defragster Thank you. That sounds great. What is CORES edit, and how do I do it? Could I set the manufacturer and product strings too?
 
BTW, the interrupts and timers are running fantastically well. Thank you to everybody for fantastic help.
 
So, turning on verbose, I managed to capture the following from the IDE. It seems informative, although there are more than a few mysteries, for example why it repeats the compilation three times each time adding one more library. (I am guessing the ide created the directory /tmp/arduino_build_5983 and that it hangs around until it is closed). But, at any rate, it seems to produce and load an image that boots.
...

The IDE does some pre-processing work to find and locate the needed libraries and organize the INO's and pre-define for the user functions declared after use, so the user need not do that. In lieu of a MAKE created and maintained by the user - the IDE dynamically discovers needed sources in this multi pass process.
 
@defragster Great. So here is what works so far, and what I am thinking to do.

The code snipped below, works. So, the idea is modify the above script to add the following to define the serial number string for each compilation.

(i.e., to the -Dstuff that it already does, just append the following -DthisPRODUCT_SERIAL_NUMBER=something -DthisPRODUCT_SERIAL_NUMBER _LEN=itslengths)

What do you think?


Code:
#define thisMANUFACTURER_NAME {'s','o','m','e','_','v','e','n','d','o','r','x'}
#define thisMANUFACTURER_NAME_LEN 12

#define thisPRODUCT_NAME {'s','o','m','e','p','r','o','d','u','c','t','x'}
#define thisPRODUCT_NAME_LEN 12

#ifndef thisPRODUCT_SERIAL_NUMBER 
#define thisPRODUCT_SERIAL_NUMBER { '0','0','0','0','0','0','0','0','0','0','0','0' }
#define thisPRODUCT_SERIAL_NUMBER_LEN 12
#endif

extern "C"
{
  struct usb_string_descriptor_struct_manufacturer
  {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint16_t wString[thisMANUFACTURER_NAME_LEN];
  };

  usb_string_descriptor_struct_manufacturer usb_string_manufacturer_name = {
    2 + thisMANUFACTURER_NAME_LEN * 2,
    3,
    thisMANUFACTURER_NAME
  };
  
  struct usb_string_descriptor_struct_product
  {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint16_t wString[thisPRODUCT_NAME_LEN];
  };

  usb_string_descriptor_struct_product usb_string_product_name = {
    2 + thisPRODUCT_NAME_LEN * 2,
    3,
    thisPRODUCT_NAME
  };

  struct usb_string_descriptor_struct_serial_number
  {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint16_t wString[thisPRODUCT_SERIAL_NUMBER_LEN];
  };

  usb_string_descriptor_struct_serial_number usb_string_serial_number =
    {
      2 + thisPRODUCT_SERIAL_NUMBER_LEN * 2, 
      3,
      thisPRODUCT_SERIAL_NUMBER
    };
}
 
Those early gcc commands are with "-E", which gcc documents as "Preprocess only; do not compile, assemble or link". Arduino uses this to discover which libraries are actually needed. Old versions of Arduino only parsed the main .ino file and used simple regex patterns. Modern Arduino IDE looks in all the .cpp files. Running the preprocessor (gcc -E) is required to properly deal with #ifdef surrounding a #include. Each new include discovered could have more #define lines, so the process of faithfully finding all actually included files is iterative.

The data in HW_OCOTP_MAC0 is written by PJRC during product testing. NXP ships IMXRT chips with those fuse bits all zeros.

Fuse memory is not erasable. Once a 0 bit is changed to 1, it can never go back to 0. Additionally, certain groups of fuses have a write-inhibit fuse bit, which once changed to 1 restricts any further changes within that group. Altering the fase-based mac address is impossible by software.

The USB code initializes a memory buffer with the serial number converted to USB string format. If you overwrite that buffer, you can cause anything you like to be transmitted in the USB string descriptor when your PC asks for the serial number. The trick is you must do this before the PC requests that descriptor. Unfortunately there is no simple way to do this without editing the core library. The middle startup hook runs before the 20 ms delay and USB initialization and late hook runs just before C++ constructors. The serial number string buffer is written during USB initialization, so anything you do from the middle hook will be overwritten. The late hook might still work, but if your PC is fast it could be too late by that point.

Perhaps in future versions we should break the USB initialization into 2 steps, to put initialization of USB descriptor buffers before the middle startup hook.
 
@PaulStoffregen Hi Paul, thank you. I think I have go this part under control for the time being. I am about to post another more serious question on another thread/
 
...
The data in HW_OCOTP_MAC0 is written by PJRC during product testing. NXP ships IMXRT chips with those fuse bits all zeros.
...

Opps - knew note was a bit wrong as PJRC gets to control Ser# progression.

Also wondered about User code timing with a hook versus CORES edit - and noticed there is a 'weak' on the related Ser# string?
 
@defragster Yep, I used that. I think I found it mentioned in one of your old posts and then tracked it down in Paul's source codes.
 
Back
Top