Generating a UUID - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 21, 2023, 08:20:19 pm
content: I have a need to generate anRFC 4122-compliant UUID on a microcontroller. Its purpose is to be presented to a master system that talks to the MCU to identify each instance of add-on board (kind of like a serial number, but globally unique). I believe this is primarily so that the system can identify when more than one instance of the same board (i.e. with identical product and vendor IDs) is present, but I don't think it does anything else with the UUID.However, I'm not sure how I can construct such a UUID given the limited facilities available to me.For a start, I don't have any time-keeping available (only a millisecond timestamp since power-on), so I can't create a Version 1 UUID that is based on a UTC timestamp ("the current time as a 60-bit count of 100-nanosecond intervals since 00:00:00.00, 15 October 1582"). What I do have available is a 64-bit unique ID from the MCU that is (or should be!) distinct for each chip. (Or is it 96-bit? The chip has three 32-bit registers containing the unique ID, but perhaps one is a non-unique value.)A Version 4 random UUID isn't suitable, because that would change every time, and this UUID is supposed to be persistently consistent for a given piece of hardware.So I'm thinking that maybe I could construct a Version 3 or 5 "Named-based" UUID? But that involves hashing using MD5 or SHA-1, and I'm hesitant to implement that on a low-powered MCU.Any suggestions?

author: eutectique
author_rank: Frequent Contributor
timestamp: November 21, 2023, 10:10:43 pm
content: Quote from: HwAoRrDk on November 21, 2023, 08:20:19 pmStrange requirement.If youstrictlyneed to generate it on the µC, you can use chip ID as a random number. Consult the relevant chapter of the data sheet about its length.Can you read the chip ID at production time, generate UUID withuuidgenutility, and write it to the µC?

author: zilp
author_rank: Frequent Contributor
timestamp: November 21, 2023, 10:14:45 pm
content: Quote from: HwAoRrDk on November 21, 2023, 08:20:19 pm"low-powered" as in?

author: ejeffrey
author_rank: Super Contributor
timestamp: November 21, 2023, 10:20:55 pm
content: The UUID is assigned once and stays constant forever? Do you have NVRAM to store it or does it need to be derived from the hardware?If you have NVRAM to store it and you have a programming / test phase of production can you just generate the UUID on the programming host and write it at that time? Alternately does you microcontroller have a built in hardware unique ID? If so, pre-generate the high bits once using whatever chosen method you like then fill in the low bits with the hardware ID.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 21, 2023, 10:23:06 pm
content: If you need a UUID with 128 bits and you have less than this available on your chip, I'm not sure how you can do that reliably.You can always do some twiddling on the 64- or 96-bit unique ID of your MCU to generate a 128-bit value, but this will never be guaranteed to be really unique. Maybe it can be made "good enough" for your purpose.One simple, if maybe less practical solution from a production POV (if you have to mass-produce this), is just to generate UUIDs at production time on a computer and flash them on each MCU. Problem solved.

author: zilp
author_rank: Frequent Contributor
timestamp: November 21, 2023, 10:35:30 pm
content: Quote from: SiliconWizard on November 21, 2023, 10:23:06 pmWell, technically true--but if you use a cryptographic hash function, the collision probability introduced by the hashing is negligible.

