Gerando números aleatórios em um Dado Eletrônico com Arduino 1

Em grande parte dos sistemas que projetamos é necessário que as entradas tenham sempre as mesmas respostas (comportamento determinístico). Um comportamento aleatório poderia ocasionar diversos bugs no sistema, os quais seriam difíceis de testar e corrigir. Quando necessitamos de uma resposta aleatória utilizamos mecanismos pseudoaleatórios (falso aleatório), pois são mais fáceis de implementar e facilitam questões de debug. No entanto, existem ocasiões que desejamos uma verdadeira aleatoriedade. Alguns dispositivos contém mecanismos que podem ser usados para gerar números verdadeiramente aleatórios, como por exemplo o Arduino Uno. Neste post vamos aprender a construir um dado eletrônico com Arduino e veremos como funcionam e como implementar geradores pseudoaleatórios e aleatórios verdadeiros.

Imagem 1 - Dado Eletrônico com Arduino

Geradores Pseudoaleatórios

Um gerador de número pseudoaleatório é um algoritmo que gera uma sequência de números que são aparentemente aleatórios, independentes entre si. Ou seja, mesmo que se saiba os números anteriores, é extremamente difícil prever qual será o próximo número (mas, ainda que difícil, é possível).

Geradores pseudoaleatórios são muito menos custosos de se implementar e atendem as necessidades da maioria das aplicações, por isso estão presentes em praticamente todas as linguagens e sistemas. Além disso, como podemos forçar um gerador pseudoaleatório a gerar uma mesma sequência diversas vezes, é muito mais fácil detectar e corrigir erros intermitentes (as vezes ocorrem, as vezes não, aleatoriamente).

Geradores Aleatórios Verdadeiros

Os geradores de números aleatórios verdadeiros, diferente dos pseudoaleatórios, tem garantia de gerar uma sequência de números independentes entre si, ou seja, mesmo que se saiba os números anteriores, é impossível prever qual será o próximo número.

Par isso, normalmente utiliza-se um recurso de hardware externo (não necessariamente deve estar fora da máquina) para captar algum processo não determinístico. Alguns exemplos são dados atmosféricos (como o site random.org), fenômenos quânticos, ruídos captados em uma câmera de baixa qualidade, dentre outros.

Geradores aleatórios verdadeiros são mais custosos de se implementar, portanto são geralmente utilizados apenas em situações mais críticas. Se a loteria utilizasse geradores pseudoaleatórios, seria possível (ainda que difícil) descobrir o próximo número premiado analisando os números anteriores.

Gerando números pseudoaleatórios no Arduino

O Arduino possui uma função nativa para geração de números pseudoaleatórios, a função random, que retorna um número aleatório entre zero e o maior valor positivo suportado por uma variável do tipo long (2.147.483.647). Seu funcionamento é descrito abaixo:

long last_val = 1;
const long random_a = 1103515245;
const long random_c = 12345;
const long random_m = 2147483647;

long random() {
    last_val = ((random_a * last_val) + random_c) & random_m;
    return last_val;
}

Neste gerador o próximo número pseudoaleatório é gerado ao se aplicar algumas operações matemáticas ao número anterior: multiplicamos o último valor por um termo A, somamos a um termo C e retornamos o resto de divisão (ou módulo) por M. Neste caso estamos utilizando uma operação AND bit-a-bit para evitar problemas com sinal, mas seu efeito é idêntico à uma operação módulo por um M igual a 2147483648. O valor gerado é salvo para ser utilizado no próximo número.

Este algoritmo é chamado de Gerador Congruente Linear (LCG, em inglês) um dos mais velhos e mais conhecidos geradores pseudoaleatórios. Sua qualidade é determinada pelos parâmetros A, C e M e seu funcionamento pode ser entendido como um espécie de roleta: o parâmetro A representa a força com qual rodamos a roleta, C nos garante que a roleta sempre rodará um valor mínimo e M representa o tamanho da roleta (máximo de números). Observe a imagem abaixo:

Imagem 2 - Parâmetros ACM

