Lately, I’ve been wanting to learn how to do direct register access on the SAM family of microcontrollers. I have a SAMD51-based Adafruit Feather M4 Express board, so was looking through the SAMD51’s datasheet to work out which registers I could do something useful with while not having to dive too deep into the microcontroller.

Generating random numbers

Looking down the extensive list of peripherals, I came across the True Random Number Generator (TRNG). This seems to be a relatively rare feature for a microcontroller – I haven’t seen any others with one – and it struck me that it should be fairly simple to get going because it shouldn’t need to interface with too many other peripherals or central parts of the chip. Indeed, reading its description revealed it just need a clock source from the Main Clock (MCLK) and an interrupt setting up and I’d be ready to go. This involved setting three registers in total: the first to send the MCLK to the TRNG, the second to enable the TRNG and the third to enable interrupts from the TRNG when a new random number is available.

So how do you actually change register values? While there are a few different variations of the syntax you can use (explained well in this Arduino forum post), I opted for the following:

[PERIPHERAL]->[REGISTER].bit.[BIT]=1

The peripheral part is the name of the particular module within the chip that you’re controlling, so MCLK or TRNG in this case. These names often match the abbreviations used in the datasheet, and are defined by header (.h) files buried deep within the Arduino folder structure. These translate memory addresses and offsets for the particular chip into names we can use to make the code a lot more readable. Putting this all together, the three instructions I needed to use were MCLK->APBCMASK.bit.TRNG_=1, TRNG->CTRLA.bit.ENABLE=1 and TRNG->INTENSET.bit.DATARDY=1.

Note that when using the MCLK module, the bit name (so TRNG in our case) needs to be followed by an underscore (so TRNG_). I’m not exactly sure why this is and it took me a little while of looking through forum posts to work out that it was needed, but the code does compile and run successfully with it.

Working these commands into the wider Arduino program gave the following as the final code:

int rand_num;

void setup() {
  Serial.begin(2000000); // Open serial port with baud of 2Mbps
  while(!Serial);

  MCLK->APBCMASK.bit.TRNG_ = 1; // Enable Main Clock (MCLK) for TRNG

  TRNG->CTRLA.bit.ENABLE = 1; // Enable TRNG
  TRNG->INTENSET.bit.DATARDY = 1; // Enable TRNG interrupt when data ready
  delay(10); // Short delay to allow generation of new random number
}

void loop() {
  rand_num = TRNG->DATA.reg; // Read random number
  if (rand_num==0){ // Data register is zero by default
    Serial.println("TRNG not correctly setup!");
  }
  Serial.println(rand_num, BIN); // Output the number as binary over USB
  delayMicroseconds(200); // wait 200 microseconds to allow PC to catch up
}

Is it truly random?

While the SAMD51’s datasheet states that the TRNG “passes the American NIST Special Publication 800-22 and Diehard Random Tests Suites”, I wanted to confirm its randomness for myself. For this, I wrote a Python program, shown below, to ingest the generated numbers, and parse and plot them. You can see a histogram plot generated from 100,000 samples across 1,000 bins below; the straightness and noisiness of the line demonstrate the numbers’ randomness.

import matplotlib.pyplot as plt
import serial
import numpy as np

arduino = serial.Serial(port='COM3', baudrate=2e6)  # open serial port

number_of_samples = 0
sample_limit = int(1e5)
bins = 1000
sample_set = np.empty([sample_limit + 1], dtype=np.int64)

while number_of_samples <= sample_limit:
    data = str(arduino.readline())  # read in data until a line break character
    sample = int(data[2:len(data) - 5], 2)  # remove leading and line end characters
    sample_set[number_of_samples] = sample
    number_of_samples += 1

plt.figure()
plt.hist(sample_set, bins, histtype='step')
plt.title("Numbers generated by SAMD51 True Random Number Generator, 100,000 samples")
plt.xlabel("Generated number")
plt.xlim([0, 4294967296])
plt.ylabel("Frequency")
plt.show()
Histogram of 100,000 number samples across 1,000 bins

If you’d like to stay up to date with my electronics experiments and other projects, don’t forget to subscribe to my newsletter.