Arquivos Projetos - Blog Ryndack Componentes
Rua Jovelina Claudino Buhrer, 440 - São José dos Pinhais - PR (41) 3383-3034
Projetos

O LED é um componente que traz muitas possibilidades de uso, pois ele serve tanto pra dar um toque para algum projeto, como também podemos criar coisas utilizando apenas eles e aproveitando para soltar a imaginação brincando com suas cores, variando formatos e caprichando na programação com os estilos de piscar. Por isso hoje trouxemos pra vocês um cubo de LEDs! Vamos começar então pela parte do funcionamento.

Funcionamento do cubo de leds

Note que os LEDs dessa montagem estão dispostos como uma matriz 3×9. Além disso, todos os LEDs de uma linha estão conectados pelos seus catodos, enquanto os LEDs de uma coluna estão ligados pelos seus anodos. Dessa forma, se queremos selecionar um LED específico, basta selecionar uma linha e uma coluna.


Analogamente para se selecionar os LEDs do cubo basta selecionar seu nível e sua coluna. Dessa forma podemos usar o arduino para selecionar posições no cubo de led. Assim, quando queremos acender determinado LED, devemos acionar a saída correspondente a linha, ou ao “andar” no caso do cubo, e a coluna específica, enquanto garantimos que as demais linhas se mantenham em zero. O código abaixo mostra como fazemos para acender o LED do centro do cubo.


void setup(){
   pinMode(A1, OUTPUT);
   pinMode(6 , OUTPUT);
}
void loop(){
   digitalWrite(A1, HIGH);

   digitalWrite(6 , HIGH);
}


Nesse trecho de código, chamamos a atenção para o fato que as linhas e as colunas estão enumeradas de acordo com a ordem em que elas estão conectadas no arduino. Além disso, destacamos que ao agrupar os leds em níveis, conseguimos reduzir a quantidade de portas usadas pelo arduino de 27 para 12. O nome desta técnica é multiplexação e ela é muito usada em eletrônica para reduzir o número de barramentos usados na transmissão de dados. Agora que já entendemos o funcionamento do cubo de LEDs, vamos para uma das partes mais legais: a montagem!

Montagem


