The 1.5$ TRNG

TRNG: True random number generator


Digispark and entropy

Some time ago I ordered a handful of the small Atmel ATTINY85 developer boards for a about a dollar each: Digispark copy board from aliexpress

I read the getting started guide at Digistump to set up a development environment. Now I just need a good use for such a neat device 🙂

I got the idea of making a random number generator for making good strong passwords and send them via USB keyboard to the PC.

I found an article by Walter Anderson called A study of entropy. Here he describes how electronic noise affects watchdog timers and how it could be used for harvesting entropy for a true random number generator.


I glued a Pushbutton onto the digispark board and soldered the button to datapin P2 and the other end to GND. There is no need for a pull-up resistor because internal pull-ups can be enabled in software.


With the quite limited hardward I wanted to build a password generator around Walter Andersons library. I wanted it to work like this:

  1. When the device is plugged in, it starts to generate random characters (a-z,  A-Z, 0-9, plus and minus). All this is stored in a 200 bytes ring-buffer in RAM. The reason for doing this is that the library generates maximum 2-4 characters per second on my device. This is quite slow so I want the random characters to be ready when I need them.
  2. When the ringbuffer is full, the device stops generating. As soon as data is drained from the buffer, it will keep it full.
  3. When the pushbutton is clicked, a fixed number of characters is taken from the ringbuffer and sent via USB keyboard to the PC.
  4. If the button is doubleclicked, then it goes to continuous mode, where the device dumps the characters to USB the keyboard as soon as they are generated.
  5. Continuous mode can be cancelled by single clicking the button again.


Here is my code:

#include "DigiKeyboard.h"
#include <Entropy.h>

//--------------------------- Setup ------------------------------------

#define DEFAULT_CONT_MODE false // What mode the device is started in. If true the device will send single characters as soon as they are generated.
#define RND_BUFFERSIZE 250      // The buffersize in RAM for random chars. 250 is about the physical limit
#define RND_SEND_CHARS 25       // Number of characters to send to the usb keyboard when button is pressed
#define SEND_LINEFEED true      // Send a linefeed after each random block.
#define DEBOUNCE_TIME 200       // A click is considered a click within these milliseconds
#define DOUBLECLICK_TIME 600    // Doubleclick should happen within these milliseconds
#define BUTTON_PIN 2
#define BUTTON_IRQ 0
#define LED_PIN 1

//---------------------- Globals for ISR -------------------------------

volatile unsigned short int buttonPressedTimes = 0;
volatile bool buttonPressed = false;
volatile bool continuousMode = DEFAULT_CONT_MODE;
volatile static unsigned long lastPressedTime = 0;

//-------------------------- Setup -------------------------------------

void setup()
  // Setup the buttons pin as input with pull up resistor, and setup interrupt function for when button is pressed
  digitalWrite(BUTTON_PIN, HIGH);
  attachInterrupt(BUTTON_IRQ, buttonISR, FALLING);
  Entropy.initialize(); // This sets up the entropy class.

//------------------------- Main Loop ----------------------------------

void loop()
  static char rnd_array[RND_BUFFERSIZE];
  static unsigned int head = 0; 
  static unsigned int tail = 0;
  unsigned short int sent_count; 

  // Reset timer for doubleclick if needed
  if ( millis()-lastPressedTime > DOUBLECLICK_TIME ) buttonPressedTimes = 0;

  DigiKeyboard.sendKeyStroke(0); // Be ready for sending keystrokes.

  // Fill the ringbuffer if possible
  if ( (( tail == 0 ) && ( head == RND_BUFFERSIZE-1 )) || ( head + 1 == tail ) ) {
    // ringbuffer is full, do nothing, exept rapid led flashing for indication
  } else {
    // There is space in ringbuffer, add a random character
    rnd_array[head++] = getRandomChar();
    if ( head >= RND_BUFFERSIZE ) head = 0;
  // Only if we detected a press on the button we dump the ringbuffer to the keyboard. If in continuous mode we just keep dumping to keyboard
  if ( ( buttonPressed || continuousMode ) && ( head != tail ) ) {
    sent_count = 0;
    do {
      if ( tail >= RND_BUFFERSIZE ) tail = 0;
    } while ( ( head != tail ) && ( ++sent_count<RND_SEND_CHARS ) );
    if ( SEND_LINEFEED && !continuousMode ) {
    if ( continuousMode && buttonPressed ) continuousMode = false; // If in continuous mode and button is pressed, we revert to stdmode.
    buttonPressed = false; // Reset the botton pressed state for the ISR, making ready for next press 

  DigiKeyboard.delay(1); // Keep communication going with USB-keyboard.

//----------------------- Helper functions ----------------------------

// Interrupt routine that gets called when the button is pressed
void buttonISR() {
  // To avoid button bounce, we only accept a new press after some time
  if ( millis()-lastPressedTime > DEBOUNCE_TIME )
    buttonPressed = true;
    lastPressedTime = millis();
  if ( buttonPressedTimes == 2 ) { continuousMode = true; buttonPressed = false; }

// Get and return one random character
char getRandomChar() {
    static const char charset[] PROGMEM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-+";
    uint32_t tmp;

    digitalWrite(LED_PIN, HIGH);
    tmp = Entropy.random(64); // get 6 bits
    digitalWrite(LED_PIN, LOW);
    return (pgm_read_byte_near(charset + tmp));

// If called countinously from main loop, this will make the led flash very fast.
void ledFastFlash() {
  static unsigned short int counter=0;
  digitalWrite(LED_PIN, ++counter%10<5 ? HIGH:LOW );

Probably the only tweeking you need to do is in the first few lines of the definition. Don’t try to push the ringbuffer size more than I did. This device has very little RAM, and no debugging facilities!

Example output after a few clicks:



The device seems to work good. The only main problem is the physical connection in the USB-port. Jiggeling the board too much when pressing the button will make a bad USB connection. This can make the program freeze or send some unwanted characters. This could be improved with an extender cable 🙂

The USB keyboard driver for the digispark is for a US keyboard. This would lead to som tweaking to get the characters right if you use another regional keyboard.

Bootloader optimization

The preprogrammed bootloader spends the first 5 seconds waiting for the Micronucleus programmer. This means that the device is useless for the first 5 seconds in normal day use.

This can be removed with a new bootloader. It can be found here. Go for the “entry-jumper” version. After installing this loader pin P0 needs to be connected to GND to upload the firmware. Normally this only has to be done once.

With this small modification the device will normally start operating within 1 second after inserting it in the USB port.

For the lazy people – without a solder iron

You could skip soldering on the button and just set “DEFAULT_CONTINUOUS_MODE” to true in the code. Now the device will continuously dump random characters via the keyboard 🙂 Just using the hardware out of the box..


2 thoughts to “The 1.5$ TRNG”

  1. You might find that the usb connection is more reliable during button presses if you apply a piece of tape (or two) to the back of the pcb (opposite side of pcb to the usb terminals) to make the connection tighter in the usb socket. (I’m assuming the problem is the pcb is very slightly too thin for reliable connection and tape might help…. but if that isn’t the problem, then clearly tape won’t help you!).

Leave a Reply

Your email address will not be published. Required fields are marked *