Manual

do

Maker

.

com

RS485 com PIC16F690

RS485 com PIC16F690

Na IDE que utilizo para programar para PIC (MikroC) encontram-se muitas bibliotecas, tal como em Arduino e por padrão, muitas mais já instaladas. Há algum tempo trabalhei em um projeto cuja comunicação em rede era RS485. Em Arduino, escrevi uma pequena prova de conceito nesse post. Como não escrevi nada para PIC, resolvi disponibilizar o protocolo desenvolvido na época, mas hoje veremos RS485 com PIC16F690.

Esquema

rs485-1-300x222.webp
(não se incomode com o modelo da imagem)

Seu alcance é de até 1200 metros com tolerância a pertubações eletromagnéticas.

Esse é um padrão de mercado e deve ser respeitado. Por exemplo, não é fundamental os resistores de 4.7k, mas o padrão prevê o máximo de proteção para a rede.

O código implementado será exposto a seguir, com os respectivos comentários.

Código

//ID do dispositivo local
char ID[] = "01";
char extID[3];
int fromEEPROM[2];
char sensorN[2];
char serial[2];

//Aqui deveria ser unsigned char, mas errei quando escrevi
unsigned short int answer = 1;
unsigned short int sleepTime;

short int freq;
short int ERR;

char sensorToStr[6];
float sensorResult;

char resposta[12];
//os splits da mensagem devem ser feitas nessa variável
char message[10];
char i;
char ok = 0;
short int SEND    = 0;
short int RECEIVE = 3;

//sbit são aliases para o tris e o pino
sbit TRANS_TRIS  at  TRISC5_bit;   // TRIS !!!
sbit TRANS_PINO  at  RC5_bit;      // PINO !!!

sbit LED_TRIS    at TRISC4_bit; //tris do led
sbit LED         at RC4_bit;

sbit BUZZER_TRIS at TRISC6_bit;
sbit BUZZER      at RC6_bit;

char output[10];
char receiveAbyte;

//flag para tocar o buzzer
short int playBuzzer = 1;

//limpar array. Desse modo economiza-se processamento, pois o tamanho já está definido
void clear(char *var,short int size){
    for (i=0;i<size;i++){
        var[i] = '';
    }
}

//conversao de hexa para decimal, da maneira mais simples possivel, para int e char
short int hex2dec(char b){
    if (b >57 && b <71){
        return b-55;
    }
    else if (b>47 && b<58){
    return b-48;
    }
}

//leitura da EEPROM
void myEEPROM(short int SorI){
    fromEEPROM[0] = 0 + EEPROM_Read(0x00+SorI);
    fromEEPROM[1] = 0 + EEPROM_Read(0x01+SorI);

    if (SorI == 0){
        if (fromEEPROM[0] == 255 && fromEEPROM[1] == 255){
            ID[0] = '0';
            ID[1] = '1';
        }
        else{
            ID[0] = (char) fromEEPROM[0];
            ID[1] = (char) fromEEPROM[1];
        }
    }
    else{
        if (fromEEPROM[0] == 255 && fromEEPROM[1] == 255){
            serial[0] = '0';
            serial[1] = '0';
        }
    }
}