A imagem acima considera os parâmetros A = 3, C = 1 e M = 8. Inicialmente estamos na posição 1, ao executar o algoritmo (girar a roleta) pela primeira vez, paramos na posição 4 (3 * 1 + 1 = 4), deslocando 3 posições no sentido horário. Na segunda execução, estamos na posição 4, deveríamos parar na posição 13 (3 * 4 + 1 = 13), deslocando 9 posições no sentido horário. No entanto o maior valor da roleta é 7 (M – 1), ao deslocar as 13 casas paramos na posição 5 (o resto de divisão entre 13 e 8 é 5).

Como utilizamos o valor anterior para gerar o próximo, precisamos de um valor inicial para gerar o primeiro número (a posição em que a roleta se encontrava antes de ser girada pela primeira vez), a este valor damos o nome de semente (seed, em inglês). A principal característica de um gerador pseudoaleatório é que, contanto que a semente seja a mesma, a sequência gerada será a mesma (estamos aplicando as mesmas operações matemáticas sobre os mesmos números). No Arduino, utilizamos a função randomSeed para determinar a semente, caso contrário, o algoritmo utilizará o valor padrão (geralmente 1).

Existem diversas técnicas de inicialização de semente para algoritmos pseudoaleatórios, dentre elas:

  • Constante: Escolhe-se uma semente fixa de forma que toda vez que o código for executado (após um reset), a sequência gerada é a mesma;
  • Timestamp: Utiliza-se o timestamp do processador (quantidade de segundos decorridos desde 1 de janeiro de 1970) de forma que, a menos que o código seja executado duas vezes em um mesmo segundo, as sequências geradas devem ser diferentes;
  • Aleatório: Utiliza-se um número aleatório verdadeiro para inicializar a sequência pseudoaleatória. Isso não torna a sequência gerada em aleatória, pode ser entendido como escolher aleatoriamente uma sequência pseudoaleatória dentre todas as possíveis;

Os valores dos parâmetros A, C e M podem variar para diferentes compiladores. Os valores mostrados acima são o padrão utilizado pelo gcc, um dos compiladores de C/C++, de código aberto mais conhecidos e utilizados. Como o compilador do Arduino é baseado no gcc, provavelmente utiliza os mesmos parâmetros.

Gerando números aleatórios no Arduino

Para gerar um número aleatório, precisamos mensurar um fenômeno aleatório e utilizá-lo para gerar uma sequência numérica.

O Arduino UNO conta com um conversor AD de 10 bits, ao utiliza-lo com o pino desconectado notamos que ainda é possível medir alguns milivolts, isso se deve à interferência do ruído eletromagnético no conversor.

Ondas eletromagnéticas, como as de rádio, TV e as emitidas de forma não intencional por eletrodomésticos (TV, microondas, liquidificadores, …) ao atingirem o conversor A/D geram uma pequena tensão, de alguns milivolts, que é captada pelo conversor (fenômeno da indutância). A imagem abaixo mostra o efeito do ruído eletromagnético em uma série de conversões A/D de um pino desconectado ao longo do tempo.

Imagem 3 - Ruído

Como existem diversas fontes de ruído, o valor desta tensão é imprevisível, podendo ser utilizada para gerar números aleatórios. Veja o código abaixo:

int analog_pin = A0; //Pino Analógico Utilizado

int32_t last_value = 1;
const long random_a = 1103515245;
const long random_c = 12345;
const long random_m = 2147483647;

int32_t true_random() {
    int32_t x = (last_value & 0xFFFFF000)
              | ((millis() & 0xFF) << 8)
              | (analogRead(analog_pin) & 0xF);
    x = ((x * random_a) + random_c) & random_m;
    return (last_value = x);  //Salva x em last_value e retorna x
}

Note que este algoritmo é semelhante ao anterior. No entanto, ao invés de utilizarmos simplesmente o último valor, o modificamos realizando algumas operações com o valor de uma leitura analógica de um pino desconectado e o valor de millis (o tempo em milissegundos decorrido desde que o Arduino foi ligado). Ao rodar este algoritmo diversas vezes, pode-se observar que as sequências diferem a cada reset.

É importante garantir o máximo de ruído nas conversões, deixe o pino analógico escolhido desconectado ou conecte-o em uma antena (fio solto). Como é feita uma conversão A/D, este algoritmo é mais lento que o anterior, mas na maioria dos casos a diferença é imperceptível.