author: nctnico
author_rank: Super Contributor
timestamp: November 21, 2023, 11:14:32 pm
content: Quote from: zilp on November 21, 2023, 10:35:30 pmThat depends on the length of the input. If your input is 1 bit, then you'll only get 2 possible hashes. The simplest thing to do is to repeat the chip's ID until the length requirement of the UUID is satisfied.BTW: I don't feel comfortable at all with generating 'unique' identifiers using some kind of randomizer. A serial number from a chip which is incremental will be unique for sure. A random generated ID will have duplicates. Murphy's law will make sure of that. I'd only use a random number if there is no other option at all.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 21, 2023, 11:27:18 pm
content: Well, it's a pretty moot point regardless of whether or not I have the CPU horsepower to calculate a MD5/SHA-1 hash with the MCU in a reasonable amount of time. I just grabbed apublic domain MD5 implementationand compiled it in, and it's ballooned my firmware size way over the amount of flash space available. So constructing a Version 3 name-based UUID on-device is out.I was really hoping to have a common firmware image that can be just programmed as-is onto every device, hence why I was hoping to take advantage of the MCU's unique hardware ID.If I wanted to generate the UUID at programming time based on the chip's hardware ID, I would have to re-compile the firmware for each and every device, because I don't have any NVRAM (e.g. EEPROM) to store it separately. I would really rather avoid a distinct firmware image per-device.I wonder if perhaps I could do the following:- Generate a single 'master' Version 1 time-based UUID.- Include that in my firmware.- On-demand, substitute the most-significant 48 bits (the 'node' field) with part of the MCU's hardware ID.I mean, the MCU's hardware ID isn't too different from a MAC address (what conventionally goes in the 'node' field), is it? I would just need to determine what part of the chip's 64-bit (or 96-bit? - TBD) hardware is suitable for truncation to 48 bits. If the manufacturer bases the hardware ID on a serial number, then I suppose there may be certain bits of the ID that are more likely not to change from chip to chip.

author: dobsonr741
author_rank: Frequent Contributor
timestamp: November 21, 2023, 11:41:55 pm
content: I do not see why would you need unique firmware for each device, or customize the FW image. Try to externalize and generate one externally and program in EEPROM or a region of the Flash at MCU programming time. Is the device SWD capable?If you need dynamically regenerated UUIDs, from time to time, and you have no timekeeping, even more so when you need cryptographically strong UUID, you need a crypto chip with TRNG in it. Not a big deal, such I2C devices costs <$1. And a UUID might not be secure - though not sure it is your case.

author: eutectique
author_rank: Frequent Contributor
timestamp: November 22, 2023, 12:01:52 am
content: Quote from: HwAoRrDk on November 21, 2023, 11:27:18 pmA decent toolchain would be capable of generating s-record files (or intel-hex, for that matter), combining two files together, and flashing them into the device. One file would be the firmware, another is the UUID for that specific device.You said nothing about your MCU, but in an ARM cortex-m device you can reserve a place for the UUID just after the vector table and before your.textsection, looking at the vast majority of linker scripts. You get the drift.

author: ejeffrey
author_rank: Super Contributor
timestamp: November 22, 2023, 12:11:00 am
content: Quote from: SiliconWizard on November 21, 2023, 10:23:06 pmWell it's enough to guarantee uniqueness within the context of instances of your device. At least as long as you trust your MCU vendor. Which is probably good enough but if you want global uniqueness do this:1) Generate a single UUID on your development workstation now, using whatever technique you prefer.2) compile that into your source code as the UUID template3) the UUID is the XOR of the template with the MCU hardware ID4) if you ever move to a different MCU vendor or change the hardware where there is a possibility for collision in the hardware unique ID, regenerate the template.

