Manual
do
Maker
.
com
Dado eletrônico ou roleta eletrônica? Bem, não importa muito.
O projeto é simples, mas agradável; o buzzer builtin da placa MaxBlitz ajudou a dar o efeito sonoro de catraca e usando o FreeRTOS pude mostrar uma outra forma de monitorar um pino. Mas usar FreeRTOS no Arduino não é igual utilizar em uma placa própria para isso.
Meu propósito era mostrar alguns recursos de software diferentes, afinal, fazer um dado eletrônico ou uma roleta eletrônica (como preferir chamar) é uma tarefa simples. Mas pode ser bem interessante.
O código está em implementação, quero adicionar mais recursos, mas com o pouco implementado já pude notar que o FreeRTOS no Arduino não funciona tão "liso" como em um ESP32 ou em um STM32.
Utilizar a matriz de LEDs é só um dos recursos pretendidos. Na verdade, quero fazer algo mais utilizando o PCF8574 contido na placa da MaxBlitz e criar um jogo com a Roleta eletrônica. O código para isso já está implementado e (aparentemente) funcional, mas vou escrevendo artigos conforme adicionar recursos.
A matriz de LEDs pode ser adquirida no nosso parceiro Curto Circuito.
Já a placa MaxBlitz é um pouco mais complicada, porque é feita em lotes limitados e acaba muito rápido, mas você pode acompanhar no site do criador. Escrevi sobre esse standalone há poucos artigos antes desse.
Compus uma matriz bidimensional para guardar os números. Já é algo pouco usual nos projetos makers, então foi uma oportunidade de mostrar o recurso:
uint8_t numbers[8][8] = {{B00000000, B00000000, B01111111, B01000001, B01000001, B01111111, B00000000, B00000000},
{B00000000, B00000000, B00000001, B01111111, B01100001, B00100000, B00000000, B00000000},
{B00000000, B00110001, B01111001, B01000101, B01100101, B00100011, B00000000, B00000000},
{B00000000, B01101110, B01010011, B01010001, B01000001, B01000010, B00000000, B00000000},
{B00000000, B00000000, B01111111, B00001000, B00001000, B01111000, B00000000, B00000000},
{B00000000, B00000000, B01001110, B01010001, B01010001, B01110001, B00000000, B00000000},
{B00000000, B00000110, B01001001, B00101001, B00010001, B00001110, B00000000, B00000000},
{B00000000, B01110000, B01001000, B01000100, B01000011, B01000000, B00000000, B00000000}};
Não precisaria ser em binário, mas é uma forma de visualizar como se fosse pontos. Se colocar cada octeto um embaixo do outro, seria como se o número estivesse deitado para a esquerda. Primeiro desenhei no papel e então escrevi o array.
O som é a coisa mais simples do mundo, bastou tocar com a função tone passando o intervalo de tempo como parâmetro.
void plac(int timeToPlac){
tone(mbz.buzzer_pin,100,timeToPlac);
}
Para guardar os parâmetros da placa, criei uma struct, que deixa a lógica mais clara, fácil de achar onde a variável foi definida e a struct também permite cópias para outros propósitos. Não vai dar pra mostrar a principal utilidade da struct nesse programa, mas ela ficou assim:
struct MaxBlitz{
int stat : 1;
uint8_t buzzer_pin = 3;
uint8_t button = 4;
uint8_t builtin_led = 13;
uint8_t pcf_address = 0x20;
uint8_t pcf_pin[8] = {1,2,4,8,16,32,64,128};
} mbz;
O blink é o "hello world" da eletrônica digital, certo? Usamos pinMode e digitalWrite em Arduino, mas com essa função não é necessário fazer o pinMode. Ela verifica se está configurado como OUTPUT e, se não estiver, faz o ajuste diretamente no registrador do respectivo pino. Se for passado 0 no loop, essa função apenas liga o LED. Utilizei o LED builtin para saber quando passou pela função setup() e a partir de então a roleta eletrônica já estará pronta para uso.
Olha só que interessante; o registrador dos pinos 0 a 7 é o DDRB e o registrador do pino 8 ao 13 é o DDRD. Então a função verifica qual foi o pino solicitado e verifica se o pino já foi configurado como OUTPUT. Se não foi, o coloca. Poderia ser direto também, mas repare na condicional relacionado ao pino, que verifica se é maior que 7 e seu estado. Depois, faz um digitalWrite comum, mas poderia ser escrito diretamente no respectivo registrador também.
void blinker(int n_times, int x_delay, uint8_t p_pin){
//check pin mode
//0-7 port D (0 e 1 são serial, não usar)
//8-13 port B (bits 6 e 7 são cristal, não usar)
uint8_t pReg = p_pin > 7 ? p_pin-8 : p_pin-1;
if (p_pin > 7 && (DDRB^(1<<pReg))){
Serial.println(DDRB,BIN);
DDRB = DDRB|(1<<pReg);
Serial.println(DDRB,BIN);
Serial.println("<<<DDRB>>>");
}
else if ((DDRD^(1<<pReg))){
DDRD = DDRD&(1<<pReg);
}
uint8_t state = digitalRead(p_pin);
if ( n_times == 0 && x_delay == 0 ) {
digitalWrite(p_pin, !state);
return;
}
for (int t=0;t<n_times;t++){
digitalWrite(p_pin,!digitalRead(p_pin));
delay(x_delay);
}
}
A função roleta também tem algo interessante.
Para gerar números randômicos em qualquer plataforma, é necessário uma semente. Se a semente for estática, a sequência randômica será sempre a mesma. Para fazer algo mais dinâmico, podemos ler o valor de um pino analógico que não esteja em uso e aproveitar sua flutuação.
Dentro dessa função também é feito o deslocamento dos bits do PCF8574 para simular uma roleta "mesmo", que é a próxima fase desse projeto. Já está implementado, mas ainda preciso preparar os LEDs. Por fim, os números definidos para a matriz de LEDs 8x8 são chamadas conforme sua posição no loop, com delay incremental, para simular a desaceleração. Na última volta do loop, ele é interrompido no momento em que passar pela posição correspondente ao número randômico gerado.
uint8_t PCF_roulette(bool state){
uint8_t result = 0;
int wait = 100;
result = analogRead(0);
randomSeed(analogRead(0));
result = random(7);
Serial.println(result);
result = random(7);
Serial.println(result);
for (uint8_t j=0; j<5; j++){
for (uint8_t i = 0; i<8; i++){
Wire.beginTransmission(mbz.pcf_address);
Wire.write((1<<i));
Wire.endTransmission();
Serial.println((1<<i));
plac(10);
delay(wait);
wait += 10;
for (int p=0;p<8;p++){
lc.setRow(0,p,numbers[i][p]);
}
if (i == result && j == 4) return result;
}
}
}
Uma das vantagens em utilizar um sistema operacional de tempo real é a possiblidade de processamento assíncrono. No caso desse programa, não é nada que não pudesse ser resolvido com interrupção, mas para figurar o recurso, criei uma task chamada vTaskRoulette que fica em loop monitorando o botão que aciona a roleta. Uma vez a função disparada, não adianta apertar subsequentemente, porque o processo estará preso no loop da roleta e assim não precisamos nos preocupar em tratar uma interrupção, nem garantir que não haverá reentrância.
oid vTaskRoulette(void * pvParameters){
while (true){
if (digitalRead(mbz.button) == 0){
uint8_t val = PCF_roulette(HIGH);
Serial.print("Resultado: ");
Serial.println(mbz.pcf_pin[val]);
}
vTaskDelay(pdMS_TO_TICKS(100));
//delay(100);
}
}
Depois em setup(), simplesmente criamos a tarefa:
xTaskCreate(vTaskRoulette, (const portCHAR*)"vTaskRoulette", 128, NULL, 2, NULL);
Um dos macetes aqui é que em alguns lugares não dá pra usar os ticks invés de delay e vice-versa. Vou deixar isso pra explicar em outro momento, por enquanto vou deixar o código completo para quem tiver interesse em reproduzir o brinquedo. Como utilizei o VisualStudio Code para programar, foi necessário fazer o include de Arduino.h, mas na IDE do Arduino esse include não é necessário:
#include <Arduino.h>
#include <Arduino_FreeRTOS.h>
#include <Wire.h>
#include "LedControl.h"
//LedControl lc = LedControl(12,11,10,1);
LedControl lc = LedControl(12,11,10,1);
unsigned long delaytime=250;
/*
pin 12 is connected to the DataIn
pin 11 is connected to the CLK
pin 10 is connected to LOAD
We have only a single MAX72XX.
*/
struct MaxBlitz{
int stat : 1;
uint8_t buzzer_pin = 3;
uint8_t button = 4;
uint8_t builtin_led = 13;
uint8_t pcf_address = 0x20;
uint8_t pcf_pin[8] = {1,2,4,8,16,32,64,128};
} mbz;
uint8_t numbers[8][8] = {{B00000000, B00000000, B01111111, B01000001, B01000001, B01111111, B00000000, B00000000},
{B00000000, B00000000, B00000001, B01111111, B01100001, B00100000, B00000000, B00000000},
{B00000000, B00110001, B01111001, B01000101, B01100101, B00100011, B00000000, B00000000},
{B00000000, B01101110, B01010011, B01010001, B01000001, B01000010, B00000000, B00000000},
{B00000000, B00000000, B01111111, B00001000, B00001000, B01111000, B00000000, B00000000},
{B00000000, B00000000, B01001110, B01010001, B01010001, B01110001, B00000000, B00000000},
{B00000000, B00000110, B01001001, B00101001, B00010001, B00001110, B00000000, B00000000},
{B00000000, B01110000, B01001000, B01000100, B01000011, B01000000, B00000000, B00000000}};
//som breve para parecer uma catraca da roleta
void plac(int timeToPlaca);
//executa um blink por N vezes com intervalo X no pino P. Se n e x = 0, só inverte o estado do pino P.
void blinker(int n_times, int x_delay, uint8_t p_pin);
//Tarefa que monitora o botão
void vTaskButton(void *pvParameters);
//Tarefa pra tocar o buzzer
void vTaskSirene(void *pvParameters);
void vTaskRoulette(void * pvParameters);
/* Faz um loop tipo "blink" nos pinos escolhidos.
times - número de vezes que deve fazer o loop
pinsToLoop - Soma dos pinos a usar. Ex: mbz.pcf_pin[0] + mbz.pcf_pin[1] + mbz.pcf_pin[4], que equivale a 1+2+8, ou 11.
PCF_loop_state(3, 255); - loop em todos os pinos, 3 vezes.
PCF_loop_state(3, mbz.pcf_pin[0]+mbz.pcf_pin[7]); - loop nos pinos 0 e 7, por 3 vezes
*/
void PCF_loop_state(int times, uint8_t pinsToLoop);
/* Inverte o estado de um pino do PCF . Ex:
PCF_toggle_pin(7); - muda o estado do pino 7
*/
void PCF_toggle_pin(uint8_t pcf_pin);
/* Coloca os pino especificados em HIGH ou LOW, não importa se já está. Ex:
PCF_set_pins_state(mbz.pcf_pin[0] + mbz.pcf_pin[7], HIGH); - coloca o bit 0 e o 7 em HIGH
*/
void PCF_set_pins_state(uint8_t pcf_pins, bool state);
/* Pega o estado do pino e retorna. Se estiver em LOW, retorna 0,
se estiver em HIGH, retorna o valor do pino. Ex:
uint8_t stat = PCF_get_pin_state(mbz.pcf_pin[7]); - retorna 128 se estiver em HIGH
uint8_t stat = PCF_get_pin_state(mbz.pcf_pin[0]); - retorna 1 se estiver em HIGH
*/
uint8_t PCF_get_pin_state(uint8_t pcf_pin);
/* Faz um sorteio randômico de um dos pinos do PCF8574 e o coloca no estado do parâmetro */
uint8_t PCF_roulette(bool state);
void PCF_loop_state(int times, uint8_t pinsToLoop){
}
void PCF_toggle_pin(uint8_t pcf_pin){
}
void PCF_set_pins_state(uint8_t pcf_pins, bool state){
}
uint8_t PCF_get_pin_state(uint8_t pcf_pin){
}
void blinker(int n_times, int x_delay, uint8_t p_pin){
//check pin mode
//0-7 port D (0 e 1 são serial, não usar)
//8-13 port B (bits 6 e 7 são cristal, não usar)
uint8_t pReg = p_pin > 7 ? p_pin-8 : p_pin-1;
if (p_pin > 7 && (DDRB^(1<<pReg))){
Serial.println(DDRB,BIN);
DDRB = DDRB|(1<<pReg);
Serial.println(DDRB,BIN);
Serial.println("<<<DDRB>>>");
}
else if ((DDRD^(1<<pReg))){
DDRD = DDRD&(1<<pReg);
}
uint8_t state = digitalRead(p_pin);
if ( n_times == 0 && x_delay == 0 ) {
digitalWrite(p_pin, !state);
return;
}
for (int t=0;t<n_times;t++){
digitalWrite(p_pin,!digitalRead(p_pin));
delay(x_delay);
}
}
uint8_t PCF_roulette(bool state){
uint8_t result = 0;
int wait = 100;
result = analogRead(0);
randomSeed(analogRead(0));
result = random(7);
Serial.println(result);
result = random(7);
Serial.println(result);
for (uint8_t j=0; j<5; j++){
for (uint8_t i = 0; i<8; i++){
Wire.beginTransmission(mbz.pcf_address);
Wire.write((1<<i));
Wire.endTransmission();
Serial.println((1<<i));
plac(10);
delay(wait);
wait += 10;
for (int p=0;p<8;p++){
lc.setRow(0,p,numbers[i][p]);
}
if (i == result && j == 4) return result;
}
}
}
void vTaskSirene(void *pvParameters){
float sinVal = 0;
int toneVal = 0;
for (uint8_t i=0; i<5;i++){
for (uint8_t x=0;x<180;x++){
//converte graus em radianos
sinVal = (sin(x*(3.1412/180)));
//agora gera uma frequencia
toneVal = (int(sinVal*100))-100;
//toca o valor no buzzer
tone(mbz.buzzer_pin,toneVal,1000);
//atraso de 2ms e gera novo tom
vTaskDelay(pdMS_TO_TICKS(2));
}
}
noTone(mbz.buzzer_pin);
vTaskDelete(NULL);
}
void plac(int timeToPlaca){
tone(mbz.buzzer_pin,100,timeToPlaca);
}
void vTaskRoulette(void * pvParameters){
while (true){
if (digitalRead(mbz.button) == 0){
uint8_t val = PCF_roulette(HIGH);
Serial.print("Resultado: ");
Serial.println(mbz.pcf_pin[val]);
}
vTaskDelay(pdMS_TO_TICKS(100));
//delay(100);
}
}
void setup() {
Serial.begin(9600);
pinMode(mbz.buzzer_pin,OUTPUT);
pinMode(mbz.button,INPUT_PULLUP);
lc.shutdown(0,false);
lc.setIntensity(0,8);
lc.clearDisplay(0);
Wire.begin();
delay(2000);
//xTaskCreate(vTaskSirene, (const portCHAR*)"vTaskSirene", 128, NULL, 1, NULL);
xTaskCreate(vTaskRoulette, (const portCHAR*)"vTaskRoulette", 128, NULL, 2, NULL);
//blinker(10,80,13);
//delay(2000);
blinker(0,0,13);
}
void loop() {
Serial.print(".");
delay(1000);
}
Deixei uma demonstração no instagram e em nossa página no facebook, dê uma conferida na roleta eletrônica funcionando, minha filha viciou na brincadeira, e olhe que agora é só um número randômico!
Continuo na divulgação do curso que produzi, tendo 3 horas de conteúdo e mostrando desde a instalação do sistema em Windows e Linux à manipulação do GPIO em Shell Script, C e Python, além de interagir com dispositivos I2C, proteção do sistema de arquivos e como fazer um backup reduzido do sistema, entre outros.
Use o cupom de desconto DOBITAOBYTE e adquira o curso por R$21,00. Assim você apoia o blog e motiva outros cursos com Raspberry e outras plataformas, valeu?
Inscreva-se no nosso canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.