Random Number Generator, R&D

Random Numbers, Overview

The following project presents a hardware device to produce random numbers. This is not an easy thing to achieve in any digital device. Software alone cannot produce random numbers. Why we should need them ... is something upon which we shall keep our own counsel for now.

It works on the principle of avalanche noise derived from electromotive force kinetic energy in a strong electric field acting on a base-emitter junction in a small signal npn transistor. The transistor subjected to a voltage exceeding its rated break-down voltage here is Q7. Its breakdown voltage is 10.8v in this case (2N3904); and we subject it to 12v.

The voltage applied to the emitter creates additional electric-hole pairs by colliding with the atoms in the crystal lattice, which occasionally spills over into a kind of avalanche. This process does not permanently damage the junction. Therefore, we will be working with avalanche breakdown in a lightly doped pn junction. The EMF which passes through the emitter to the base is then amplified with TL082 op-amps; the unpredictability of this activity of electrons and atoms is generally considered to be random. The amplified raw signal will be the source of our digital outputs.

In the blog post we will provide:

  1. Full schematics for the Random Number Generator
  2. Full integration with a microchip PIC16LF device to interpret the raw signal.
  3. Exhaustive code for the PIC
  4. Serial output to a PC serial port and terminal window

 

The schematics

Seen above is the output to the serial port terminal. A sequence of numbers between 1 and 8 inclusive. There should be an equal weighting of each integer, i.e., from a sample of 80 numbers, you would like 10 8s, 10 7s, 10 6s, etc. That is unlikely in a truly random system - until large numbers of samples are taken, when we should expect each integer to have parity.

The speed of output will be determined by the frequency of the Analogue to Digital sampling and conversion, which can be changed in the firmware code, supplied below. Also, it should be easy to allow a smaller of larger range of output numbers by modifying the firmware. The ADC takes three samples, to return three digits of binary. Each of the eight outcomes are statistically equally likely.

We also provide a visual LED display attached to the Peripheral Interface Controller.

 

See below for:

  1. RNG schematic and photographs
  2. PIC to serial schematic and photographs
Random number generator

Comments

Please note that this device can be calibrated by changing the value of R8; it is shown as 10kohm, but I use 6.8kohm, to reduce the raw signal spread, and to ensure that the signal truly reaches ground on a down swing.

Second, the external voltage reference for the comparator is at pin 3 of the PIC. Apply the voltage which gives the best results for both low or high upon which the comparator will work.

Finally, See below for:

 

  1. Raw signal from the Random Number Generator
  2. Digital output from the comparator, on which the ADC works

Results

The serial output sent to a PC gives the following data:

While writing this post, I received 809 results in decimal. It is not correct to sum these results and then get an average. A true random number machine would produce as many 1s as 8s, as 6s, etc.

Therefore, the achieved spread of digits, 1, 2, 3 etc., was as follows:

1 = 78

2 = 102

3 = 121

4 = 94

5 = 106

6 = 114

7 = 101

8 = 93

 

The conversion from binary decimal is largely on the basis of 000 = the number 1, and 111 = the number 8; so, more 000s would weight the results to the lower integers. But those greater than 4 are 414, and those less thanĀ  or equal to 4 are 395. So at this point of testing, the calibration seems about right.

The value of resistor R8 is 6.8k. The voltage reference for the comparator at pin 3 of the PIC is 1.98v, which I advise for anyone who creates this device.

 

See the serial output below, and the PC serial terminal which I use, Putty - which should be set up as shown, with the Port setting appropriate; and a Baud of 1000 - which ought to be faster, .. but let us not go into that here. Comments welcome.

Firmware, PIC16LF1938

Finally, the code for the device. I paste it here in a single file for ease of cutting and pasting. You could split it into headers and subfiles, etc.

It contains no use of arrays to get the Port settings for the LEDs; and a lot of excess code for the EUSART. The work for the Comparator is generated by the Microchip Code Configurator. It is possible to ammend the code to derive your own decimal results from samples of the comparator digital output. We here convert three digits into only eight integers, based on sampling every 10,000 oscillations of the 32KHz crystal on Timer 1, interrupt based.

 

--

Open a new MPLABX X project and use this code in the main.c file.

Code

