sábado, 25 de fevereiro de 2017

Acelerômetro com PIC (medidor de força G)

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.

 

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. 
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

 

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

 

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. 
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. 
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

 

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 ;-;
 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!

quarta-feira, 15 de fevereiro de 2017

Recursos especiais do PIC (parte 2)

Como prometido, continuo aqui com o último post sobre alguns módulos dos PICs. Lembrando novamente que estamos falando sobre o PIC18F2550/4550 mas isto se aplica a todos que tiverem. Confiram sempre o datasheet do seu microcontrolador para conferir o nome e função dos registradores.

Fail-Safe Clock Monitor

No último post da série, falamos sobre o WDT e como ele pode nos ajudar quando o PIC "trava", mas e a causa deste travamento? Um estouro de pilha? Uma operação ilegal? Uma flutuação de tensão? Ruído na alimentação? Bem, em todos estes casos com certeza o WDT irá atuar e se for algo transiente, o PIC retoma à operação normal. Mas e se ele travou porque houve uma falha no seu Oscilador externo? Se deu defeito no capacitor ou no cristal do circuito de clock? Aí entra o FSCM pra salvar o dia.
 Segundo o datasheet, "O Fail-Safe Clock Monitor (FSCM) permite ao microcontrolador continuar a operar na eventual falha do oscilador externo ao mudar automaticamente o clock do dispositivo para o bloco oscilador interno". Ao detectar uma falha no oscilador externo, o PIC gera uma interrupção (OSCFIF), muda o clock para o interno e limpa o WDT. Com a interrupção você pode optar por dois caminhos: entrar em um dos estados de gerenciamento de energia do PIC ou escolher a próxima fonte de clock.
No programa, tudo que envolver tempo terá que ser ajustado para o novo clock interno, ou você pode usar um cristal sempre da mesma frequência do clock interno escolhida (eu trabalharia com 8MHz).
Para ativar este recurso, primeiro configure o registrador OSCCON selecionando o clock desejado em caso de falha e as interrupções (INTCON). Ative a interrupção pelo bit OSCFIE e a monitore pelo bit OSCFIF. Lembre-se de ativar o bit de configuração FCMEN (CONFIG1H).

Two-Speed Start-up

 Este é o mais simples de todo: ative o bit de configuração IESO (CONFIG1H). Em modos de oscilador que usem um cristal (XT, HS, XTPLL e HSPLL), este recurso permite o microcontrolador rodar no clock interno até que o clock do cristal esteja estável. O sugerido é apenas que a primeira coisa que seu programa deve fazer é configurar o clock interno (escolher a velocidade) através do OSCCON. Assim que o clock do cristal estiver pronto, automaticamente o PIC muda sua fonte de clock para ele. É uma boa alternativa para o Power-Up Timer.

High/Low Voltage Detector

 Este é o tipo de recurso que você quer ter ativado caso esteja alimentando seu circuito com bateria ou fontes que estejam sujeitas a flutuações de tensão. Este módulo é basicamente um comparador de tensão com sua referência ajustável. Quando a tensão no pino RA5 (HLVDIN) ficar acima ou abaixo do nível configurado, é gerada uma interrupção (HLVDIF). Este módulo é configurado pelo registrador HLVDCON. Importante ressaltar que cada vez que você ativa este módulo, ele leva um tempo para estabilizar, e para isto, há um bit que informa quando ele está pronto (IRVST). Segundo o datasheet, para usar ele você deve prosseguir da seguinte forma:
1. Desabilite o módulo limpando o bit HLVDEN (HLVDCON<4>).
2. Escreva o valor aos bits HLVDL3:HLVDL0 que selecionam a tensão de disparo do HLVD desejada.
3. Arme o bit VDIRMAG para detectar uma tensão acima (VDIRMAG=1) ou abaixo (VDIRMAG=0) da tensão de disparo.
4. Habilite o módulo HLVD armando o bit HLVDEN.
5. Limpe a bandeira de interrupção do HLVD, HLVDIF
(PIR2<2>), que pode ter sido armada por uma interrupção anterior.
6. Habilite a interrupção do HLVD, se desejado, armando os bits HLVDIE e GIE/GIEH (PIE2<2> and INTCON<7>). Uma interrução não será gerada até o bit IRVST estar armado.
E pronto! O HLVD estará aí pra te ajudar a saber se uma tensão está abaixo ou acima de um valor desejado. Isto é muito útil pra monitorar a tensão da bateria que alimenta seu circuito, por exemplo.


