Acelerômetro com PIC (medidor de força G)
Autor: Stephen da Silva Madeira | Publicado: 2017-02-25T19:47:00-03:00

Depois de algumas postagens falando sobre os módulos do PIC e alguns truques, chegou a hora de juntar quase tudo. Este será o post mais complexo e rico da história do blog. Varredura, WatchDog Timer, Timers, Interrupções, Estados de gerenciamento de energia e ADC. Tudo em um único projeto. Trago a vocês um circuito (e programa) que mede os valores de aceleração em 3 eixos (x, y e z) passados por um acelerômetro (MMA7361) e exibe em dois displays de LED de 7 segmentos a resultante vetorial da aceleração. Pegue uma boa xícara de café e se prepare para revisar algumas coisas e aprender outras. Vamos lá!


Vetores, resultante vetorial (comprimento de um vetor) e aceleração.

&nbsp;

Podemos de forma simples definir vetor como uma reta que interliga dois pontos, dotada de comprimento, sentido e direção. Aqui vamos tratar todos os vetores saindo da origem até um ponto, tendo como comprimento final a aceleração em um dos eixos cartesianos (x, y ou z) e a resultante dos três.&nbsp;


Os três eixos cartesianos




Vamos agora pensar em 4 vetores: a aceleração no eixo X, no eixo Y, no eixo Z (todos em verde) e a resultante destas três (em vermelho).




Três vetores e a resultante deles.




O comprimento deste vetor resultante é dado por , sendo l seu comprimento total. 
"Tá, mas por que precisamos disso?" Bem, nenhum corpo está sujeito a apenas uma força em um único sentido, mas sim à combinação de todas as forças nele, ou seja, à resultante. Imagine um carro acelerando e virando ao mesmo tempo (acelerando dentro de uma curva): você tem uma força te puxando para dentro da curva (eixo X), uma força para frente acelerando o carro (eixo Y) e uma para baixo, para que o carro não voe (eixo Z). Vetorialmente, temos algo assim:




Mas no final das contas, todo o carro (e você dentro dele) está submetido à resultante de todas estas forças atuando juntas. E é isto que nosso projeto medirá!



O circuito

&nbsp;

O circuito fica conforme abaixo:


Resistores sem valor devem ser calculados.






Temos como "cérebro" nosso PIC18F2550, com um cristal de 20MHz alimentando o PLL interno, gerando um clock final de 48MHZ. Temos dois displays de ânodo comum, sendo controlados por varredura, que consiste em alternar os displays de forma rápida, sem que nossos olhos percebam. Isto nos economizou 6 pinos do microcontrolador. Os displays são selecionados pelos transistores Q1 e Q2, e os segmentos controlados pelo PORTB. 
Temos dois botões: S1, que alternará os modos de visualização (valor atual ou maior valor) e S2, responsável por colocar nosso circuito no modo de gerenciamento de energia (SLEEP).

R12 e R13 formam um divisor de tensão para abaixar a tensão do pino do PIC - que são 5V em estado alto - para 3.3V, o aceitado pelo acelerômetro, sendo por meio deste divisor de tensão, controlado pelo pino RC6, que selecionamos o acelerômetro no modo sleep ou normal. Perceba que liguei dois pinos do módulo acelerômetro: o 3.3V e o GS, sendo este pino responsável por selecionar o máximo valor lido e a resolução, que no nosso caso serão 6g com resolução de 206mV/g (lembrando que este g aqui não é de gramas!). O circuito deve ser todo alimentado com 5V providos por uma fonte bem estável e com baixo ruído, para evitar erros de leitura por parte do ADC.
Para calcular os resistores do display, use a fórmula R = (Vcc - Vf)/If, onde R é o valor do resistor em ohms, Vcc a tensão de alimentação (no nosso caso, 5 V), Vf a tensão direta dos LEDs do display e If a corrente direta daqueles. 



Código

&nbsp;

Primeiro vou colocar os código e depois explico.



main.c





delays.h





ADC.h