// CONFIG1
#pragma config IESO = ON // Internal/External Switchover->Internal/External Switchover mode is enabled
#pragma config BOREN = ON // Brown-out Reset Enable->Brown-out Reset enabled
#pragma config PWRTE = OFF // Power-up Timer Enable->PWRT disabled
#pragma config FOSC = INTOSC // Oscillator Selection->INTOSC oscillator: I/O function on CLKIN pin
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable->Fail-Safe Clock Monitor is enabled
#pragma config MCLRE = ON // MCLR Pin Function Select->MCLR/VPP pin function is MCLR
#pragma config CP = OFF // Flash Program Memory Code Protection->Program memory code protection is disabled
#pragma config CPD = OFF // Data Memory Code Protection->Data memory code protection is disabled
#pragma config WDTE = OFF // Watchdog Timer Enable->WDT disabled
#pragma config CLKOUTEN = OFF // Clock Out Enable->CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin

// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection->Write protection off
#pragma config LVP = OFF // Low-Voltage Programming Enable->High-voltage on MCLR/VPP must be used for programming
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable->Stack Overflow or Underflow will cause a Reset
#pragma config PLLEN = ON // PLL Enable->4x PLL enabled
#pragma config BORV = LO // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (Vbor), low trip point selected.

#include <xc.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <xc.h>
#include <stdlib.h>

#define RECEIVE_BUFF_SIZE 64
#define _XTAL_FREQ 20000000
#define INPUT 1
#define OUTPUT 0
#define HIGH 1
#define LOW 0
#define ANALOG 1
#define DIGITAL 0
#define PULL_UP_ENABLED 1
#define PULL_UP_DISABLED 0
// get/set C12IN1Neg aliases
#define C12IN1Neg_TRIS TRISA1
#define C12IN1Neg_LAT LATA1
#define C12IN1Neg_PORT RA1
#define C12IN1Neg_ANS ANSA1
#define C12IN1Neg_SetHigh() do { LATA1 = 1; } while(0)
#define C12IN1Neg_SetLow() do { LATA1 = 0; } while(0)
#define C12IN1Neg_Toggle() do { LATA1 = ~LATA1; } while(0)
#define C12IN1Neg_GetValue() RA1
#define C12IN1Neg_SetDigitalInput() do { TRISA1 = 1; } while(0)
#define C12IN1Neg_SetDigitalOutput() do { TRISA1 = 0; } while(0)
#define C12IN1Neg_SetAnalogMode() do { ANSA1 = 1; } while(0)
#define C12IN1Neg_SetDigitalMode() do { ANSA1 = 0; } while(0)
// get/set C1INPos aliases
#define C1INPos_TRIS TRISA3
#define C1INPos_LAT LATA3
#define C1INPos_PORT RA3
#define C1INPos_ANS ANSA3
#define C1INPos_SetHigh() do { LATA3 = 1; } while(0)
#define C1INPos_SetLow() do { LATA3 = 0; } while(0)
#define C1INPos_Toggle() do { LATA3 = ~LATA3; } while(0)
#define C1INPos_GetValue() RA3
#define C1INPos_SetDigitalInput() do { TRISA3 = 1; } while(0)
#define C1INPos_SetDigitalOutput() do { TRISA3 = 0; } while(0)
#define C1INPos_SetAnalogMode() do { ANSA3 = 1; } while(0)
#define C1INPos_SetDigitalMode() do { ANSA3 = 0; } while(0)
// get/set C1OUT aliases
#define C1OUT_TRIS TRISA4
#define C1OUT_LAT LATA4
#define C1OUT_PORT RA4
#define C1OUT_ANS ANSA4
#define C1OUT_SetHigh() do { LATA4 = 1; } while(0)
#define C1OUT_SetLow() do { LATA4 = 0; } while(0)
#define C1OUT_Toggle() do { LATA4 = ~LATA4; } while(0)
#define C1OUT_GetValue() RA4
#define C1OUT_SetDigitalInput() do { TRISA4 = 1; } while(0)
#define C1OUT_SetDigitalOutput() do { TRISA4 = 0; } while(0)
#define C1OUT_SetAnalogMode() do { ANSA4 = 1; } while(0)
#define C1OUT_SetDigitalMode() do { ANSA4 = 0; } while(0)

// Declarations
void USARTWriteChar(char ch);
void USARTWriteString(const char *str);
void USARTWriteLine(const char *str);
void USARTWriteInt(int16_t val, int8_t field_length);
void USARTHandleRxInt();
void USARTGotoNewLine();
void USARTReadBuffer(char *buff,uint16_t len);
void USARTFlushBuffer();
void PIN_MANAGER_Initialize (void);
void PIN_MANAGER_IOC(void);
void interrupt_service_routine ();
void sample ();
void main_init ();
void USARTInit(uint16_t baud_rate);
void SYSTEM_Initialize(void);
void OSCILLATOR_Initialize(void);
void CMP1_Initialize(void);
bool CMP1_GetOutputStatus(void);