author: rteodor
author_rank: Frequent Contributor
timestamp: November 22, 2023, 04:14:17 am
content: Quote from: eutectique on November 22, 2023, 12:01:52 amJust set the linker script to reserve space for UUID. Then depending on tools available either patch the image before flashing or do it in two steps: flash once with the same image then set UUID either in another flashing run or via an interface like UART, CAN, Ethernet.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 22, 2023, 07:14:21 am
content: Quote from: HwAoRrDk on November 21, 2023, 11:27:18 pmYes, but unless you envision to make billions of devices and need to ensure a collision probability < 10e-12 among all of them, you probably don't need to resort to such a big weapon.For instance, to generate reasonable 32-bit and 64-bit UIDs (that I never claimed to be standard UUIDs of course) from more data bits, locally on MCUs, I've used Fletcher checksums successfully. Very lightweight, simple and fast. While having some known flaws, that would qualify as "good enough" for a lot of practical applications. Now if you must comply with some standard, of course, forget about that and use the big weapons.Quote from: HwAoRrDk on November 21, 2023, 11:27:18 pmAs several have already suggested, this can easily be done without having to modify the firmware itself. You can flash the UUID (along with other production data if needed) separately using the same tools.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 22, 2023, 08:28:42 am
content: Quote from: nctnico on November 21, 2023, 11:14:32 pmThere is no fundamental problem; length of the random ID just has to be long enough. FYI, I have bad news for you: the whole microcontroller isdesignedto fail randomly through metastability of flip-flops; RAM fails randomly, even ECC RAM fails randomly. It's just designed to failrarely enough. You can do the same analysis for a random ID, and it is much easier to do so, even on a napkin (unlike RAM corruption modeling which requires quite deep semiconductor understanding.)The benefit of randomly generated ID is that it does not need central registration, which in some cases can become a daunting task, and has also surface area for bugs.Quote from: nctnico on November 21, 2023, 11:14:32 pmIncremental ID won't be unique "for sure" if there is a race condition bug in assignment. Or any other bug. Or RAM corruption on the incremental counter, which has similar order of magnitude of probability than generating e.g. random 256-bit identifiers to begin with.UUID is of course more complex than just random alone, utilizing the concept of having synchronized clocks all over the planet (or universe, as they market it). Whether this helps or hinders depend on application.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 24, 2023, 05:32:43 am
content: If I wanted to set the UUID value in an otherwise-generic firmware post-build, does this sound like a decent strategy?1. Declare a variable in my source code for it, placing it in a special section, e.g.:static const uint8_t my_uuid __attribute__((section(".my_uuid"), used, retain))2. At some point post-build:a) Generate the per-unit UUID via whatever means (random, based on chip hardware ID, etc), put the 128-bit raw binary value in a file.b) With objcopy, make a unit-specific copy of the firmware, substituting the UUID value into the special section, e.g.:objcopy -O ihex --update-section .my_uuid=generated_uuid.bin firmware.elf firmware_with_uuid.hexIt seems I need the 'used' and 'retain' attributes to stop the linker garbage-collecting the section because nothing apparently references it. But that doesn't make sense to me because obviously other code references the variable.Should I be putting anything in my linker script for this new section? That is, regarding placement or ordering, or otherwise. Right now it seems to be placing the section after .rodata.*, which I think is a suitable place, but I can't help but feel that's by coincidence, rather than design. Perhaps adding the section to the linker script would avoid the need for the attributes mentioned above?

author: rteodor
author_rank: Frequent Contributor
timestamp: November 24, 2023, 07:08:23 am
content: First of all you did not tell what controller and what compiler you are using. Both the controller and the compiler have specific ways regarding self-flashing.Your mention of '.rodata' section hints at GCC.Quote from: HwAoRrDk on November 24, 2023, 05:32:43 amYes this is very recommended to declare a dedicated section in the linker script. Just like with serial numbers.And it is preferably to put this section away from the rest of the code at a well defined address (towards end of flash for example) and there are reasons for that:1.flash block size: flashing is usually done at a flash erase block size (which might or might not be the same as flash write size). If this dedicated section is placed in the vicinity to other used data/code, then that data/code needs to be copied too before erasing and writing back the block. Software can do that yes, but it shouldn't.2.block alignment: leaving the linker to place the section you might find that the section overlaps over two block sizes. The software can manage this too but it is just much simpler to tell the linker what you want. Every compiler I used had linker commands to allocate a section to a well determined place.3.flash access size: some controllers might have larger access size and caching, for example: the flash is accessed (internally in the MCU) in 128 bytes chunks and if you change data in flash the data in cache has to be updated too. Otherwise if the controller runs code from the immediate vicinity of the updated flash area it my crash. This is valid usually for larger controllers/SoC's and it is very specific. In the example above a 128 bytes section could be reserved entirely even if only 5 bytes are used.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: November 24, 2023, 07:36:14 am
content: Quote from: rteodor on November 24, 2023, 07:08:23 amHuh?Who said anything about self-flashing? That's not what I'm doing here.And I'm not proposing in my previous post to update the UUID location in flash with a second programming operation, after the main firmware flashing. No, I'm talking about substituting parts of the firmware imagebeforeflashing.