Antes de tudo, precisamos ter em mãos os seguintes materiais:

  1. LEDs (27 unidades);

  2. Protoboard;

  3. Resistores de 330R (9 unidades);

  4. Resistores de 10k (3 unidades;

  5. Transistores 2N2222 (3 unidades);

  6. Fios para as ligações;

Com esses materiais em mãos, a primeira parte desse projeto é montar o cubo, para isso vamos construir uma guia, nela os leds serão posicionados lado a lado, com espaços constantes entre eles, assim conseguiremos montar o cubo de forma rápida, garantindo que seus lados fiquem retos. Essa guia consiste em um quadrado de 50mm de lado com nove furos com espaços de 20 mm entre eles. Cada furo deverá ter o diâmetro do led, que para o nosso caso é de 5 mm.

Para construir essa guia você pode fazer do modo que desejar, mas nós usamos uma impressora 3D. Assim, primeiro desenhamos ela com o tinkercad e geramos o desenho no formato STL deste link, em seguida usamos o Cura para criar o arquivo .gcode. Por fim imprimimos esse arquivo usando a nossa impressora 3D. A peça ficou como mostrado abaixo.



Agora que temos essa peça em mãos, posicionamos um led em cada furo, e soldamos os seus catodos com o do led ao lado, formando uma pequena grade, como mostra a figura abaixo, essas grades serão usadas para confeccionar os andares do nosso cubo, portanto precisamos de três peças deste tipo.



Finalmente usando algum tipo de apoio (no nosso caso usamos um pedaço de papelão), soldamos as três grades uma sobre a outra, unindo apenas os anodos dos leds de cima aos de baixo, o que nos deixa com um cubo de 3 níveis e 9 colunas, como as imagens abaixo.



Circuito de controle


Para realizar as conexões do circuito de controle pegamos um pedaço de cabo flat de 12 vias e o separamos em 4 pedaços de 3 vias, assim conseguimos soldar estes pedaços a cada nível e a cada coluna do cubo de led, ficando um pedaço para cada linha de colunas do cubo, enquanto o último pedaço é soldado nos níveis, conforme vocês podem ver na imagem abaixo.


Para os cabos soldados nas colunas, a outra extremidade é ligada nas saídas numeradas de 2 até 10 de um Arduino, com cada via passando antes por um resistor de 330Ω. Já para os 3 níveis conectamos cada via ao coletor de transistores NPN 2N2222. Esses transistores possuem seus emissores aterrados, enquanto suas bases estão ligadas às saídas A0, A1 e A2 do Arduino por meio de resistores de 10k. Isso está representado na imagem abaixo.




Agora que já montamos nosso cubo e já sabemos o básico sobre o seu funcionamento, vamos começar a criar e explorar alguns efeitos que podemos criar com ele!


Explorando com o cubo

1 – Função “seleciona_nivel()”


Essa função é bem simples e o objetivo dela é escrever como alto o nível selecionado, enquanto demais são mantidos em nível lógico baixo. Ela ficou implementada como segue:


void seleciona_nivel(int nivel){
   for (int i = 0; i < 3; ++i){
      digitalWrite(niveis[ i ], LOW);
}
   if (nivel >= 1 && nivel <= 4){
      if (nivel != 4) {
         digitalWrite(niveis[nivel - 1], HIGH);
      }
      else{
         for (int i = 0; i < 3; ++i){
            digitalWrite(niveis[ i ], HIGH);
         }
      }
   }
}

Aqui começamos escrevendo todos os níveis como baixo, em seguida é verificado se o valor de nível escolhido está no intervalo correto, se não está, então nada é realizado, apenas se mantêm os níveis como baixo, mas se ele está no intervalo correto, então o nível selecionado é escrito como alto. Observe que se o nível selecionado for 4 então todos os níveis são descritos como alto.

Veja que o nível foi acionado por meio de um vetor chamado “niveis[ i ]”, esse é um vetor de 3 posições com cada nível do cubo descrito nele. Além desse vetor, temos outro para as colunas (com nove posições) e uma matriz para os estados do cubo.


2 – Função “envia_estados()”


A segunda função implementada é a função enviar estados. Ela funciona com o auxílio da variável global “int estados[3][9];”, e é responsável por enviar os valores dessa matriz para a saída, então o que ela faz é simplesmente realizar uma varredura da matriz e enviar os valores dela para as saídas correspondentes do Arduino. Ela ficou como mostra abaixo.


//Envia a matriz que representa os niveis dos led para o cubo.
//Pode realizar uma varredura nivel a nivel (varredura 1) ou
//habilitar todos os niveis simultaneamente (varredura 2).
void envia_estados(int estados[ 3 ][ 9 ], int varredura){
   if(varredura==1){
      for(int i=0; i<=2; i++){
         for(int j=0; j<9; j++){
            digitalWrite(colunas[ j ], estados[ i ][ j ]);
            seleciona_nivel(i+1);
         }
      }
   }

   else if(varredura==2){
      for(int j=0; j<9; j++){
         digitalWrite(colunas[ j ], estados[ 0 ][ j ]);
      }
      seleciona_nivel(4);
   }
}

O código começa verificando o modo de varredura desejado, ou seja, se é desejado realizar uma varredura nível a nível, ou se o objetivo é controlar as colunas apenas. Se o modo escolhido for o 1, então temos dois loops, o primeiro acionando as colunas e o segundo selecionando o nível, enviando assim todos os valores da matriz para a saída. Entretanto, se a opção foi a 2 então temos apenas um loop lendo as colunas, e o único nível enviado para a saída é o nível 0 da matriz.


3 – Função “acende_limpa_cubo()”


Essa função é bem simples, ela é formada apenas pelos loops para a escrita em matriz, como mostrado abaixo:



//Função torna alto ou baixo o nivel lógico de todos os leds do cubo.
void acende_limpa_cubo(int on_off){

   for(int i=0; i<4; i++){
      for(int j=0; j<9; j++){
         estados[i][j]=on_off;
      }
   }
}

Nessa função temos que o valor de “on_off” é escrito em todas as posições da matriz “estados[ 3 ][ 9 ]”, assim, se passarmos 1 para esse função e posteriormente enviar essa matriz para a função “envia_estados()”, o cubo acenderá completamente, mas ele apagará completamente se o valor de on_off for 0.


4 – Função “led_aleatorio()”


Como o nome já diz, o objetivo dessa função é acender um led aleatório, ou seja, colocar em alto uma posição aleatória na matriz estados. Para isso usamos a função “random()” do Arduino para selecionar a posição aleatória, como no código abaixo.


//Acende ou apaga um led aleatório do cubo.
void led_aleatorio(int pausa, int on_off){
   estados[random(0,3)][random(0,9)]=on_off;
   for(int tempo=0; tempo<pausa; tempo++){
      envia_estados(estados, 1);
   }
}

Essa função recebe dois valores, a “pausa” determina por quanto tempo o led ficará aceso, porém, diferente de outros lugares do código, aqui não usamos a função “delay()” e sim um “for”. Fazemos isso porque o delay iria pausar a execução do código, mantendo apenas o último comando da função “envia_estados()’, por outro lado o “for” executa essa função completamente, várias vezes, dando a impressão de que o programa foi pausado.

O outro parâmetro recebido pela função é o “on_off”, que assim como o parâmetro de mesmo nome atua na função “acende_limpa_cubo()”, determina se os leds serão ligados ou desligados.


5 – Função “Plano_vertical()”


A próxima função que trazemos aqui é a função “plano_vertical()”, nessa função acendemos leds que estão posicionados lado a lado, gerando planos verticais. O código ficou como abaixo:


//Acende conjuntos de 3 colunas do cubo, formando planos.
void plano_vertical(int plano){
   if(plano>=1 && plano<=3){
      for(int i=0; i<3; i++){
         for(int j=(3*(plano-1)); j<(3*plano); j++){
            estados[i][j]=1;
         }
      }
   }
   else if(plano>=4 && plano <=6){
      for(int i=0; i<3; i++){
         for(int j=(plano-4); j<(plano+3); j+=3){
            estados[i][j]=1;
         }
      }
   }
}

Primeiramente os planos que essa função gera estão enumerados de 1 a 6, sendo que os de 1 a 3 são paralelos ao plano frontal e os de 4 a 6 são perpendiculares a estes. Assim, essa função recebe o parâmetro “plano” para selecionar o plano que será acionado, e por meio dos loops adequados ele envia nível alto para a matriz de estados.


6 – Função “gira_led()”

Agora vamos falar da última função criada para controlar o cubo, a função “gira_led()”.A ideia por trás dessa função está em usar uma matriz 4×9, sendo que em cada linha armazenamos os níveis lógicos de todas as colunas de led, dessa forma ao alternamos entre as linhas dessa matriz, os leds em nível alto irão aparentar ser deslocados.

Assim podemos gerar um efeito de rotação usado um loop que envia os valores presentes na linha para saída, e em seguida passa para a próxima linha após uma breve pausa.

Essa função ficou como abaixo:


//Função cria o efeito de leds girando em torno da coluna
//central. A quantidade de voltas pode ser inserida e o sentido
//de rotação pode ser horário ou anti-horário, sendo para horário
//e 2 para anti-horário.
void gira_led(int pausa, int voltas, int sentido) {
   // Padrões de ligar/desligar dos LEDs para cada passo do movimento
   const int padrao_horario[4][9] = {
      {LOW, LOW, HIGH, LOW, HIGH, LOW, HIGH, LOW, LOW},
      {LOW, LOW, LOW, HIGH, HIGH, HIGH, LOW, LOW, LOW},
      {HIGH, LOW, LOW, LOW, HIGH, LOW, LOW, LOW, HIGH},
      {LOW, HIGH, LOW, LOW, HIGH, LOW, LOW, HIGH, LOW}
   };
   const int padrao_antihorario[4][9] = {
      {LOW, LOW, HIGH, LOW, HIGH, LOW, HIGH, LOW, LOW},
      {LOW, HIGH, LOW, LOW, HIGH, LOW, LOW, HIGH, LOW},
      {HIGH, LOW, LOW, LOW, HIGH, LOW, LOW, LOW, HIGH},
      {LOW, LOW, LOW, HIGH, HIGH, HIGH, LOW, LOW, LOW}
   };

   int (*padrao)[9]; // Ponteiro para o padrão de acordo com o sentido
   if (sentido == 1) {
      padrao = padrao_horario;
   }
   else {
      padrao = padrao_antihorario;
   }
   for (int i = 0; i < voltas; i++) {
      for (int j = 0; j < 4; j++) {
         // Define o estado dos LEDs de acordo com o padrão atual
         for (int k = 0; k < 9; k++) {
            digitalWrite(colunas[k], padrao[j][k]);
         }
         delay(pausa);
      }
   }
}

Para essa função enviamos os valores de “pausa”, “voltas” bem como o “sentido”.

Começando pela “pausa”, esse parâmetro define o tempo entre um estado e outro, assim, se quisermos um efeito de rápida rotação passamos um valor baixo para a “pausa”. O parâmetro “voltas” define a quantidade de vezes que o efeito se repete, ou seja, se for enviado 10 para “voltas”, esse efeito será repetido 10 vezes. Finalmente o “sentido” de rotação é definido, sendo que 1 é para sentido horário e 2 para anti-horário.


Loop Principal


Finalmente vamos falar do nosso loop principal, nele, o que fazemos é usar as funções já apresentadas para criar diversos efeitos com o cubo. Por exemplo, o trecho abaixo apresenta uma forma de acender o cubo, acendendo leds de forma aleatória:


for(int n=0; n<100; n++){ //Cubo acendendo 1 led por vez, de forma aleatória
   led_aleatorio(500,1);  //até todos os leds serem acesos.
}
acende_limpa_cubo(1);
for(int n=0; n<1000; n++){
   envia_estados(estados, 1);
}

Aqui executamos a função “led_aleatório()” 100 vezes, assim o cubo vai acendendo lentamente, um led por vez. Entretanto para garantir que qualquer led que não tenha sido aceso ainda acenda, ao final da execução dessa função, usamos a função “acende_limpa_cubo(1)”. Para apagar o cubo lentamente podemos usar o mesmo código, apenas mudando o valor de “on_off” enviado para as funções “led_aleatório(500, on_off)” e “acende_limpa_cubo(on_off)”, ou seja mudar o valor de “on_off” para 0.

Outro efeito que podemos é fazer planos “caminharem” pelo cubo, para isso a função “planos_venticais()” é usada como mostrado abaixo.


acende_limpa_cubo(1);
for(int nivel=0; nivel<4; nivel++){ //"planos" subindo e descendo.
   seleciona_nivel(nivel);
   delay(250);
}
seleciona_nivel(0);
delay(250);
for(int nivel=3; nivel>=0; nivel--){
   seleciona_nivel(nivel);
   delay(250);
   if(nivel==0){
      acende_limpa_cubo(0);
      envia_estados(estados, 2);
   }
}

for(int n=0; n<3; n++){ //"planos" verticais se movendo para lateralmente.
   plano_vertical(n+4);
   envia_estados(estados,2);
   delay(250);
   acende_limpa_cubo(0);
   envia_estados(estados, 2);
}

delay(250);

for(int n=0; n<3; n++){
   plano_vertical(6-n);
   envia_estados(estados,2);
   delay(250);
   acende_limpa_cubo(0);
   envia_estados(estados, 2);
}

for(int n=0; n<3; n++){ //"planos" verticais se movendo para tras e para frente.
   plano_vertical(n+1);
   envia_estados(estados,2);
   delay(250);
   acende_limpa_cubo(0);
   envia_estados(estados, 2);
}

delay(250);

for(int n=0; n<3; n++){
   plano_vertical(3-n);
   envia_estados(estados,2);
   delay(250);

   acende_limpa_cubo(0);
   envia_estados(estados, 2);
}

A princípio temos a função “acende_limpa_cubo(1)” mantendo todos os valores da matriz de estados em alto, em seguida, os dois laços de repetição alternam os níveis acionados, causando o efeito de “subir e descer”. Os laços seguintes usam a função “planos_verticais()” para produzir um efeito similar nas demais direções.

O ultimo efeito do qual vamos falar usa a função “gira_led()”, ao contrário das funções anteriores essa não necessita de um loop() para se realizar um efeito, sendo que ela foi aplicada como abaixo:


seleciona_nivel(3); //Efeito de leds girando uma vez em cada nivel,
gira_led(75, 15, 2); //alternando o sentido.
seleciona_nivel(2);
gira_led(75, 15, 1);
seleciona_nivel(1);
gira_led(75, 15, 2);

Nesse trecho de código gera o efeito de rotação uma vez em cada nível em sentidos alternados, ou seja, no sentido anti-horário nos níveis 1 e 3 e no sentido horário no nível 2, gerando 15 rotações por nível.

Enfim, o código deste projeto bem como o de outros projetos do nosso blog podem ser encontrados no nosso github.

Não esqueça de contar para a gente o que você achou desse projeto nos comentários e qual o próximo que vocês querem ver por aqui!!!

0

Projetos


Os hamsters são animais conhecidos por seu habito de correr incessantemente em suas rodinhas, desse modo é normal se perguntar que velocidade eles alcançam, ou ainda, querer saber que distancia eles percorrem. Pensando nisso construímos um velocímetro e contador de voltas para rodinhas de hamsters. Curioso para saber como? Não se preocupe que contamos neste post.

Antes de mais nada vamos aos materiais usados, que são os seguintes:

  1. Arduino Uno;

  2. Display LCD 16×2;

  3. Sensor infravermelho TCRT5000;

  4. Resistores de 330Ω, 3,3kΩ e 10kΩ (1 de cada);

Montando o velocímetro

A ideia aqui é usar um sensor infravermelho TCRT5000  para encontrar o momento em que o hamster completa uma volta na sua rodinha, e com o tempo de cada volta, juntamente ao tamanho da rodinha calcularmos a velocidade e a distancia percorrida por ele, para em seguida exibir esses valores em um display LCD. Para isso, vamos montar o circuito como mostrado na figura abaixo.

Nessa figura temos que o display LCD esta ligado com os pinos D4 a D7 nos pinos de mesmo nome do Arduino (ou seja, nos pinos PD4 a PD7 no Atmega328p), além disso, os pinos  E, R/W e RS estão conectados nos pinos A0, A1 e A2 (ou PC0, PC1 e PC2) respectivamente. Para a alimentação do display, assim como o TCRT5000, esta sendo utilizado os 5V fornecidos pelo próprio Arduino.

O funcionamento do TCRT5000 se da por meio de um fototransistor acoplado a um LED infravermelho, assim, quando um objeto é posto próximo desse sensor, a luz infravermelha é refletida para o fototransistor, que atuara como um circuito fechado. No nosso circuito, o emissor do fototransistor esta conectado ao terra (gnd), portanto quando ele atuar como um circuito fechado temos que a tensão no coletor do fototransistor e consequentemente pino D8 do Arduino (ou PB0 para o Atmega, pino onde o conector esta ligado) sera de 0V. Já quando não existir nenhum objeto próximo ao TCRT5000, o fototransistor funciona como um circuito aberto, e a tensão de seu coletor sera de 5V, ja que o mesmo esta ligado nessa tensão por meio de um resistor de 10kΩ.

Programando nosso velocímetro

Agora que já temos os materiais em mãos e montamos o nosso circuito, iremos programar o Arduino. O código completo usado nesse projeto, além da biblioteca usada para controlar o LCD estão presentes no nosso GitHub.

O que vamos fazer no código é usar o timer0 para nos fornecer uma medida de tempo para o calculo da velocidade de nosso velocímetro, para isso, precisamos que ele seja reiniciado a cada volta completada pelo hamster. Com esse fim vamos usar a interrupção por mudança de estado do pino PB0, assim, sempre que o microcontrolador receber um sinal do TCRT5000, ele sofrera uma interrupção, e nela ocorrera a contagem da quantidade de voltas dada pelo Hamster e a inicialização do timer, sendo que após duas interrupções suscetivas, ou seja, duas voltas completas, a velocidade é calculada. Entretanto, se a rodinha ficar inativa por muito tempo (mais que 10 segundos), usaremos a interrupção por estouro do timer para desliga-lo.

Para simplificar a escrita e a compreensão do nosso código, usamos 5 funções (além da main e das interrupções), que servem para ligar o LCD, configurar o pino PB0 como entrada habilitando as interrupções, configurar o timer0, realizar os cálculos necessários e criar um loop que mantem o circuito sempre em funcionamento. Cada função usada esta explicada abaixo.

Função “Liga_LCD()”

A primeira função que vamos discutir é a função “Liga_LCD()”, ela foi implementada como presente abaixo.

void Liga_LCD(void){
          LCD_init();
          LCD_clear();
          LCD_move_cursor(0,1);
          LCD_write("Hodometro para");
          LCD_move_cursor(1,4);
          LCD_write("Hamsters");
          _delay_ms(2000);
          LCD_clear();
}

Nela iniciamos o display usando “LCD_init()”, então limpamos o display com “LCD_clear()” e finalmente a mensagem “Hodometro para Hamsters” é exibida por dois segundos com as funções “LCD_write()”, “LCD_move_cursor()” e “_delay_ms()”. 

A função LCD_init() envia um conjunto de instruções que configuram o display no modo de 4 bits com o cursor ligado. Enquanto isso a função LCD_clear() apaga tudo o que esta exibido no display e retorna o cursor para a primeira posição da primeira linha. Já a função LCD_move_cursor() posiciona o cursor na posição desejada no display, essas funções fazem parte da biblioteca LCD.h. Já a ultima função _delay_ms() pertence a biblioteca <util/delay.h> e pausa a execução do programa pelo tempo desejado.

Função “configura_contador()”

A próxima função configura PB0 como entrada no registrador DDRB, em seguida, o pull up é ativado, isso mantém o nível de tensão do pino PB0 em 5V, a menos que a entrada seja 0V, o que facilita a identificação de transições para baixo. Logo após, usamos os registradores PCICR e PCMSK0, para habilitar as interrupções por mudança de estado do pino PB0, assim, sempre que o pino PB0 muda de estado, ocorre uma interrupção. Essa função ficou como mostra o exemplo abaixo:

void configura_contador(void){
          DDRB &= ~(1<<PB0);
          PORTB |= (1<<PB0);

          PCICR |= (1<<PCIE0);
          PCMSK0 |= (1<<PCINT0);
}

A rotina da interrupção para a mudança de estado do pino PB0 em primeiro lugar realiza a leitura desse pino, e se este sofreu transição para nível alto, apenas um pequeno delay de 50ms é realizado, mas se a transição é para nível baixo, então o timer0 é reiniciado, a quantidade de voltas é incrementada, e o valor do tempo é salvo na variável “tempo”. Essa interrupção foi implementada como mostrado a seguir:

ISR(PCINT0_vect){
          if(PINB & (1<<PB0)){
          _delay_ms(50);
          }
          else{
                    voltas +=1;
                    voltas_total +=1;
                    tempo = i;
                    i = 0;
_delay_ms(50);

TCCR0B |= (1<<CS00);
TCNT0 = 0; } }

Função “configura_timer()”

Da mesma forma que a função configura contador, essa função configura uma interrupção, mas desta vez por estouro do timer0. Para isso, primeiro configura-se o timer0 no modo de operação normal e desconectado. Essa configuração foi selecionada, pois com ela é possível obter uma precisão de 0,16ms, que é o suficiente para a construção do velocímetro. Por fim a interrupção por estouro do timer é habilitada. A função que configura o timer esta presente abaixo.

void configura_timer(void){
          TCCR0A = 0;
          TCCR0B = 0;
          TIMSK0 |= (1<<TOIE0);
}

Com essa configuração temos 62500 interrupções por estouro por segundo, sendo que a cada interrupção a variável “i” é acrescida em 1 e quando ocorre uma interrupção por mudança de estado do pino PB0, o valor de “i” é salvo em “tempo” e “i” é reiniciada com zero. Entretanto, se não ocorre nenhuma mudança de estado em PB0 dentro de 10 segundos, então i>624999 e quando isso ocorre, a interrupção desconecta o timer e reinicia as variáveis “voltas” e “i”. Essa interrupção está abaixo.

ISR(TIMER0_OVF_vect){
          i++;
          if (i>624999){
                    i=0;
                 
                    voltas = 0;
                    TCNT0 = 0;
                    TCCR0B &= ~(1<<CS00);
          }
}

Ao se desconectar o timer o Arduino fica aguardando uma interrupção por mudança de estado do PB0 para reiniciar o timer. Além disso o velocímetro é zerado.

Função “calcula_velocidade()”

O código desta função está abaixo:


void calcula_velocidade(float tempo, float raio){
    if (voltas > 1){
        tempo = tempo/(62500);
        distancia = (voltas_total-1)*2*3.1416*raio;
        velocidade = 3.6*2*3.1416*raio/tempo;
    }
    else {
        velocidade = 0;
    }
}

Esta função é a responsável pelo calculo da velocidade do hamster em km/h. Para isso, primeiro é verificado se houveram 2 incrementos na variável “voltas”, uma vez que na primeira vez que o TCRT5000 é ativado, o timer está zerado. Se já houveram 2 incrementos em voltas, então o tempo em segundos é calculado usando:

(1)   \begin{equation*} tempo=\frac{tempo}{62500} \end{equation*}

Com o tempo em segundos calculamos a velocidade com a equação abaixo.

(2)   \begin{equation*} velocidade=3.6*\frac{2*\pi*raio}{tempo} \end{equation*}

Nessa equação temos que 2*\pi*raio é o comprimento da circunferência da rodinha do HHamster, ou seja, a distancia que ele percorre em uma volta completa. Assim a velocidade será obtida dividindo este valor pelo tempo. O fator 3.6 é usado para converter a velocidade (encontrada em metros por segundo) para quilômetros por hora.

Além disso, para calcular a distancia percorrida pelo Hamster basta multiplicar a circunferência de sua rodinha pelo numero de voltas dada, como abaixo.

(3)   \begin{equation*} distância=2*\pi*raio*numero-de-voltas \end{equation*}

Função “loop()”

Por fim temos a função loop(), nossa ultima função, que ficará sempre em execução, sendo implementada como abaixo:



void loop(void){
    char str_distancia[20];
    char str_velocidade[20];
    int vel_int;
    int vel_dec;
    int distancia_int;
    int distancia_dec;
   
    while(1){
                calcula_velocidade(tempo, raio);
                distancia_int = floor(distancia);
                distancia_dec = 100*(distancia-distancia_int);
                sprintf(str_distancia, "Dis: %d,%d[m]   ", distancia_int, distancia_dec);
                vel_int = floor(velocidade);
                vel_dec = 100*velocidade - 100*vel_int;
                sprintf(str_velocidade, "Vel: %d,%d[km/h]   ", vel_int, vel_dec);
                LCD_move_cursor(0,0);
                LCD_write(str_distancia);
                LCD_move_cursor(1,0);
                LCD_write(str_velocidade);
                voltas_cont=voltas;
         }
    }   
}

