Manual
do
Maker
.
com
Como prometido no post sobre rs485 com Arduino, agora vamos ver como fazer a comunicação bidirecional, completando assim o post sobre o assunto; mas além disso vamos entrar em detalhes importantes sobre interrupções com PIC (introduzido nesse post). Veremos como controlar uma interrupção adequadamente, achar as flags correspondentes e buscar as respostas sobre um determinado PIC no datasheet. Para finalizar, esse post usará como base o PIC16F877A para os exemplos. Iniciaremos pelas ferramentas.
Essa ferramenta é essencial para quem trabalha com MCU PIC. Com ela você poderá ver todas as características de uma determinada micro-controladora, filtrar por recursos ou simplesmente descontrair vendo os diversos sabores de PIC que existem. Ela trabalha offline e está disponível para Android no Google Play, no link acima.
Essa ferramenta é excepcional; possui diversas calculadoras para calculos específicos de projetos eletrônicos, além de plugins que dão mais poder à ferramenta. Entre os plugins, há um simulador de circuito, mas não deixe de visitar o link do Google Play para confirmar.
Existem muitos fatores que influenciam na escolha da MCU e, dependendo do tamanho do projeto, centavos podem ser representativos em escala de produção. Tamanho de circuito, número de trilhas, recursos periféricos, saídas PWM, comunicação USB e outros recursos variam de modelo para modelo, então o primeiro passo é amadurecer a idéia do objetivo, posteriormente escolher a MCU. Um detalhe que deve ser levado em consideração é a intensidade de processamento e complexidade do código e da operação. A quantidade dos diversos tipos de memória de cada MCU varia drasticamente e é necessário ter esse recurso de sobra ou muito bem planejado para não ter grandes surpresas durante uma fase mais avançada do desenvolvimento.
Especificamente esse post é baseado no P16F877A porque é a MCU padrão dos exemplos da IDE MikroC e como estou me adaptando à board de desenvolvimento (EasyPIC (link)), preferi utilizar uma MCU que minimizasse possíveis dúvidas.
A comunicação com o CI RS485 é transparente, de forma que a comunicação se dá pela UART, bastando escrever dados seriais. O 'pulo do gato' está no chaveamento dos pinos DE e RE do MAX485 e a forma de ligá-los é bastante simples; faz-se um jumping entre eles. O que definirá se os dados sairão ou entrarão é mais do que o TX e RX da UART, fazendo-se fundamental o chaveamento de controle de fluxo, descrito e detalhado no código adiante.
A comunicação RS485 possui duas partes fundamentais - o Master e o Slave. Isto é porque não podem ambos iniciar uma comunicação no barramento, pois não haveria como controlar a leitura e escrita sincronizada. Então, essa rede possui 1 Master e N Slaves.
A tarefa do Master é ser o controlador do barramento, que pode conter até 32 dispositivos em uma distância máxima de 1200 metros! Ou seja, toda a conversa é iniciada pelo Master e todos os Slaves ficam 'escutando' o barramento esperando que haja alguma tarefa para si.
A mensagem enviada pelo master é disparada como um broadcast no barramento; todos os dispositivos escutarão as mensagens, mas a resposta deverá partir exclusivamente do dispositivo a qual a mensagem pertença. Isso é possível graças a um identificador, normalmente colocado ao inicio da mensagem. Quando o Slave responde, novamente a mensagem trafega como um broadcast, porém é imediatamente descartado após a leitura do primeiro byte, que identifica a mensagem como uma resposta. Esses detalhes serão descritos no decorrer do código, mais adiante.
Indo mais a fundo, fica claro que o chaveamento do CI RS485 nos slaves está em um modo e no Master está de outro modo. Assim o é. Então, há duas maneiras para o Slave aguardar por uma mensagem.
Esse é o modo mais simples; em um loop infinito com um delay, o programa tentará validar dados no buffer na UART. Encontrando dados, passa a executar a tarefa de tratamento da mensagem de entrada e saída. Apesar de ser de fácil implementação, possui alguns contras, entre os quais, o consumo de energia certamente será maior, pois a MCU executará uma quantidade X de instruções a cada ciclo. Também nesse modo, é possível que haja pequenos delays na recepção e resposta pois a MCU pode estar executando outras tarefas que precedam a verificação de dados seriais.
A maneira mais clean de lidar com esse tipo de tarefa é com a utilização de interrupções. Ao meu ver, há uma semelhança muito grande com threads em programação desktop. Explico mais detalhadamente.
Quando habilitamos um determinado recurso na MCU, quando esse recurso é utilizado ele gera uma interrupção; um bit é marcado em reação ao evento. As interrupções podem ser desabilitadas ou pode-se habilitar de uma a todas, mas para isso é necessário basicamente ter o datasheet da MCU à mão. A grosso modo, a interrupção é como se fosse uma thread, que reage ao evento assincronamente. Os detalhes serão vistos no código, não se preocupe.
Ainda mais sobre as interrupções, elas são controladas por estágio; um conjunto de chaves que se ligam. O desenho do início deste post descreve com clareza uma interrupção; é como o chaveamento de um interruptor de luz. Para conhecer as chaves, deve-se procurar no datasheet por Interrupts. No datasheet do PIC16F877A, a tabela de interrupções é a seguinte:
Os circulos são as chaves necessárias para ligar as interrupções até o RCIF, marcado com um quadrado. Então, para saber quais chaves ligar, siga o esquema do datasheet e anote as chaves necessárias.
A vantagem de utilizar interrupção é que o processador não precisará fazer nada, ficará dormindo até que seja 'despertado' por um evento - quando o master disparar uma mensagem no barramento e essa mensagem chegar ao Slave, um bit é levantado e a implementação da função de interrupções começa a executar seu código, normalmente analisando qual interrupção aconteceu.
Falando diretamente da família F16, o conjunto de chaves necessários para habilitar a interrupção na recepção serial é inicialmente o GIE, ou Global Interrupt Enable. O GIE é a chave geral à qual todos os demais conjuntos de chaves se ligam, como se pode ver na tabela anterior. Se esta chave estiver desligada, nenhuma interrupção específica será utilizada.
Ligando o GIE, o próximo a habilitar é o PEIE ou, Peripheral Interrupt Enable. Essa chave controla as interrupções periféricas. Enfim, para habilitar a interrupção no RX do UART, deve-se habilitar o RCIE. E como saber essas relações sem ter que decorar nada? - resposta simples! RTFM (Read The Fine Manual).
No exemplo de interrupção utilizado nesse post, deve ser habilitada a interrupção de recepção serial, descrito no datasheet como RCIF (Receive Interrupt Flag). Um Ctrl+F no datasheet e encontrei o registrador responsável por essa flag, o PIR1:
Então para habilitá-lo há diversas maneiras, mas preferi utilizar a IDE MikroC, segurando a tecla Ctrl e clicando sobre uma variável qualquer do PIC padrão (escreva por exemplo, PORTC), abre-se a página de constantes, que foi o que utilizei para definir as minhas variáveis da maneira mais clara no código adiante.
Quando uma interrupção acontece, a primeira tarefa é desligar a chave geral; não é prudente manipular interrupções com interrupções acontecendo a todo o tempo. Depois, analisa-se qual interrupção aconteceu e executa-se o bloco de códigos designado para tratar aquele tipo de evento. Ao termino, o bit da interrupção deve ser limpo e posteriormente a chave geral deve ser religada.
No decorrer do código pode ser bastante cansativo lembrar o nome de todos o bits de cabeça. Criar uma variável para cada também não é uma boa providência, pois isso consome preciosos recursos. Então para solucionar esse problema, utiliza-se o alias. Assim posso chamar o bit PIR1.RCIE de INTERRUPT_RX, o que parece bem auto-explicativo.
Agora vamos ver detalhes de todos os recursos citados até esse momento.
Esse é o código do Slave:
char i; // o char é menor do que o short int em alguns compiladores
short unsigned int SEND = 0; //..mas utilizo mesmo assim
short unsigned int RECEIVE = 3;
short unsigned int LOCAL = 0;
//Criando um alias. O formato é simples:
//sbit APELIDO at VARIAVEL_DO_BIT
sbit TRANS_TRIS at TRISC0_bit; // TRANCEIVER
sbit TRANS_PINO at RC0_bit; // PINO DO TRANSCEIVER
//variavel para buffer de leitura, do tamanho de 10 bytes
char output[10];
/*
Sempre que for trabalhar com interrupções, declara-se essa função no MikroC.
Os passos que se deve seguir são os seguintes:
*/
void interrupt() {
//1 - parar de receber interrupções para tratar o que chegou (desligar chave geral):
GIE_bit = 0;
//2 - verificar se a interrupção que chegou é a pretendida (se foi gerada pelo RX). Se for:
if (RCIF_bit == 1){
//a - muda o valor da variável global LOCAL (poderia ser qualquer outra coisa)
LOCAL = 1;
//b - le a serial (pois se houve interrupção, há dados)
UART1_Read_Text(output,"]",10); //le para a variável output até o demilitador ']', 10 bytes max.
//c - Tratamento concluido. Limpar a flag da interrupção RX
RCIF_bit = 0;
}
//levanta a chave geral
GIE_bit = 1;
}
//PARABENS ! Agora você já sabe tratar uma interrupção! Mas seguindo...
//Uma função para mudar o estado do transceiver
void sendOrReceive(int condition){
// O TRANSCIEVER é o pino DE e RE, nao confundir com TX/RX .
// no esquema da board de desenvolvimento EasyPIC utiliza-se RC0 (PORTC.B0)
if (condition == SEND){
//transmitir
TRANS_TRIS = 0;
TRANS_PINO = 1;
}
else{
//receber
TRANS_TRIS = 1;
TRANS_PINO = 1;
}
Delay_ms(100);
}
//Por fim, o código principal
void main() {
UART1_Init(9600); // Inicializa a comunicação serial
Delay_ms(100); // Evita haver erro marcado com esse delay
/*
Chaves que necessitam ser ligadas:
RCIF___./ .___PEIE___./ .___GIE
*/
RCIE_bit = 1; // Ativa interrução no RX
TXIE_bit = 0; // Desativa no TX
PEIE_bit = 1; // Ativa interrupção na chave de periféricos
GIE_bit = 1; // Ativa a chave geral
while (1) {
sendOrReceive(RECEIVE); //chamada da função
//Verifica aquela variável global (agora modificada pela interrupção)
if (LOCAL == 1){
//muda o transceiver para envio...
sendOrReceive(SEND);
//escreve a resposta...
UART1_Write_Text("INTERRUPCAOnn");
//..e volta a variável global para 0.
LOCAL = 0;
}
Delay_ms(1000);
}
}
As conexões físicas devem ser feitas como mostrado na imagem anterior.
Laranja e Marrom são TX e RX ligados à MCU. O branco é controle de fluxo. A ligação com pilha AA foi apenas para saber onde é ground e onde é VCC, alimentados diretamente pelo PIC. No exemplo da IDE utiliza-se os pinos RC6 e RC7 que são TX e RX e o pino de controle RC2, porém na board de desenvolvimento o pino utilizado é RC0.
Os resistores das extremidades são 56R, os outros dois são 4K7.
No vídeo, estou rodando direto da placa de desenvolvimento de uma empresa em que trabalhei, conversando diretamente com um terminal (GtkTerm) utilizando um conversor RS232 para RS485.
Nem todos os passos de um um produto que estamos desenvolvendo podem ser exibidos, mas quando o produto estiver disponível para o mercado, farei uma avaliação e indicarei onde comprar, pois com certeza será útil a muitas pessoas.
Se gostou, não deixe de compartilhar; dê seu like no video e inscreva-se no nosso canal Manual do Maker Brasil no YouTube.
Próximo post a caminho!
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.