//Variables
volatile char URBuff[RECEIVE_BUFF_SIZE]; //USART Receive Buffer
volatile int8_t UQFront;
volatile int8_t UQEnd;
char num_incr = 0;
unsigned char total = 0;
char dice;
char print;
char data1;
char data2;
char data3;
char USARTReadData();
uint8_t USARTDataAvailable();

// Main application

void main(void)
{
// initialize the device
SYSTEM_Initialize();

main_init();

USARTInit(9600);

// Print to PC serial terminal
USARTWriteLine("***********************************************");
USARTWriteLine("Random number between 1 and 8");
USARTWriteLine("Jason Powell (c), SDI, 2018");
USARTWriteLine("----------");
USARTWriteLine("***********************************************");
USARTGotoNewLine();
USARTGotoNewLine();

while (1)
{
sample();

if(total >=4)
{

__delay_ms (10); // TO ALLOW FVR TO CHARGE UP

LATBbits.LATB0 = 1;
LATBbits.LATB1 = 1;
LATBbits.LATB2 = 1;
LATBbits.LATB3 = 1;
LATBbits.LATB4 = 1;
LATBbits.LATB5 = 1;
LATBbits.LATB6 = 1;
LATBbits.LATB7 = 1;

char buf[5];
char data = print; // or char data = 0x34;
unsigned char i;

sprintf(buf,"%d",data); // print it.!
i = 0;
while (buf[i]!='\0')
{
TXREG = buf[i++];
while(PIR1bits.TXIF==0);
}
USARTGotoNewLine();
total = 0;
}

if(total ==3)
{

if(data1 == 0 && data2 == 0 && data3 == 0)
{
print = 1;
}
if(data1 == 0 && data2 == 0 && data3 == 1)
{
print = 2;
}
if(data1 == 0 && data2 == 1 && data3 == 0)
{
print = 3;
}
if(data1 == 1 && data2 == 0 && data3 == 0)
{
print = 4;
}
if(data1 == 0 && data2 == 1 && data3 == 1)
{
print = 5;
}
if(data1 == 1 && data2 == 1 && data3 == 0)
{
print = 6;
}
if(data1 == 1 && data2 == 0 && data3 == 1)
{
print = 7;
}
if(data1 == 1 && data2 == 1 && data3 == 1)
{
print = 8;
}

if(print == 1)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 1;
LATBbits.LATB2 = 1;
LATBbits.LATB3 = 1;
LATBbits.LATB4 = 1;
LATBbits.LATB5 = 1;
LATBbits.LATB6 = 1;
LATBbits.LATB7 = 1;
}
if(print == 2)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 0;
LATBbits.LATB2 = 1;
LATBbits.LATB3 = 1;
LATBbits.LATB4 = 1;
LATBbits.LATB5 = 1;
LATBbits.LATB6 = 1;
LATBbits.LATB7 = 1;
}
if(print == 3)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 0;
LATBbits.LATB2 = 0;
LATBbits.LATB3 = 1;
LATBbits.LATB4 = 1;
LATBbits.LATB5 = 1;
LATBbits.LATB6 = 1;
LATBbits.LATB7 = 1;
}
if(print == 4)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 0;
LATBbits.LATB2 = 0;
LATBbits.LATB3 = 0;
LATBbits.LATB4 = 1;
LATBbits.LATB5 = 1;
LATBbits.LATB6 = 1;
LATBbits.LATB7 = 1;
}
if(print == 5)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 0;
LATBbits.LATB2 = 0;
LATBbits.LATB3 = 0;
LATBbits.LATB4 = 0;
LATBbits.LATB5 = 1;
LATBbits.LATB6 = 1;
LATBbits.LATB7 = 1;
}
if(print == 6)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 0;
LATBbits.LATB2 = 0;
LATBbits.LATB3 = 0;
LATBbits.LATB4 = 0;
LATBbits.LATB5 = 0;
LATBbits.LATB6 = 1;
LATBbits.LATB7 = 1;
}
if(print == 7)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 0;
LATBbits.LATB2 = 0;
LATBbits.LATB3 = 0;
LATBbits.LATB4 = 0;
LATBbits.LATB5 = 0;
LATBbits.LATB6 = 0;
LATBbits.LATB7 = 1;
}
if(print == 8)
{
LATBbits.LATB0 = 0;
LATBbits.LATB1 = 0;
LATBbits.LATB2 = 0;
LATBbits.LATB3 = 0;
LATBbits.LATB4 = 0;
LATBbits.LATB5 = 0;
LATBbits.LATB6 = 0;
LATBbits.LATB7 = 0;
}
}