Nessa função, primeiro temos a declaração das variáveis a serem utilizadas sendo duas strings e quatro variáveis int. Em seguida, temos um loop infinito (while(1)), nele utiliza-se a função calcula_velocidade() para se calcular a velocidade e a distancia total percorridas pelo Hamster. Em seguida, estes valores são gravados em números inteiros, primeiro a parte inteira, depois as duas primeiras casas depois da virgula. Isso porque a função sprintf, usada para transformar números em strings, não funciona com variáveis do tipo float. Finalmente esses valores são exibidos no display do nosso velocímetro.

Função “main()”

Por fim, temos a função main do nosso programa, que ficou como a seguir:

void main(void){   
    SREG   |=  (1<<7);
    voltas  = 1;
    Liga_LCD();
    configura_contador();
    configura_timer();

    loop();
}

Na função main, apenas habilita-se as interrupções globais e chama-se as funções Liga_LCD(), configura_contador(), configura_timer() e loop().

Testando

Para testar o nosso projeto contamos com a ajuda do Antônio, o nosso hamster atleta, e ao final de uma noite ele correu por 3200 metros. No link abaixo você encontra o nosso projeto funcionando.
https://youtube.com/shorts/fL2wfnrB3ng?feature=share
E então, o que vocês acharam deste projeto? Conte pra gente nos comentários.