Estes eram os últimos módulos que faltavam eu comentar. Sei que ficou parecendo estar tudo por cima mas a ideia era apresentar os módulos pra que vocês saibam da existência deles. Recomendo e muito lerem o datasheet para entenderem como eles funcionam e para como usá-los da melhor forma nos seus projetos. Tire sempre o máximo proveito do microcontrolador que você comprou!

As aparências enganam (algorítimos de ordenação)

Ordenar de forma crescente ou decrescente um sequência de dados na memória de um microcontrolador ou em uma memória externa pode ser necessário uma hora, ainda mais se for usar a busca binária, ou pelo simples fato de você querer dados ordenados. Mas como fazer isto?
Procurando no Google por "sorting algorithms", achei uma página bem interessante que permite visualizar de forma gráfica o que acontece em cada algorítimo. Fui pesquisar como cada um funciona e, buscando o mais simples de programar, acabei no Bubble sorting. Após programar ele com sucesso no PC, resolvi escrever ele e rodar no PIC18F2550. Pra minha surpresa, quando fui organizar uma memória de 512 bytes (4Kbits), esta tarefa foi executada de forma absurdamente lenta (cerca de 10 minutos pra finalizar). Resolvi escrever outro, mas não fui procurar na internet (até procurei o Quick sort mas fiquei com preguiça por dar muito trabalho haha): resolvi pensar. Desenvolvi um algorítimo, mas depois descobri que era exatamente o Selection Sort. Desta vez, o mesmo trabalho que o Bubble levou 10 minutos, em cerca de 3 o Selection fez. "Ora, mas na página lá em cima ele se mostrava bem mais lento. O que houve?" Bem, vou deixar vcs curiosos pela resposta por enquanto. Agora vou mostrar como cada algorítimo realmente trabalha e mostrar os códigos pro PIC que fiz.


Bubble Sorting

Bem simples de programar, o Bubble Sorting consiste em trocar duplas de dados se n+1 > n (sendo n o índice do vetor). Vamos usar de exemplo um vetor de cinco posições com os números 1, 7, 3, 5 e 2. Observe a imagem abaixo que mostra uma iteração apenas (leia como um gibi):


Na próxima iteração, o algorítimo veria que 1 < 3, e portanto não os trocaria de lugar; 3 < 5 mesma coisa, até que por fim chega em 5 > 2, ou seja, trocaria ambos de lugar. Se os números forem iguais, ele não faz nada. O programa precisa repetir isto até todos estarem ordenados.
Abaixo segue um pseudocódigo como exemplo:




Selection Sorting

Tão simples de programar quanto o Bubble, este método surpreendeu ao se mostrar significantemente mais ágil com memórias externas. Este consiste em buscar e "pegar" os menores valores encontrados no vetor e os colocar na ordem crescente, fazendo isso cada vez avançando a partir do último número ordenado. A imagem abaixo exemplifica como isto deve ser feito:


Veja que quando os números forem iguais nada é feito. Creio que este método seja o mais intuitivo (mais próximo do que faríamos como pessoas). Tudo é feito em uma única "passada" pelo vetor.
Aqui segue o pseudocódigo:




Mas e a velocidade?

OK, matando sua curiosidade vamos lá: e a velocidade? A página e vídeos que demonstravam o funcionamento dos algorítimos estavam todos errados? Bem, sim e não.
O problema destes exemplos em animação ou vídeo é considerar que o tempo de leitura e escrita da memória e de comparação sejam todos iguais, mas sabemos que não é verdade. O PIC leva muito mais tempo lendo ou escrevendo uma memória do que simplesmente comparando dois valores da sua RAM. Em uma memória 24C64, por exemplo, a escrita leva cerca de 5 ms a mais do que a leitura, portanto, o algorítimo que menos escrever ganhará vantagem, e em um vetor enorme isto se torna muito relevante, como no exemplo que dei em que o Selection levou cerca de um terço do tempo do Bubble. Ainda mais, o algorítimo que menos escrever aumentará a vida útil da memória. Por isso devemos tomar cuidado com comparações fora do contexto, não só nestes algorítimos mas em qualquer outro. Precisamos pensar muito maior do que as coisas que nos são mostradas. Trago este caso pra que vocês reflitam como é importante considerar os diversos fatores em um programa e escolher métodos conforme a realidade com a qual está trabalhando.