//o formato da mensagem é ID:bdc. O tempo de resposta para que não haja colisão é baseada
// no ID do dispositivo em questão
void commandAnalyser(){
    //BROADCAST - se recebe um broadcast (mensagem = B):
    //envia o ID:bdc baseando o tempo no ID.
    i = output[0];
    if (i == 'B'){
        sleepTime = atoi(ID);
        for (i=0;i<sleepTime;i++){
            Delay_ms(10);
        }
        clear(resposta,12);
        strcpy(resposta,ID);
        strncat(resposta,":BDCn",5);
        answer = 1;
        return;
    }

    //string em output
    clear(message,10);
    //2 bytes sao o ID
    clear(extID,3);
    extID[0] = output[0];
    extID[1] = output[1];

    //se nao for ID identico, ignorar. Todas as msgs que não são broadcast são tratadas assim
    if (strcmp(extID,ID)){
        clear(resposta,12);
        clear(message,10);
        clear(output,10);
        answer = 0;
        return;
    }
    //parse da mensagem
    clear(message,10);
    for (i=3;i<strlen(output);i++){
        message[i-3] = output[i];
    }
    //AA:BCD
    if (message[0] == 'P'){
        if (message[1] == 'U'){
        //levanta pino output[5]. Não implementado
        }
        else if (message[1] == 'D'){
            //abaixa pino output[5]
        }
    }
    else if (message[0] == 'T'){
        //Tom no buzzer RC6
        /* A T E N Ç A O
        O funcionamento esta perfeito. Pode acontecer que, ao iniciar uma
        interrupção, o BUZZER pare de tocar. Isso acontece porque no main()
        o pino está sendo levantado a cada 50ms. Se houver uma interrupção
        nesse intervalo, o som pode parar.
        O caso mais comum é não haver intermitência do BUZZER durante uma
        interrupção, pelo mesmo motivo.
        Solução: Pode-se simplesmente baixar o pino para o BUZZER tocar, sem
        interagir no loop do main().
        */
        playBuzzer = message[1] - 49;
        clear(resposta,12);
        resposta[0] = 'O'; resposta[1] = 'K'; resposta[2] = 'n';
        resposta[3] = 0;
    }
    else if (message[0] == 'L'){
        //liga ou desliga (L0/1)
        LED = message[1] - 49;
        clear(resposta,12);
        resposta[0] = 'O'; resposta[1] = 'K'; resposta[2] = 'n';
        resposta[3] = 0;
    }
    else if (message[0] == 'S'){
    //Sensor
    //i para economizar memoria
    i = hex2dec(message[1]);
    //TODO: a amostra vem do pino. fazer array de sensor/pino (ERRADO ABAIXO)
    freq = ADC_Get_Sample(hex2dec(message[1]));
    //1,2,3 supondo LM35
    if (i <4){
        sensorResult = (5.0 * freq * 100.0)/1024.0;
    }
    clear(resposta,12);
    strcpy(resposta,ID);
    strncat(resposta,":S",2);
    //1 =B, 2=D, 3=C
    sensorN[0] = message[1];
    sensorN[1] = ' ';
    strncat(resposta,sensorN,1);
    strncat(resposta,"=",1);
    clear(sensorToStr,6);
    ERR = FloatToStr(sensorResult,sensorToStr);
    if (ERR == 0){
        strncat(resposta,sensorToStr,5);
    }
    else{
        strncat(resposta,"0.00",5);
    }
    strncat(resposta,"n",1);
}

//GRAVAR ADDR
else if (message[0] == 'G'){
    //-------------------- gravação
    /*
    A gravação será da serial (2 bytes) e do ID (2 bytes)
    message[1] deve ser igual a S (serial) ou I (id), seguido dos 2 bytes.

    Os primeiros 2 bytes da eeprom são ID e os 2 seguintes são serial.
    O dispositivo pode receber qualquer formato, decimal ou hexa.
    */
    //-----------------------------
    if (message[1] == 'I'){
        EEPROM_Write(0x00,0x00 + (int) message[2]);
        Delay_ms(50);
        EEPROM_Write(0x01,0x00 + (int) message[3]);
        Delay_ms(50);
        ID[0] = message[2];
        ID[1] = message[3];
    }
    else if (message[1] == 'S'){
        EEPROM_Write(0x02,0x00 + (int) message[2]);
        Delay_ms(50);
        EEPROM_Write(0x03,0x00 + (int) message[3]);
        Delay_ms(50);
        serial[0] = message[2];
        serial[1] = message[3];
    }

    clear(resposta,12);
    clear(message,10);
    for (i=4;i<strlen(output);i++){
        message[i-4] = output[i];
    }
    strcpy(resposta,ID);
    strncat(resposta,":",1);
    strncat(resposta,message,strlen(output)-4);

    //feita a configuração, deve efetuar um reset
    /*
    asm{
    GOTO 0x0000;
    }
    */
}
//quando se consulta o ID na eeprom, ele é armazenado em ID mesmo!
else if (message[0] == '?'){
    if (message[1] == 'I'){
        myEEPROM(0);
    }
    else{
        myEEPROM(2);
    }
    //agora monta a resposta
    clear(resposta,12);
    strcpy(resposta,ID);
    strncat(resposta,":",1);
    if (message[1] == 'S'){
        strncat(resposta,serial,2);
        strncat(resposta,"n",1);
    }
    else{
        strncat(resposta,ID,2);
        strncat(resposta,"n",1);
    }
}
else{
    answer = 0;
}
}