//Get the amount of data waiting in USART queue
uint8_t n= USARTDataAvailable();

//If we have some data
if(n!=0)
{
//Read it
char data=USARTReadData();

//And send back
USARTWriteChar('<');
USARTWriteChar(data);
USARTWriteChar('>');
}

}
}

void main_init()
{
OPTION_REG = 0b10000011; // ENABLE WEAK PULL UPS, AND NO PRESCALER ON TMR0
// OTHERWISE, THE DIPS DO NOT READ PROPERLY
FVRCON = 0b10000011; // ENABLE INTERNAL VREF
TRISC = 0b00001000;
//ADC SETTINGS: 0 = DIGITAL ; 1 = ANALOG
// TMR1 uses 32 kHz external crystal
T1GCON = 0x00; // Ensure that T1 Gate is disabled
T1CONbits.nT1SYNC = 1; // Do not sync with system clock
T1CONbits.TMR1CS = 0b10; // external clock as source
T1CONbits.T1OSCEN = 1; // enable oscillator driver
T1CONbits.T1CKPS = 0; // 1:1 prescaler
TMR1IF = 0;
TMR1IE = 0;
T1CONbits.TMR1ON = 1;
//INTCON = 0b00000000;
TMR0IF = 0;
TMR0=0;
TMR0IE = 0;
INTCONbits.PEIE = 1;
TMR1IE = 1;
ei();
LATE = 0x00;
TRISE = 0x08;
WPUE = 0x00;
OPTION_REGbits.nWPUEN = 0x01;
APFCON = 0x00;
}// END INIT ()

void sample()
{
FVRCON = 0b10000011; // ENABLE INTERNAL VREF
__delay_us (300); // TO ALLOW FVR TO CHARGE UP
ADCON0 = 0b00000001; // ANA0 CHANNEL, pin 3, (RA1), AND ENABLED
ADON = 1; // SWITCH ON
ADCON1 = 0b00100011; //HERE IT IS SET TO READ OFF INTERNAL 5V REFERENCE
ADGO = 1; // TURN ON THE ADC
while (ADGO == 1); // WAIT WHILE CONVERSION TAKES PLACE
dice = ADRESH; // ASSIGN NAME TO RESULT
}

void interrupt isr()
{

if (RCIE && RCIF)
{
USARTHandleRxInt();
return;
}

if (TMR1IF && TMR1IE) // tIMER 1 FOR THE PULSE TO MAIN BOARD!!
{
TMR1IF = 0;
TMR1IE = 0;

total++;

if(total == 1 && dice >=127)
{
data1 = 1;
}
if(total == 1 && dice <127)
{
data1 = 0;
}

if(total == 2 && dice >=127)
{
data2 = 1;
}
if(total == 2 && dice <127)
{
data2 = 0;
}

if(total == 3 && dice >=127)
{
data3 = 1;
}
if(total == 3 && dice <127)
{
data3 = 0;
}

// etc, not in isr, but at top

TMR1 = 54700;
TMR1IE = 1;
}
// TMR1 = 32700;// set the clock half way complete, which means 1 second

} // END ISR

void USARTInit(uint16_t baud_rate)
{
//Setup queue
UQFront=UQEnd=-1;

//SPBRG
switch(baud_rate)
{
case 9600:
SPBRG=25; // was 129 from internet site
break;
case 19200:
SPBRG=64;
break;
case 28800:
SPBRG=42;
break;
case 33600:
SPBRG=36;
break;
}
//TXSTA
TXSTAbits.TX9=0; //8 bit transmission
TXSTAbits.TXEN=1; //Transmit enable
TXSTAbits.SYNC=0; //Async mode
TXSTAbits.BRGH=1; //High speed baud rate

//RCSTA
RCSTAbits.SPEN=1; //Serial port enabled
RCSTAbits.RX9=0; //8 bit mode
RCSTAbits.CREN=1; //Enable receive
RCSTAbits.ADDEN=0; //Disable address detection

//Receive interrupt
RCIE=1;
PEIE=1;

ei();
}

void USARTWriteChar(char ch)
{
while(!PIR1bits.TXIF);

TXREG=ch;
}

void USARTWriteString(const char *str)
{
while(*str!='\0')
{
USARTWriteChar(*str);
str++;
}
}

void USARTWriteLine(const char *str)
{
USARTWriteChar('\r');//CR
USARTWriteChar('\n');//LF

USARTWriteString(str);
}