0

Projetos

É provável que você já tenha desejado reproduzir um áudio com o Arduino, mas como se faz isso? Vem com a gente, que nesse post você vai aprender!

A ideia por traz do projeto desse post é usar uma rede R2R, que é um conversor digital analógico formado por alguns resistores (se você ainda não conhece, confira o nosso post sobre ela), para reproduzir um áudio salvo na memória de um Arduino, de maneira simples e barata, usando poucos componentes.

Para esse projeto você não irá precisar de muita coisa, os materiais são:

  1. 25 resistores de 1kΩ

  2. 1 capacitor de 100nF

  3. Uma caixa de som

  4. Um Arduino

Esse projeto pode ser dividido em 3 etapas, sendo elas:

  1. Escolha e extração de um arquivo de áudio

  2. Escrita do código a ser executado

  3. Montagem do circuito


Escolha e Extração do Áudio 

A primeira etapa é a escolha de um arquivo de áudio. Para isso, precisamos levar em conta uma limitação comum em microcontroladores, a memória. No caso do Arduino, ela é de 32Kb, então precisamos garantir que o arquivo de áudio utilizado e o programa escrito não excedam este valor.

Assim, escolhemos um arquivo que seja curto, e com uma baixa frequência de amostragem. Para este artigo foi escolhido um áudio de cerca de 16 segundos com uma frequência de amostragem de 2000 amostras por segundo.
Com um arquivo de áudio selecionado, é preciso transformar ele em um vetor, que será gravado no Arduino. Para isso, usamos a função audioread do octave. O resultado dessa função é uma matriz [y fs], na qual y são os dados de áudio e fs a taxa de amostragem.