Dado Eletrônico com Arduino

Vamos observar o comportamento dos dois geradores em um projeto de um dado eletrônico com Arduino. A cada segundo, o sistema irá gerar um número entre 1 e 6 e exibi-lo. Como trata-se de um dado, ao invés de usarmos um LCD ou outro tipo de display alfanumérico vamos utilizar uma matriz de LEDs para exibir os números na forma de pontos, tal como um dado de verdade.

Material Necessário

Circuito do Dado Eletrônico com Arduino

Para exibir os valores do dado, vamos utilizar o módulo de matriz de LEDs 8 x 8 com o CI MAX7219. Também é possível utilizar 7 LEDs comuns, mas o uso deste módulo facilitas as conexões e permite o ajuste de intensidade e limitação de corrente sem necessidade de circuitos externos.

Apenas 5 pinos são necessários para controlar 64 LEDs, sendo eles VCC, GND, DataIn, Clk e Enable. A comunicação é feita de forma serial em um protocolo compatível com o protocolo SPI (Serial Peripheral Interface, ou Interface Serial de Periféricos). Para compreender melhor o funcionamento deste componente você pode acessar o post Módulo MAX7219 Matriz de LEDs 8×8.

O circuito construído está representado na imagem abaixo. Note que a antena é apenas ilustrativa, pode ser utilizado um simples fio comprido ou mesmo deixar desconectado.

Imagem 4 - Dado eletrônico com Arduino

Bibliotecas Necessárias

Vamos utilizar a biblioteca LedControl para controlar a matriz de LEDs, esta biblioteca pode ser obtida através do gerenciador de bibliotecas da ArduinoIDE.

Imagem 5 - Instalação da Biblioteca

Além da LedControl, vamos utilizar uma biblioteca que desenvolvi. Esta encapsula os algoritmos mostrados anteriormente em objetos de forma a facilitar seu uso. A biblioteca pode ser encontrada no meu github. Para utilizá-la, baixe e descompacte a mesma na pasta libraries da Arduino IDE. Se desejar criar sua própria biblioteca, acesse o post Biblioteca Arduino: aprenda como criar a sua.

Código Fonte do Dado Eletrônico com Arduino

O código fonte, a ser carregado no Arduino, está descrito abaixo:

#include <randE.h>
#include <LedControl.h>

#define TRUE_RAND 1    // 1 para usar o gerador aleatório verdadeiro, 0 para usar o gerador pseudo aleatório

#if defined(TRUE_RAND) and (TRUE_RAND == 1)
    TrueRand rnd(A0);    //Gerador aleatório verdadeiro utilizando o canal 0 do conversor AD
#else
    LCG rnd(0xC0FFEE);  //Gerador pseudoaleatório utilizando a semente 0xC0FFEE (12648430)
#endif

LedControl lc(    //Controlador da matriz de leds
    12,           //DataIn - Pino 12
    11,           //CLK - Pino 11
    10,           //Load - Pino 10
    1             //Estamos utilizamos apenas um módulo de Matriz de LEDs.
);   

