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


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

Conhecimento, Sem categoria

Os encoders são botões giratórios, similares aos potenciômetros, e que podem ser girados indefinidamente. Eles estão presentes em rádios, mouses, impressoras 3D, equipamentos de bancada, entre vários outros dispositivos eletrônicos. Assim, entender o seu funcionamento e aprender a usa-lo é algo muito interessante e útil, por isso vamos te ensinar mais sobre eles!

Podemos classificar os encoders em dois tipos, os absolutos e os incrementais. Aqui não iremos nos aprofundar muito nos encoders absolutos, mas vale apena uma breve explicação sobre eles. De modo simplificado, os encoders absolutos são construídos com um disco com diversas trilhas, cada uma com um sensor e uma marcação específica de modo que, ao se combinar as informações captadas pelos sensores de todas as trilhas é possível se formar um código, que é único para a posição em que o encoder se encontra, ou seja, em um encoder absoluto podemos determinar exatamente sua posição através de um código.

O segundo tipo de encoder, o incremental é um pouco mais simples. Ele é formado por uma unica trilha com marcações igualmente espaçadas e dois sensores postos lado a lado, assim, quando o seu disco é girado, com o auxilio do seu eixo, temos a ordem com a qual os sensores são ativados e que dependem do sentido de rotação.

Conhecendo o encoder incremental

Um exemplo de encoder incremental é o EC11, que é o modelo que usaremos neste artigo. Ele possui cinco terminais, sendo dois deles usados por uma chave, que pode ser ativada pressionando o seu eixo. Enquanto isso os demais terminais são usados para controlar o encoder. A imagem abaixo representa o simbolo esquemático de um encorder desse tipo.

Aqui temos os terminais S1 e S2 para a chave, enquanto os terminais A e B permitem acesso a saída dos sensores. O terminal C é o comum aos dois sensores e geralmente é aterrado. O acionamento desse encoder ocorre de forma mecânica, sendo que, quando giramos o seu eixo, o contato entre um dos terminais das extremidades (A ou B) é fechado com o terminal comum (C). Como já foi dito, a ordem com a qual os sensores são acionados dependem do sentido de rotação do encoder, assim, se ele é rotacionado no sentido horário, sabemos que o contato entre A e C é fechado e em seguida entre B e C. Mas o inverso ocorre se girado no sentido anti-horário, quando fecha-se primeiro o contato entre B e C, e depois entre A e C.

Isso pode nos descrever duas ondas quadradas em quadratura, como abaixo.

Por quadratura nos referimos a diferença de fase entre os sinais, isto é, temos que um sinal esta adiantado em relação ao outro por 90° (aqui vale ressaltar que esse ângulo não se refere ao ângulo físico do encoder, mas sim a defasagem dos sinais).

Mapeando as transições do encoder

As ondas apresentadas mostram os sinais nos canais A e B de acordo com a posição do encoder. Se percorrermos as ondas da esquerda para a direita vemos que o sinal A está adiantado em relação ao sinal B. Isso ocorre quando giramos o encoder no sentido horário. Entretanto, se percorrermos as ondas da direita para a esquerda, veremos que o sinal B fica adiantado em relação ao A, o que acontece quando giramos o encoder no sentido anti-horário. Assim, para saber o sentido de rotação, precisamos apenas encontrar o sinal adiantado.

Agora que conhecemos seu funcionamento, pensamos em como identificar o sinal adiantado. Com as formas de onda em mãos, podemos mapear as transições realizadas, ou seja, encontrar todas as transições possíveis e determinar em quais condições ela ocorre. 

Vamos usar como exemplo o caso em que A e B estão em nível alto, ou seja, AB=11, os próximos estados possíveis são AB=01, quando girado no sentido horário e AB=10, quando girado no sentido anti-horário. Isso fica mais claro na imagem abaixo.

Além disso, se AB permaneceu em 11 ele ficou parado, e para AB=00 temos um erro de leitura. Assim, para cada posição temos 4 estados futuros, sendo 1 deles impossível (ou um erro de leitura). Isso está sintetizado na tabela abaixo.