sábado, 29 de outubro de 2016

Driver para LEDs - Alta eficiência

Se você acompanha o blog, vai olhar o título e dizer "mas ele já não postou um driver aqui?". Bem, já. Mas este é novo e melhor. Por quê? Um motivo bem especial: eficiência bem maior.
Se você lembrar do primeiro driver que postei aqui, vai lembrar que foi um usando dois transistores, o que dissipa bastante energia no transistor ligado ao LED e nos resistores, ou no caso do com o LM317 como tem pela internet, dissipa tanto no CI quanto no resistor. Então qual a saída? Circuitos do tipo Buck! Esta é a solução.

Sobre o circuito

Não vou me ater a explicar o princípio de funcionamento deste circuito para não alongar o post, mas se você não o conhece, sugiro pesquisar (sou apaixonado por esta topologia de circuito). Para fazer tudo funcionar, usaremos o LM2576, porém, qualquer um da família LM25xx que seja um Buck com pelo menos 500mA de capacidade serve (só observar as mudanças no esquema e valores de componentes, mas a ideia pode ser expandida a todos). Mas aí você abre o datasheet e me volta dizendo "cara, isso não é um CI dedicado a LEDs! Nem tem a palavra LED dentro do datasheet!". É, ele não é um CI para controlar LEDs nem é um CI que forneça corrente constante. Mas eu não estou louco nem de brincadeira. Não ainda.
LEDs costumam se comportar de forma ôhmica, logo, isso nos permite a, aplicando uma tensão constante, obter uma corrente constante. Se instalado em um bom dissipador, o aquecimento dele não mudará muito sua curva V*I. Se você buscar o datasheet destes LEDs, verá que quase sempre a máxima corrente direta que eles aceitam é de 350mA. Se trabalharmos com 320mA, isso nos dá uma tensão direta de 3,125V para 1W (lembrando que na maioria dos casos eles suportam até cerca de 1350mW). Então, se alimentarmos o LED com uma tensão constante e permitindo que ele aqueça pouco, teremos uma operação segura. Conseguiremos esta tensão constante então usando o LM2576 conforme o circuito abaixo:


Este é nosso buck. R1 e R2 definem a tensão que o CI irá manter. A fórmula que define a tensão é Vout = 1,23*(1+R1/R2), que neste caso, teremos 3,034V, o que nos dará 330mA no LED (temos 20mA de folga ainda!). Se achar apertado, pode substituir o resistor de 1k5 por 1k3, o que nos dará 3,315V e 302mA. Eu fiz com estes valores pois eram os que eu tinha em casa (este circuito, na verdade, era um teste pra um regulador para 5V, mas acabou tendo muito ruído e virou fonte do LED). C1 alí está errado:  1000uF são suficientes! C1 e C2 devem ser de 25V ou 35V e preferencialmente do tipo low-ESR. C3 e C4 podem ser cerâmicos ou de tântalo.
Veja que além de usarmos um circuito do tipo buck, que é bem eficiente, ainda fugimos do resistor Shunt, que costuma ser um grande desperdiçador de energia.
A PCI dele ficou mais ou menos assim:



Tentei seguir ao máximo as recomendações do fabricante. Quem quiser, recomendo ler o datasheet na seção 9.2.2.2 para fazer a placa conforme o fabricante recomenda. Mas esta funciona perfeitamente.
Creio que o detalhe mais difícil da montagem seja o indutor. Ele pode ser comprado ou enrolado a mão mesmo. Aqui usei um salvado de uma placa de impressora. Tem vários aplicativos para aparelhos móveis e na internet referências de como dimensionar um indutor. Novamente, o fabricante recomenda usar os com núcleo de ferrite ou preferivelmente toroidais, por gerarem menos EMI.
E se estiver duvidando ainda do funcionamento, aí seguem as fotos:



O LED não aqueceu fora do normal, manteve um brilho satisfatório e em 12V o circuito todo consome cerca de 0,17W (maravilhas do Buck!!!). O dissipador no CI é dispensável (esqueci de tirar para a foto. Estava usando quando ele cumpria outra função).
E é isso ai. Fim de post.
E o post que estou devendo sobre o FSCM do PIC18F2550? Só quando eu tiver tempo haha