Os dados y são dispostos na forma de uma matriz que tem os frames de áudio colocados nas linhas e os canais nas colunas. A fim de simplificar o processo e de gerar um arquivo menor usamos um áudio mono, que fará com que y possua apenas uma coluna.

O próximo passo é transformar y em um vetor de uma unica linha com valores inteiros de 0 a 255, o que é possível com algumas contas simples e com a função reshape. Por fim, esse arquivo é salvo como um arquivo “.h”, que sera usado no código do Arduino. Isso esta demonstrado no script abaixo.

# Le o arquivo de audio, aqui usamos inicio e fim de modo que fim-inicio = numero_de_amostras. 
[y fs] = audioread("nome_do_arquivo_de_audio", [inicio fim]) 

# Determina o numero de linhas e o numero de colunas (1 para áudios mono) de y. 
[linha coluna] = size(y) 

# Transpõe y, em um vetor de 1 linha. 
y = reshape(y, 1, linha) 

# Transforma y em um vetor de 0 a 255 com números inteiros e salva em audio. 
audio = round(255*y+128) 

# Cria um arquivo .h com "const unsigned char pontos[ ] PROGMEM = {" salvo na primeira linha. 
dlmwrite("Audio.h", "const unsigned char pontos[ ] PROGMEM = {", "") 