Nessa tabela, temos um número binário de quatro bits para cada movimento do encoder. Além disso podemos escrever horário como sendo 1, anti-horário como -1 e parado ou impossível como 0, assim conseguimos escrever um vetor de 16 posições com as transições possíveis. Esse vetor é {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0} e ele é muito útil, uma vez que podemos usa-lo para incrementar ou decrementar uma variável, como veremos logo mais.

Montando um teste

Para entender como usar um encoder preparamos um exemplo prático, mas, antes de mais nada precisamos de alguns materiais básicos, são eles:

  1. Display LCD 16X2;
  2. Arduíno;
  3. Resistores de 330Ω e de 3,3kΩ (1 de cada);
  4. Encoder rotativo EC11;
  5. LED;

O que faremos é controlar o brilho de um LED, para isso, ajustaremos a razão cíclica de um PWM gerado com um Arduino. O valor da razão cíclica será determinado pelo encoder e exibido no display LCD, assim, precisamos montar o circuito como abaixo.

 

Aqui temos os pinos 0 e 1 (PD0 e PD1 para o ATmega328p) para a entrada dos sinais A e B do encoder, enquanto os pinos de 4 a 7 (PD4 ao PD7) são conectados nos pinos de mesmo valor do LCD, para o envio de dados. Já os pinos A0, A1 e A2 (PC0 ao PC2) são conectados nos pinos En, R/W e RS respectivamente, para configurar o display. Por fim temos o pino 11 (PB3), que é por onde saíra o PWM gerado. 

Lendo o encoder

Com o hardware montado, podemos programar o Arduino. O programa completo com as bibliotecas utilizadas estão disponíveis em nosso github. Para o programa criamos uma função denominada “le_encoder()”, que como o nome já diz, é responsável pela leitura do encoder. Essa função ficou como mostrado abaixo:


int le_encoder(void){

    static int8_t A_B = 3;                                                                                     // A_B |==> Armazena os estados anterior e atual do encoder.
    static int8_t incremento;                                                                                // incremeto|==> Variavel retornada pela função, indica o sentido de rotação do encoder.
    static int8_t entradas[]= {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};          // entradas[] |==> Vetor com as mudanças de estados possíveis.

// Cria um binario de 4 bits com os 2 MSDs indicando o estado passado e os 2 LSDs indicando o estado atual do encoder.
    A_B<<=2;
    A_B |= (PIND & 0x03);
    incremento += entradas[A_B & 0x0f];

// Evita incrementos e decrementos descontrolados.
    if(incremento>1){
        incremento=0;
    }
    if(incremento<-1){
        incremento=0;
    }
    return incremento;
}

Primeiro temos a declaração das variáveis locais, sendo todas estáticas para não serem reiniciadas a cada chamada da função. Em seguida, o valor antigo da variável A_B, responsável por armazenar o estado dos pinos PD0 e PD1, sofre um deslocamento de dois bits para a esquerda (A_B<<=2) e o valor dos pinos PD0 e PD1 são lidos com o auxilio de uma mascara e colocados nos 2 bits menos significativos (A_B |= (PIND & 0x03)). Isso forma um binário que representa a mudança de estado do encoder.

Logo após, soma-se na variável incremento 1, -1 ou 0, de acordo com o valor dos quatro bits menos significativos de A_B. Para isso lê-se a posição A_B & 0X0f do vetor entradas[], que é o vetor com as transições possíveis, que já discutimos aqui. Aplica-se a mascará 0x0f para se descartar os bits mais significativos de A_B, e ler apenas os 4 menos significativos, que são formados pelo valor de A_B anterior e atual.
Finalmente temos um “if”, que verifica se o valor do incremento é maior que 1 ou menor que -1, e se sim zera esse valor. Isso evita incrementos sucessivos de forma descontrolada.

Controlando o PWM