quinta-feira, 28 de julho de 2016

Mais mudanças e recursos especiais do PIC (parte 1)

Pra galera que acompanha o blog, até então eu vinha usando os compiladores da Mikroe (tanto o mikroC quanto o mikroBasic no início), mas eles são pagos e limitam na versão demo os recursos e tamanho do programa que você pode escrever. Chegando ao limite dele, precisei achar uma saída, e resolvi então migrar para o compilador e IDE da própria Microchip: o MPLAB e como compilador o XC8. Então, a partir de agora, meus códigos serão todos para XC8.
As diferenças na linguagem não são tantas; a maior dificuldade é lidar com a falta de bibliotecas como havia no mikroC. Porém, como sou legal, me encarreguei de fazer o máximo que pude das bibliotecas mais usuais do mikroC e deixei neste link: só baixar e usar.
E nesse post já farei uso dessa nova plataforma. Hoje vou falar sobre um recurso interessante do PIC e que muitas vezes passa despercebido e raramente é usado, seja por não saber o que é ou por não achar referências muito claras, seja em português ou em inglês: o WatchDog Timer.

WatchDog Timer ou WDT

Vamos supor que seu PIC de alguma forma executou uma instrução que gerou uma operação inválida ou travou em um loop aguardando a resposta de um dispositivo que por algum motivo não respondeu, ou ainda, que sua fonte de clock primário parou. Para não deixar seu dispositivo parado/travado, existe esse módulo interno do MCU: o WDT. Vou deixar os maiores detalhes pra que vocês leiam na internet ou no datasheet, mas basicamente, o WDT opera com um clock próprio. É um timer que incrementa sozinho, mas a diferença é que automaticamente ao haver um overflow o PIC irá resetar. Cabe, então, ao seu programa constantemente limpar o WDT, e para isso temos a instrução clrwdt em assembly. Ela irá limpar o timer do WatchDog. No XC8 temos a função ClrWdt(); que fará o mesmo trabalho.
No exemplo de programa, usarei o PIC18F2550. Temos algumas pequenas diferenças, que explicarei. 

Prescaler e base de tempo

Primeiro de tudo, como falei, esse módulo tem seu próprio clock, e com isso, sua base de tempo. É importante se atentar a isso, que fica bem explícito no datasheet. No caso do PIC18F2550, com prescaler 1:1 temos 4 ms como base de tempo, ou seja, se a cada 4 ms o WDT não for limpo, o PIC irá executar a instrução de RESET. Fazendo uso do prescaler, podemos chegar a pouco mais de 2 minutos. É bom pensar no tempo escolhido, pois caso o PIC pare, ele ficará assim por esse determinado tempo.
E aqui entra uma pequena diferença: na família 12F e 16F, o prescaler é selecionado no registrador OPTION_REG, enquanto que na 18F há um um endereço especial dos bits de configuração para ele.

  

Controle por Software

No PIC18F2550 ainda temo outro recurso: o controle por Hardware do funcionamento do WDT. Quando este modo é ativado nos bits de configuração, temos um bit chamado SWDTEN (Software Controlled Watchdog Timer Enable bit) que nos permite, dentro do software, ligar ou desligar o módulo.

Limpando o Timer do WatchDog

Mais uma vez, aqui é bom uma conferida no datasheet: o WDT pode ser resetado por mais de um caminho. O comum a todos, como já citado, é a instrução própria para isso. Mas ainda há outros dependendo do PIC: mudança na fonte de clock primário, a instrução SLEEP, e etc.
Mas atentando ao principal: como vou ficar de tempo em tempo passando a instrução clrwdt? Tenho que ficar escrevendo isso linha a linha no código? Não! Aqui usamos um dos Timers disponíveis para gerar interrupções de tempo conhecido e assim passar a instrução. Sabendo a frequência do Clock e o tamanho do Timer podemos definir o prescaler e/ou postscaler para chegar a um tempo de interrupção menor que do WDT, e assim, limpá-lo antes do WDT estourar. Aqui você pode usar a calculadora que fiz :D (não deixe de conferir essa postagem).


Juntando tudo