No código principal, começamos com os #pragmas responsáveis por configurar os bits de configuração do PIC. Veja que estamos usando como oscilador o modo HSPLL_HS, que significa que estamos usando um cristal de alta velocidade para alimentar o PLL que por fim gerará o clock final do microcontrolador, que no caso será de 48MHz. Desabilitamos o bit do WatchDog Timer (WDT) para que possamos controlar ele pelo software e configuramos o prescaler dele para 64. O WDT tem como base um período de 4 ms, mas como colocamos o prescaler dele para 64, 64x4 = 256 ms, que é o tempo que levará para o WDT estourar e resetar o PIC. 
Em seguida declaramos os headers necessários e o #define para a velocidade do clock. 
Logo se notam 3 funções: mask, ISRHP e GO_SLEEP.

1) A função mask é responsável por converter o valor numérico em um valor no PORTB para que sejam corretamente exibidos os números no display. Os valores retornados pelo switch case estão no formato binário. 
2) A função ISRHP (Interrupt Service Routine of High Priority) é um pouco mais complexa. Primeiro ela verifica se a causa da interrupção foi o Timer 1: se foi, ele limpa o registrador do WDT, recarrega os registradores do Timer 1 para que a interrupção ocorra a 200 Hz (o que permite limpar o registrador do WDT antes do estouro), trata de desligar o display ligado anteriormente, atualizar o valor do PORTB pro do outro display e o liga. Por fim, incrementa a variável atualiza, que será responsável por fixar a taxa de atualização do display em até 5Hz e limpa o bit da interrupção. Se a causa da interrupção não foi o Timer 1, ela foi gerada pela INT0, então a função verifica se foi ela a causa. Se sim, ela troca o modo do display entre os já citados e limpa o bit da respectiva interrupção.

3) A função GO_SLEEP põe o PORTB em estado baixo, corta os transistores de acionamento do display, põe o acelerômetro em sleep mode e desativa o WDT através do bit SWTDEN, isto pois mesmo nos modos de gerenciamento de energia o clock do WDT continua ativo. Importante observar isto, caso contrário, o WDT irá resetar o PIC, uma vez que o Timer 1 irá parar e consequentemente a interrupção que chama a rotina que limpa o registrador daquele. A função continua reativando tudo que fora desativado após acordar do modo sleep. Explicarei mais sobre os modos de gerenciamento de energia no final do post.

Na main, começamos com a configuração dos módulos. Não explicarei a configuração dos módulos: deixarei a cargo de vocês buscarem no datasheet cada registrador para entenderem o que cada configuração fez. Caso surjam dúvidas estas serão bem vindas no campo para comentários lá em baixo. O que vou comentar são apenas três coisas:
1) Começo ela limpando o registrador do WDT e o desativando, pois como o Timer 1 não foi ativado ainda, não quero que ele estoure por acidente. Veja que o ideal é ativá-lo somente após configurar o Timer 1 e sua interrupção.&nbsp;

2) O bit IPEN (Interrupt Priority Enable) ativa o modo de prioridades das interrupções. Isto nos permite escolher qual interrupção terá prioridade caso duas ocorram ao mesmo tempo. Juntamente à ativação deste bit, uma coisa muda na programação: agora a função que tratará as interrupções deve ter outro formato! A função que for tratar das de alta prioridade deverá ser da forma tipo_de_retorno interrupt high_priority nome_da_função, e a que tratar as de baixa prioridade, tipo_de_retorno interrupt low_priority nome_da_função. E para escolher qual interrupção terá prioridade alta ou baixa não basta apenas tratar ela dentro da função, mas configurar seus respectivos bits, que serão aqueles terminados em IP, como por exemplo a interrupção do Timer 1, que tem seu bit de prioridade TMR1IP, sendo atribuído o valor 1 para alta prioridade e 0 para baixa. Veja que neste código, coloquei o Timer 1 como prioritário, afinal, se outra função segurar o processador, o WDT pode não ser resetado, vindo a estourar e resetar o PIC.&nbsp;

3) os bits USBEN e SSPEN foram limpos para liberar os respectivos pinos do PORTC ao uso, evitando conflito.



Prosseguindo, temos um for simples, responsável por fazer os displays exibirem os números de 0 a 9 (para verificar se todos os segmentos estão bons). 
Em seguida vem o while(1), que criará o loop responsável pela execução do programa em sí.