Agora que já conseguimos identificar o sentido de rotação do nosso encoder, vamos realizar o controle do PWM. Para isso, usaremos um inteiro de 8 bits e sem sinal, assim, temos uma variável que ira de 0 a 255. Fazemos isso porque o registrador OCR2A, responsável pelo PWM no pino PB3, é de 8 bits. O código fica como segue abaixo.

void calcula_PWM(int incremento){

static uint8_t D;                    // D = duty cicle em percentual
static uint8_t duty = 0;             // duty = duty cicle valor passado para o OCR2A para dutyrole do PWM
char s_duty[20];                     // s_duty = string com o duty cicle em percentual para exibir no LCD.

if (incremento){ if(duty>10){ if(duty<245){ duty += 3*incremento; } else if(duty<253){ duty += incremento; } else if(incremento>0){ duty = 255; } else{ duty += incremento; } } else if(duty>2){ duty += incremento; } else if(incremento<0){ duty = 0; } else{ duty += incremento; } D=100*duty/255; if(duty > 254){ sprintf(s_duty, "Duty: %d%% Max ", D); } else if(duty < 1){ sprintf(s_duty, "Duty: %d%% Min ", D); } else{ sprintf(s_duty, "Duty: %d%% ", D); } OCR2A = duty; LCD_move_cursor(0,0); LCD_write(s_duty); _delay_ms(20); } }

Nessa função primeiro se verifica se existe algum incremento (ou decremento) e se sim, verifica se a variável duty (que será passada para o OCR2A) está entre 10 e 245. Em caso afirmativo será somado 3 vezes o incremento em duty, mas caso essa variável esteja entre 2 e 10 ou 245 e 253, esse incremento será de apenas uma unidade. No caso dessa variável ser menor que dois ou maior que 253 então a saída será 0 ou 255 respectivamente.

O incremento de 3 quando duty está entre 10 e 245 permite um controle veloz da razão cíclica, mas mantem o controle do brilho do led suave, já que uma mudança dessa grandeza no PWM não é perceptível. Além disso, quando o valor está próximo de 255 o controle do PWM não permite um novo incremento, já que isso acarretaria em uma transição abrupta de nível máximo para mínimo. O mesmo ocorre nas proximidades de 0.

Em seguida calculamos o valor percentual da razão cíclica na variável D, exibindo esse valor no display LCD na forma “Duty: XXX”. Além disso, imprimimos na tela Máx quando a razão cíclica é 100% e Min quando essa é 0%. Por fim enviamos o valor de duty para o registrador OCR2A.

Main

Finalmente temos a função main, que realiza a configuração das entradas e saídas, além de configurar o PWM. Ela ficou como abaixo:


int main(void){
// Configura os pinos de entrada. 
    DDRD &= ~((1<<PD0)+(1<<PD1));
    PORTD |= ((1<<PD0)+(1<<PD1));

// Configura PWM.
    DDRB |= (1<<PB3);
    TCCR2A |= ((1<<COM2A1)+(1<<WGM21)+(1<<WGM20));
    TCCR2B |= (1<<CS20);
    OCR2A = 0;

// Inicia o LCD.
    LCD_init();
    LCD_move_cursor(0,0);

// Loop de execução.
    while(1){
        calcula_PWM(le_encoder());
    }
return 0;
}

O que fazemos aqui é configurar os pinos PD0 e PD1 como entradas (no registrador DDRD) e habilitar o resistor de pull up interno (no PORTD). Em seguida, habilitamos PB3 como saída (com DDRB), e configuramos o timer 2 no modo fast PWM, sem prescale. Aqui também iniciamos o LCD, configurando as entradas e saídas pertinentes com a função LCD_init(), e posicionamos o cursor na primeira casa e na primeira coluna. Finalmente, temos um loop infinito que realiza a constante chamada da função calcula_PWM().

Nós montamos este circuito para testarmos na prática e ele ficou funcionando como no vídeo deste link. O que você achou desse artigo? Pretende usar o encoder em algum projeto? Nos conte aqui nos comentários.

0