# Adiciona o vetor com o audio ao arquivo .h criado. 
dlmwrite("Audio.h", audio, ",", "-append") 

# Fecha a chave. 
dlmwrite("Audio.h", "}", "", "-append")



Importante lembrar que para este script funcionar adequadamente, o arquivo de áudio deve estar salvo na mesma pasta que o octave. A fim de melhor aproveitar a memória do microcontrolador, foi usado o comando PROGMEM, que faz com que a variável, quando gravada, seja salva na memória flash, e não na RAM do Arduino.


Código do Arduino

O código implementado no Arduino ficou como está a seguir

#define F_CPU 16000000

#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include "Pontos.h"

int i=0, Min=130;

//Interrupção quando há um estouro do timer0.
ISR(TIMER0_OVF_vect){ 

     //Reinicia o timer0.
     TCNT0 = Min; 

     //Manda o valor da posição i do vetor com o audio para o PORTD.
     PORTD = pgm_read_byte_near(pontos + i); 

     //incrementa a posição do vetor com o audio.
     if(i>30998){ 
          i = 0;
     }
     else{
          i += 1;
     }
}

//Configura o timer0 e as saídas.
void main (void){
     //Habilita interruoções globais.
     SREG |= (1<<7); 

     //Configura o timer0 para operação normal, não conectada.
     TCCR0A = 0; 
     TCCR0B = 0;

     //Define o prescale como sendo 64.
     TCCR0B |= ((1 << CS01)+(1 << CS00)); 

     //Inicia o timer0.
     TCNT0 = Min; 

     //Habilita interrupção por estouro do timer 0.
     TIMSK0 |= (1<<TOIE0); 

     //Configura o PORTD (pinos 0 ao 7), como saídas.
     DDRD = 0xFF; 

     //Loop vazio
     while (1){

     }
}