Aqui segue um exemplo em C para o XC8, PIC18F2550:


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
/*
 * File:   WDT.c
 * Author: Stephen
 *
 * Created on 7 de Julho de 2016, 14:33
 */

// PIC18F2550 Configuration Bit Settings
// 'C' source line config statements
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1L
#pragma config PLLDIV = 5       // PLL Prescaler Selection bits (Divide by 5 (20 MHz oscillator input))
#pragma config CPUDIV = OSC1_PLL2// System Clock Postscaler Selection bits ([Primary Oscillator Src: /1][96 MHz PLL Src: /2])
#pragma config USBDIV = 2       // USB Clock Selection bit (used in Full-Speed USB mode only; UCFG:FSEN = 1) (USB clock source comes from the 96 MHz PLL divided by 2)

// CONFIG1H
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator (HS))
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRT = ON        // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOR = ON         // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 0         // Brown-out Reset Voltage bits (Maximum setting 4.59V)
#pragma config VREGEN = OFF     // USB Voltage Regulator Enable bit (USB voltage regulator disabled)

// CONFIG2H
#pragma config WDT = ON         // Watchdog Timer Enable bit (WDT enabled)
#pragma config WDTPS = 256      // Watchdog Timer Postscale Select bits (1:256)

// CONFIG3H
#pragma config CCP2MX = OFF     // CCP2 MUX bit (CCP2 input/output is multiplexed with RB3)
#pragma config PBADEN = OFF     // PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset)
#pragma config LPT1OSC = OFF    // Low-Power Timer 1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = ON       // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection bit (Block 0 (000800-001FFFh) is not code-protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 (002000-003FFFh) is not code-protected)
#pragma config CP2 = OFF        // Code Protection bit (Block 2 (004000-005FFFh) is not code-protected)
#pragma config CP3 = OFF        // Code Protection bit (Block 3 (006000-007FFFh) is not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) is not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM is not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 (000800-001FFFh) is not write-protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 (002000-003FFFh) is not write-protected)
#pragma config WRT2 = OFF       // Write Protection bit (Block 2 (004000-005FFFh) is not write-protected)
#pragma config WRT3 = OFF       // Write Protection bit (Block 3 (006000-007FFFh) is not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) are not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot block (000000-0007FFh) is not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM is not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 (000800-001FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 (002000-003FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection bit (Block 2 (004000-005FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection bit (Block 3 (006000-007FFFh) is not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) is not protected from table reads executed in other blocks)


#define _XTAL_FREQ 20000000
#include  xc.h 
#include "delays.h"


void interrupt high_priority ISR()
{
    if(TMR0IF)
    {
        ClrWdt();
        TMR0H = 179;
        TMR0L = 180;
        TMR0IF = 0;
        LATC6 ^= 1;
    }
}

void main(void)
{
    ClrWdt();
    SWDTEN = 1;
    INTCON = 0xE0;
    INTCON2 = 0x74;
    IPEN = 1;
    T0CON = 0x87;
    TMR0H = 179;
    TMR0L = 180;
    TRISC = 0;
    LATC = 0;

    delay_ms(2000);
    LATC7 = 1;
    TMR0IE = 0;
    while(1);
}
Como podem ver, todos os #pragma são responsáveis por direcionar o compilador a gerar o código pros bits de configuração (essa parte não fica junto com a memória de programa). Explicando o importante, configurei meu PIC para rodar com Clock externo (HS) a 20 MHz, informado ao compilador em Hertz pelo #define _XTAL_FREQ 20000000. Meu prescaler do WDT ficou em 256, pois 256 x 4 ms = 1024 ms, ou aproximadamente um segundo para o WDT resetar o PIC. Habilitei o bit do WDT com opção de controle por software.
o #include "delays.h" inclui no programa o header delays, que será usado exatamente como a função delay_ms(); do mikroC;
Na interrupção, configurada para alta prioridade pelo modificador high_priority, vemos que a coisa é bem simples: executo a função para limpar o WDT, recarrego o Timer0 com seus valores calculados e limpamos a interrupção. De brinde tem um LED no PORTC6 que pisca para indicar que o WDT está sendo limpo. Simples assim! Ela está em modo de alta prioridade pois, se houver outra interrupção dentro do programa, é preferível que esta tenha prioridade, para evitar que o PIC reset por acidente.
Sobre a main, já começo o programa limpando o timer e habilitando via software o WDT. Em seguida, vem a configuração da interrupção para o Timer0 em modo de alta prioridade e a configuração do Timer0 para gerar a interrupção antes de 1024 ms. Dou um delay de 2 segundos e ligo outro LED, dessa vez no PORTC7, para indicar que desligarei a interrupção do Timer0, o que faço em seguida e ponho o PIC em loop eterno. Como desliguei a interrupção, a função de interrupção não será chamada, não limpando o WDT e assim permitindo seu estouro e consequente reset do PIC.
O que você verá mesmo é o LED do PORTC6 ligar (avisando que a interrupção foi chamada), o outro LED liga e um segundo depois os dois apagam, indicando que o PIC resetou.