void USARTHandleRxInt()
{
if(RB1==1)
RB1=0;
else
RB1=1;

//Read the data
char data=RCREG;

//Now add it to q
if(((UQEnd==RECEIVE_BUFF_SIZE-1) && UQFront==0) || ((UQEnd+1)==UQFront))
{
//Q Full
UQFront++;
if(UQFront==RECEIVE_BUFF_SIZE) UQFront=0;
}

if(UQEnd==RECEIVE_BUFF_SIZE-1)
UQEnd=0;
else
UQEnd++;

URBuff[UQEnd]=data;

if(UQFront==-1) UQFront=0;

}

char USARTReadData()
{
char data;

//Check if q is empty
if(UQFront==-1)
return 0;

data=URBuff[UQFront];

if(UQFront==UQEnd)
{
//If single data is left
//So empty q
UQFront=UQEnd=-1;
}
else
{
UQFront++;

if(UQFront==RECEIVE_BUFF_SIZE)
UQFront=0;
}

return data;
}

uint8_t USARTDataAvailable()
{
if(UQFront==-1) return 0;
if(UQFront<UQEnd)
return(UQEnd-UQFront+1);
else if(UQFront>UQEnd)
return (RECEIVE_BUFF_SIZE-UQFront+UQEnd+1);
else
return 1;
}

void USARTWriteInt(int16_t val, int8_t field_length)
{
char str[5]={0,0,0,0,0};
int8_t i=4,j=0;

//Handle negative integers
if(val<0)
{
USARTWriteChar('-'); //Write Negative sign
val=val*-1; //convert to positive
}
else
{
USARTWriteChar(' ');
}

if(val==0 && field_length<1)
{
USARTWriteChar('0');
return;
}
while(val)
{
str[i]=val%10;
val=val/10;
i--;
}

if(field_length==-1)
while(str[j]==0) j++;
else
j=5-field_length;

for(i=j;i<5;i++)
{
USARTWriteChar('0'+str[i]);
}
}

void USARTGotoNewLine()
{
USARTWriteChar('\r');//CR
USARTWriteChar('\n');//LF
}

void USARTReadBuffer(char *buff,uint16_t len)
{
uint16_t i;
for(i=0;i<len;i++)
{
buff[i]=USARTReadData();
}
}
void USARTFlushBuffer()
{
while(USARTDataAvailable()>0)
{
USARTReadData();
}
}

// Section: CMP1 APIs

void CMP1_Initialize(void)
{

// set the CMP to the options selected in MPLABĀ® Code Configurator
// C1POL not inverted; C1ON enabled; C1HYS disabled; C1SYNC asynchronous; C1OUT CPOL_VPVN; C1SP hi_speed; C1OE COUT_pin;
CM1CON0 = 0xA4;

// C1PCH CIN+_pin; C1INTN no_intFlag; C1INTP no_intFlag; C1NCH CIN1-;
CM1CON1 = 0x01;

}

bool CMP1_GetOutputStatus(void)
{
return (CM1CON0bits.C1OUT);
}

void SYSTEM_Initialize(void)
{
OSCILLATOR_Initialize();
PIN_MANAGER_Initialize();
CMP1_Initialize();

}

void OSCILLATOR_Initialize(void)
{
// SPLLEN disabled; SCS FOSC; IRCF 500KHz_MF;
OSCCON = 0x38;
// OSTS intosc; HFIOFR disabled; HFIOFS not0.5percent_acc; PLLR disabled; T1OSCR disabled; MFIOFR disabled; HFIOFL not2percent_acc; LFIOFR disabled;
OSCSTAT = 0x00;
// TUN 0x0;
OSCTUNE = 0x00;
// Set the secondary oscillator

}

void PIN_MANAGER_Initialize(void)
{
LATA = 0x00;
TRISA = 0b11101111;
ANSELA = 0b00101111;

LATB = 0x00;
TRISB = 0x00;
ANSB0 = 0;
ANSB1 = 0;
ANSB2 = 0;
ANSB3 = 0;
ANSB4 = 0;
ANSB5 = 0;
WPUB = 0x00;

LATC = 0x00;
TRISC = 0xFF;

LATE = 0x00;
TRISE = 0x08;
WPUE = 0x00;

OPTION_REGbits.nWPUEN = 0x01;

APFCON = 0x00;

}

Jason Powell

Thanks! and send any comments or whatever.

Here's a picture of me, as if I were Mr Tumble, taken with an IPhone - disfiguring and nose first - as they always are.

Jason Powell

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.