No código acima, vimos que o registrador TCNT0 incrementa seu valor em uma unidade uma vez a cada \frac{N}{f_{CPU}} segundo com N sendo o valor do prescale (64). Além disso, haverá um estouro no timer sempre que o valor de TCNT0 atingir 255. Dessa forma, temos que a frequência do timer (f) será:

(1)   \begin{equation*} f=\frac{f_{CPU}}{N*(255-Min)} \end{equation*}

Com Min sendo o menor valor de TCNT0. A fim de que o audio seja reproduzido adequadamente, precisamos que f seja igual a fs. Usando fs=2000, f_{CPU} = 16MHz e N igual a 64, temos:

(2)   \begin{equation*} 2000=\frac{16000000}{64*(255-Min)} \end{equation*}

Resolvendo para Min encontramos Min = 130. Desse modo, temos um estouro do timer a cada 0,5ms, fazendo com que a rotina de interrupção ISR seja acionada. Nessa interrupção, primeiramente atualizamos o valor presente na saída (PORTD) e reiniciamos o valor de TCNT0.

Em seguida, atualizamos a posição do vetor que será mandado ao PORTD na próxima iteração.
Já na função main temos a configuração do PORTD como saída, configuração do timer0 no modo desconectado com o prescale definido e configuração da interrupção por estouro do timer.
Por fim temos o loop vazio, que mantem o microcontrolador ativo aguardando a próxima interrupção. O código usado e um arquivo .h com um áudio exemplo estão disponíveis em nosso GitHub.

Montagem

A montagem é bem simples, como mostra a imagem abaixo:


Ela basicamente consiste em uma rede R2R, com a saída ligada em um capacitor e uma caixa de som para computador. Já os pinos de 0 a 7 são ligados aos pinos de mesmos valores do Arduino.
O funcionamento desse circuito é simples, já que ele consiste basicamente em um conversor digital analógico. Esse conversor transforma a saída do Arduino em um sinal de tensão analógico que é usado na caixa de som. O capacitor presente é um capacitor de desacoplamento, já que a saída do R2R apresenta um nível de tensão CC e a componente de CA.
É importante observar que esse circuito não apresenta um amplificador, já que a caixa de som usada já possui um amplificador interno. Viu só como foi fácil? Agora me conta nos comentários, que som que você vai usar para esse projeto? 

0