author: rteodor
author_rank: Frequent Contributor
timestamp: November 24, 2023, 08:26:28 am
content: Quote from: HwAoRrDk on November 24, 2023, 07:36:14 amUh, it seems that my morning didn't startAnd I have to run already...Never tried this updating of elf file but I don't see why it should not work.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 24, 2023, 08:30:07 am
content: Most MCUs have special places in flash which are not erased through the normal chip erase command, but by a different command. For example, in STM32, it's called User Option Bytes IIRC, see the manual. These flash regions are the perfect place to store things like factory-generated unique IDs, because when you develop your stuff, you don't wipe it by accident as easily, but if you so wish, youcanalways update it.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: November 24, 2023, 02:25:07 pm
content: If your programmer tool support hex files, instead of linking/objcopying an UUID into ELF you can also write it into a separate hex file and either load two files into the programmer tool or merge them into single file (there are tools likemergehexorhexmerge), or code some script generating an UUID and adding it straight into the fw.hex (that would be few lines in Python).But what degree of uniqueness you actually need? If the requirement is just “allmyboards must have different UUIDs” then using the mentioned 96-bit CPU ID extended to 128 bits with a fixed part would be just fine.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: November 24, 2023, 02:46:11 pm
content: A Python tool:Code:#!/usr/bin/env python3# requires intelhex package: pip install intelhex# fash address where to place the UUIDUUID_ADDR = 0x3FF00from sys import argv, exitif len(argv)!=2:exit("Usage: "+argv+" <fw.hex>")import uuidimport intelhexfw = intelhex.IntelHex(argv)new_uuid = uuid.uuid1() # RFC4122v1fw.puts(UUID_ADDR, new_uuid.bytes)fw.write_hex_file(argv)

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 24, 2023, 02:48:11 pm
content: If the purpose is unique serialisation, as opposed to cryptographic, then you probably don't need all that many bits.A method I use to generate ethernet MACs is to run a 32 bit timer at the system clock rate (50Mhz), and on the first human interaction ( e.g. dip switch change) seen after the first-ever powerup for the factory test, capture the timer value and write to a dedicated location at the end of the internal flash as the serial number.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 24, 2023, 02:49:17 pm
content: BTW you can also buy I2C EEPROMS preprogrammed with globally unique IDs ( and a registered ethernet MAC prefix), from Microchip

author: nctnico
author_rank: Super Contributor
timestamp: November 24, 2023, 03:02:56 pm
content: Quote from: mikeselectricstuff on November 24, 2023, 02:49:17 pmThose are a good option as well. I use these in several of my designs. They are a very cost effective solution.

author: peter-h
author_rank: Super Contributor
timestamp: November 24, 2023, 04:13:50 pm
content: What is this UUID for?

author: Siwastaja
author_rank: Super Contributor
timestamp: November 25, 2023, 08:27:33 am
content: Quote from: abyrvalg on November 24, 2023, 02:46:11 pmOr, from a makefile or bash script, after installing srec_cat, something like this. This example adds a 32-bit value stored in makefile variable $(devid) to address 0x10001080, combining that with existing whole.hex, to produce out.hex.Code:printf "%s" "${devid}" | fold -w 2 | tac | xxd -r -p - > tmp.bin$(OBJCOPY) --change-addresses 0x10001080 -I binary -O ihex tmp.bin tmp2.hexsrec_cat whole.hex -intel tmp2.hex -intel -o out.hex -intelFirst line converts the hexadecimal string (e.g. $devid=acdcabba) to 4-byte little endian binary file: printf echoes the hexadecimal string, fold divides it two characters (one byte) per each line, tac reverses the line order (for little endianness), and xxd -r converts from ascii hexadecimal to binary.Second line converts the binary value to .hex, adding an address to it (addresses are a feature of .hex files).Third line combines that hex with another hex. The weirdest thing in srec_cat is the command line syntax where you first give the filename then the format. Something like srec_cat or the python library as shown above is needed, as .hex files are sparse and contain address meta information, therefore they cannot be combined by simple means like concatenation of two hex files.