O if é responsável por verificar a variável atualiza, responsável por fazer que os valores do display sejam atualizados em até 5Hz. Isto evita que os valores fiquem trocando muito rápido, e ajuda a exibir menos ruídos, deixando uma leitura mais consistente. Dentro do if temos a leitura dos valores retornados por cada canal do ADC, armazenando o valor em cada variável do respectivo eixo, sendo este valor convertido para a unidade de gravidades (g) logo em seguida (variáveis Xg, Yg e Zg). A conversão é feita da seguinte forma: o ADC retorna um valor de 0 a 1023 para a tensão presente no canal. Para saber a tensão lida, precisamos multiplicar o valor lido pela tensão de referência e dividir pelo maior valor que pode ser retornado, ou seja, multiplicamos por 5000 mV e dividimos por 1023. Isto nos dá o valor lido em mV. Em seguida convertemos a tensão em g. Sabendo que o MMA7361 tem uma sáida de 1650 mV para 0 g, precisamos subtrair isto do valor lido. O datasheet ainda informa que, quando a escala selecionada for de 6g (nosso caso), temos na saída 206 mV para cada g, então precisamos dividir o valor lido por 206. Pronto, temos o valor lido de cada canal em g. Para saber o valor resultante dos três vetores, usamos a fórmula citada lá em cima e armazenamos este valor na variável Gt. O programa então verifica se o valor lido é maior do que o maior lido anteriormente e caso seja, atualiza o valor máximo. Ele segue verificando o modo selecionado de exibição e joga nas variáveis digit1 e 2 os valores numéricos das unidades e dos décimos do devido valor, ou seja, nosso circuito nos dá valores de 0,0 a 6,0, em intervalos de 0,1. A variável atualiza é zerada para que a contagem de tempo reinicie. Por último, mas não menos importante, o programa verifica se a chave ligada no pino RC2 está pressionada (0 para pressionada e 1 para solta), e caso esteja pressionada, chama a função GO_SLEEP.

Perceba a quantidade de coisas que foram abordadas neste programa. Pode parecer até cansativo, mas se estudado com calma será bastante enriquecedor para seu conhecimento tanto de hardware quanto de software.


Power-managed modes

&nbsp;

Os estados de gerenciamento de energia na família 18F são dois: sleep e idle. O modo pode ser escolhido pelo bit IDLEN do registrador OSCCON. Para entrar em qualquer um dos modos basta dar a instrução assembly SLEEP ou, no compilador XC8, chamar a função nativa SLEEP.

A diferença entre os dois é que no modo idle, o clock do processador é cortado, porém os periféricos continuam executando suas funções. Em sleep, o processador e todos os periféricos sem clock próprio param, ou seja, apenas o WDT e o Timer 1 (quando sua fonte de clock for o oscilador de baixa potência) continuam ativos. Ambos os modos visam reduzir o consumo de energia, aumentando a durabilidade das baterias que alimentam o circuito, por exemplo. Para se obter o mínimo consumo possível, todos os pinos devem ser colocados em estado baixo, por isso que na função GO_SLEEP desliguei todo o PORTB. "Ué, mas você deixou o PORTC com o valor 0x03!" Sim, pois os transistores que controlam os displays - que neste caso são de ânodo comum - são do tipo PNP. Caso eu ponha o PORTC todo em estado baixo, os transistores saturariam e os displays ficariam ligados exibindo "88", que é justamente onde haveria o maior consumo de energia, já que todos os segmentos estariam acesos. Novamente, para mais informações, leiam o datasheet (para este recurso, seção 3.0). Lá vocês terão todas as informações, como as formas de sair destes estados, como o clock reage a eles e etc.
Vale lembrar que quando o microcontrolador sair de qualquer um dos modos de gerenciamento de energia por meio de uma interrupção, o programa não voltará ao início, mas continuará de onde parou (agora a função GO_SLEEP deve fazer mais sentido, não?). Caso ele saia por meio de um reset, seja no pino MCLR, seja pela instrução RESET ou pelo estouro do WDT, o programa voltará ao início. 



Para fechar, fica uma foto do circuito montado na protoboard:




Tenho inveja de quem consegue deixar os circuitos organizados e apresentáveis nas protoboards ;-;


&nbsp;E é "só" isso por hoje. Estou aberto a dúvidas e sugestões. Use o campo dos comentários aqui em baixo para que possamos evoluir juntos. Um abraço e até a próxima!