E é isso! Esta é a base de uso do WDT. Agora é só implementar em seus programas sempre que possível para garantir que jamais seu projeto pare de funcionar. Próximo post falarei sobre o Fail-Safe Clock Monitor (FSCM). Até mais!

sábado, 9 de abril de 2016

Circuito anti-noob (é isso mesmo)

(ATUALIZADO 10/04/2016 22:09) 
Apesar da brincadeira no nome, o circuito é sério, afinal, difícil achar quem na eletrônica nunca queimou um componente ou circuito por ter invertido a polaridade ou fritou algo porquê não tinha um fusível ou custou a descobrir que nada funcionava pois o fusível estava queimado, não?
Este circuito, que pode ser usado na protoboard ou implementado em outro circuito vem pra resolver tudo isso: protege contra inversão de polaridade indicando se está certo ou não e indica se o fusível está queimado! Três circuitos em um!


o conjunto R2 D2 formam uma fonte de tensão baseada em um zener de 5V1 para o LED indicador , D3 protege contra alimentação reversa e R3 limita a corrente para o LED. Ao lado temos o mesmo circuito, porém invertido, para acender o LED caso a polaridade esteja errada. A ideia de se usar um zener é ter sempre o mesmo brilho no LED e segurança para qualquer tensão.
O indicador de fusível rompido é quase o mesmo circuito, porém com Q2 D8 e R8. Com o fusível
bom, a corrente flui por D8, cortando Q2. Se o fusível abrir, R8 liga a base ao negativo, acendendo o LED3.
D9, D10 e D11 impedem que a corrente circule pelos resistores associados de forma desnecessária.
Sobre o protetor contra reversão de polaridade, deixo a explicação para o vídeo de onde tirei o circuito. Aqui, inclusive, ele explica porque não usar a óbvia solução do diodo ou da ponte de diodos.
O circuito foi projetado para 12V. Para tensões maiores que 24V será necessário montar mais uma fonte zener, mas para a base de Q2. Para outras tensões, recalcular os resistores.


Essa é a PCB sugerida, pra vc usar em uma protoboard, por exemplo.
Para o MOSFET IRF9530, até 2,5A pode-se omitir um dissipador. Acima disso, recomendo por um dissipador de calor.
Caso ache um MOSFET de RDon menor, como o FQP27P06, será melhor usar ele para correntes maiores.

Então é isso! Monte o circuito e esteja protegido de noobs (e da sua falta de atenção)!
Boa sorte e boas montagens!

quinta-feira, 4 de fevereiro de 2016

Trilha curva pode?

Fala pessoal, tudo na paz?
Resolvi trazer essa postagem depois de ter visto muita gente questionando sobre a forma "correta" de se traçar as trilhas de uma PCB. Tem quem diga que não pode usar ângulos retos (trilha com "quinas"), tem gente que diz que não pode usar curvas. Enfim, não parece haver um consenso. Então resolvi, depois de pesquisar, trazer um compilado simples, com imagens, pra provar quem está certo ou errado nesta história.


O problema

Suponhamos a situação da seguinte imagem:


Considere que cada círculo seja uma ilha: terminais de resistores, diodo, MOSFET ou seja lá o que for, e você precisa conectar os dois. O caminho mais óbvio é ligar os dois em linha reta. Mas e se tem uma terceira ilha no meio do caminho e que não deve ser ligada com essas outras duas? Aí começa a dúvida.
Existem três caminhos lógicos e mais simples:


As três obviamente resolvem o problema. Interligamos os dois pontos  sem fazer uma reta já que hipoteticamente não podíamos. Mas vamos olhar isso de forma mais detalhada:


Se você somar os valores de cada trecho, verá que o primeiro desenho da esquerda para a direita possui maior comprimento seguido do com a curva e por fim o com um "chanfro". Aqui reside uma parte do problema: o comprimento da trilha. Em circuitos simples, não há o porque se preocupar com o comprimento das trilhas, mas em circuitos sensíveis, como ligações do tipo Kevin em sensores de corrente, circuitos que operam a altas frequências ou onde a resistência da própria trilha possa interferir, é preferível ter o menor comprimento o possível para evitar erros de medida ou interferências. Então a solução mais adequada é a do meio, que possui o menor comprimento.
O primeiro desenho da esquerda para a direita é bom quando você vai desenhar placas de coisas simples, como um pisca-pisca, uma lanterna com LEDs ou coisas que não tenha a preocupação de resistências mínimas ou interferências e que você fará na mão, usando a caneta, pois fica prático. 
E qual a situação ideal para as curvas? Aqui vejo apenas um caso que tenha uma justificativa na física para seu uso: circuitos que trabalhem com altas tensões (geralmente > 60 ~ 80V). Aqui entra um princípio da física chamado "Poder das Pontas", que resumidamente, diz que os corpos acumulam cargas de forma mais concentrada nas suas pontas, ou seja, em circuitos de alta tensão onde muitos elétrons estão circulando, quinas e pontas abrem margem para fugas ou até mesmo geração de arcos voltaicos (lembrando que tem toda a parte de isolamento, distância e etc.). Há quem diga que em circuitos de alta corrente também seja melhor usar curvas para "facilitar" o caminho, mas pela física (lei de ohm mandando abraço), quanto maior a corrente num resistor, maior a queda de tensão - o resistor aqui é sua trilha que, por mais que a idealizemos como um condutor perfeito (R = 0), ela tem uma resistência elétrica sim, e sob altas correntes, altas quedas de tensão.
Então, se não for passar alta tensão em uma trilha, não se justifica FISICAMENTE (depois explico o porquê desta ênfase) fazê-la curva.
Ainda na dúvida sobre essas dimensões acima? Vamos então observar cada uma de perto.
Para isso, colocarei todas as trilhas em ciclos trigonométricos pra facilitar a compreensão.


No nosso primeiro caso, notavelmente, o comprimento total da trilha em branco será 2L (L + L). Creio que aqui não há dúvidas. 


Quando adicionamos um ângulo (um chanfro) na trilha, a trigonometria entra em ação. "A soma dos catetos é sempre maior que a hipotenusa". Lembra do seu professor falando isso? Então, aqui está o exemplo. A trilha em sí que é a diagonal do triângulo branco será L / cos 45°, o que dá aproximadamente 1,4. Opa, 1,4 é menor que 2, então antes ter uma trilha que seja 1,4L do que 2L (30% menor). 


Por fim, o último caso é o da curva. Aqui já entra a parte de circunferência. Considere o arco sendo parte de uma circunferência. O comprimento total desta é 2 x pi x L, mas como temos apenas um quarto da circunferência (apenas 90°), o comprimento do arco será de (2 x pi x L) / 4, ou aproximadamente 1,57L. Ainda assim menor que o primeiro exemplo, mas maior que o segundo.

Mas e na prática?

Tirando os casos onde o uso de um desses tipos seja imperativo, de fato não há regra para o uso!
O que normalmente é levado em conta é o espaço disponível para cada trilha, a estética da placa e a facilidade de execução e confecção, seja na mão, tinta fotossensível, transferência de toner ou industrial mesmo.

Preferência para as curvas pela facilidade de desenho e estética. (Filtro PWM para Peltier)

Trilhas chanfradas para reduzir seus comprimentos, devido o trabalho com sinais (placa de desenvolvimento)

Veja que mesmo um circuito que trabalha com "altas" correntes (apróx. 6A) eu usei trilhas curvas, pois pra mim as quedas de tensão não eram importantes. Eu queria o protótipo funcionando para efetuar os ajustes, mas na placa onde trabalho com um microcontrolador, os cuidados com as trilhas já foi maior.
Então espero ter esclarecido essa dúvida de pode ou não pode cada tipo de trilha. Qualquer dúvida use o campo abaixo para comentários!
Valeu pessoal e até a próxima!