int dice_show(int v) {
    //Mostra os valores numéricos representados por pontos na matriz de leds
    switch(v) {
        case 1:
            lc.setRow(0,0,B00000000);
            lc.setRow(0,1,B00000000);
            lc.setRow(0,2,B00000000);
            lc.setRow(0,3,B00011000);
            lc.setRow(0,4,B00011000);
            lc.setRow(0,5,B00000000);
            lc.setRow(0,6,B00000000);
            lc.setRow(0,7,B00000000);
            break;
        case 2:
            lc.setRow(0,0,B00000011);
            lc.setRow(0,1,B00000011);
            lc.setRow(0,2,B00000000);
            lc.setRow(0,3,B00000000);
            lc.setRow(0,4,B00000000);
            lc.setRow(0,5,B00000000);
            lc.setRow(0,6,B11000000);
            lc.setRow(0,7,B11000000);
            break;
        case 3:
            lc.setRow(0,0,B00000011);
            lc.setRow(0,1,B00000011);
            lc.setRow(0,2,B00000000);
            lc.setRow(0,3,B00011000);
            lc.setRow(0,4,B00011000);
            lc.setRow(0,5,B00000000);
            lc.setRow(0,6,B11000000);
            lc.setRow(0,7,B11000000);
            break;
        case 4:
            lc.setRow(0,0,B11000011);
            lc.setRow(0,1,B11000011);
            lc.setRow(0,2,B00000000);
            lc.setRow(0,3,B00000000);
            lc.setRow(0,4,B00000000);
            lc.setRow(0,5,B00000000);
            lc.setRow(0,6,B11000011);
            lc.setRow(0,7,B11000011);
            break;
        case 5:
            lc.setRow(0,0,B11000011);
            lc.setRow(0,1,B11000011);
            lc.setRow(0,2,B00000000);
            lc.setRow(0,3,B00011000);
            lc.setRow(0,4,B00011000);
            lc.setRow(0,5,B00000000);
            lc.setRow(0,6,B11000011);
            lc.setRow(0,7,B11000011);
            break;
        case 6:
            lc.setRow(0,0,B11011011);
            lc.setRow(0,1,B11011011);
            lc.setRow(0,2,B00000000);
            lc.setRow(0,3,B00000000);
            lc.setRow(0,4,B00000000);
            lc.setRow(0,5,B00000000);
            lc.setRow(0,6,B11011011);
            lc.setRow(0,7,B11011011);
            break;
    }
    return v;
}

int dice_roll() {
    lc.clearDisplay(0);
    delay(250);
    return dice_show(rnd.next_int(1,7));    //Gera um numero aleatório (ou pseudoaleatório) entre 1 e 6, exibe na matriz
}

void setup() {
    // Inicializa Max7219
    lc.shutdown(0, false);
    lc.setIntensity(0, 8);
    lc.clearDisplay(0);
    // Inicializa Serial
    Serial.begin(9600);
    Serial.flush();
}

void loop() {
    Serial.println(dice_roll());    //Exibe um valor na matriz e envia pela serial
    delay(1250);
}

A macro TRUE_RAND (linha 4) nos permite escolher utilizar o gerador pseudoaleatório (LCG) ou o aleatório verdadeiro (utilizando o conversor A/D).

Resultados do Dado Eletrônico com Arduino

Inicialmente vamos ver o comportamento do algoritmo pseudoaleatório:

Imagem 6 - Dado eletrônico com Arduino

Abaixo está o comportamento do algoritmo pseudoaleatório após um reset.

Imagem 7 - Dado eletrônico com Arduino

Observe que a sequência gerada em ambos os casos é a mesma, isso pois estamos utilizando uma inicialização de semente fixa, de modo que as sequências geradas sempre serão iguais.

Vamos ver agora o comportamento do algoritmo aleatório verdadeiro:

Imagem 8 - Dado eletrônico com Arduino

E após um reset.

Imagem 9 - Dado eletrônico com Arduino

Diferente do caso anterior, as sequências geradas são diferentes após um reset. Isso acontece pois estamos utilizando uma medida aleatória (ruído eletromagnético) na geração, deste modo os números gerados são imprevisíveis.

Para finalizar, vale a pena ressaltar que a maioria das aplicações não necessitam de números aleatórios verdadeiros, um bom algoritmo pseudoaleatório com uma boa inicialização de semente (timestamp por exemplo) consegue suprir a maioria das necessidades. Observamos apenas um exemplo de algoritmo pseudoaleatório, entretanto, existem outros algoritmos mais complexos e com melhor qualidade. Alguns SOs incluem bons mecanismos para geração de números aleatórios. O Unix, por exemplo, contém um gerador que utiliza dados do ambiente computacional (dados de registradores, timers, leituras de drivers e entradas de teclado) para gerar valores aleatórios.

Gostou do post sobre Gerando números aleatórios em um Dado Eletrônico com Arduino? Deixe seu comentário logo abaixo.

Faça seu comentário

Acesse sua conta e participe

Um Comentário

  1. Boa tarde Matheus,

    Como está?
    Preciso de uma ajuda sua, se me conseguir ajudar.
    Eu gostava de por o arduino a fazer o seguinte:
    Quando passasse vários objetos por um raio-x gostava que ele aleatóriamente apitasse e identificasse esse objeto acha que é possível fazer isso?