void reader(){
    for (i=0;i<10;i++){
    output[i] = 'n';
}
receiveAbyte = UART1_Read();
if (receiveAbyte == '['){
    i = 0;
    while (receiveAbyte != ']'){
        if (UART1_Data_Ready() == 1){
            receiveAbyte = UART1_Read();
            output[i] = receiveAbyte;
            i++;
        }
    }
    output[i-1] = 0;
    ok = 1;
}
}

void interrupt() {
GIE_bit = 0;
if (RCIF_bit == 1){
reader();
RCIF_bit = 0;
}
GIE_bit = 1;
}

void sendOrReceive(int condition){
    // O TRANSCIEVER é o pino DE e RE, nao confundir com TX/RX .
    // no esquema da board esta utilizando rc0
    //
    if (condition == SEND){
        //transmitir
        TRANS_PINO = 1;
        Delay_ms(10);
    }
    else{
        //receber
        TRANS_PINO = 0;
        Delay_ms(10);
    }
    Delay_ms(50);
    }

void main() {
    TRANS_TRIS = 0;
    LED = 1;
    Delay_ms(10);
    UART1_Init(9600);     // initialize UART1 module
    Delay_ms(100);        // ensure that error flag is 0
    //ADC_Init();
    //               RCIE___./ .___PEIE___./ .___GIE
    RCIE_bit    = 1;         // enable interrupt on UART1 receive
    TXIE_bit    = 0;         // disable interrupt on UART1 transmit (default)
    PEIE_bit    = 1;         // enable peripheral interrupts
    GIE_bit     = 1;         // enable all interrupts

    ANSEL  = 0;
    ANSELH = 0;

    C1ON_bit = 0;    // Turn off comparators
    C2ON_bit = 0;

    LED_TRIS    = 0;         //aterrando no pic
    BUZZER_TRIS = 0;         //aterrando no pic

    myEEPROM(0);
    Delay_ms(50);

    while (1) {
        BUZZER = playBuzzer;
        //passa para escrita (transciever)
        sendOrReceive(RECEIVE);
        if (ok == 1){
            sendOrReceive(SEND);
            commandAnalyser();
            if (answer == 1){
                //UART1_Write_Text(resposta);
                UART1_Write_Text(resposta);
            }
        else{
            answer = 1;
        }
        ok = 0;
    }
    Delay_ms(50);
    BUZZER = 1;
    Delay_ms(50);
    }
}

A mensagem deve ter um formato assim:
[ID:MSG]
Por exemplo, ler o sensor 1 no dispositivo 09:
[09:S1]

Todos os dispositivos recebem a mensagem. Se o byte 0 não for B e sim '[', então o segundo e terceiro byte são avaliados. Qualquer dispositivo que não seja o 09 irá ignorar a mensagem. O dispositivo 09 então lerá o primeiro byte do campo da mensagem. 'S' representa o sensor, e '1', o sensor 1. Então o dispositivo monta a mensagem [ID:S1VALOR] e responde ao server.

O buzzer é para localização do dispositivo. Por exemplo, tenho 50 dispositivos, qual é o 09? Teria quer ler identificador por identificador, mas se tiver um buzzer tocando, posso ir diretamente ao dispositivo.

Enfim, o código pode ser facilmente modificado agora para suas necessidades. Não fiz video porque eu não tenho nenhum hardware comigo infelizmente, mas esse código é o suficiente para sua comunicação RS485.

Aqui foi utilizado o recurso de interrupções, ou seja, quando chega um dado serial o programa para completamente para atender a interrupção, precedendo qualquer tratamento com o desligamento das interrupções, afim de 'represar' qualquer novo dado entrante.

Se precisar de mais conceitos sobre interrupções ou outros recursos utilizados aqui, procure aqui no site, pois há referência para tudo o que foi utilizado.

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!

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.