Eu fiz uma postagem longa sobre RS485.
Primeiro, o uso de Megas parece um exagero, a menos que você já os tenha em mãos. Um Uno, ou uma das placas de fator de forma menores parece ser perfeitamente adequado para monitorar alguns interruptores e acender algumas luzes.
Até mesmo o Rpi parece desnecessário. Outro Uno poderia monitorar facilmente suas linhas RS485 e se conectar via Ethernet (um escudo Ethernet) ao resto de sua casa ou o que quer que você esteja fazendo.
... não há provisão para evitar "colisões" ao se comunicar ...
Bem, você constrói isso. Você dá a cada Mega um endereço (por exemplo, armazenado na EEPROM) e então "endereça" aquele a você deseja e, em seguida, aguarde uma resposta. Por exemplo, no código da minha página acima:
Mestre
#include "RS485_protocol .h "#include <SoftwareSerial.h>const byte ENABLE_PIN = 4; const byte LED_PIN = 13; SoftwareSerial rs485 (2, 3); // receber pino, transmitir pino // rotinas de retorno de chamada void fWrite (const byte what) {rs485.write (what); } int fAvailable () {return rs485.available (); } int fRead () {return rs485.read (); } configuração vazia () {rs485.begin (28800); pinMode (ENABLE_PIN, OUTPUT); // habilita a saída do driver pinMode (LED_PIN, OUTPUT); // LED embutido} // fim do byte de configuração old_level = 0; void loop () {// ler o nível de byte do potenciômetro = analogRead (0) / 4; // nenhuma mudança? esqueça if (level == old_level) return; // assemble message byte msg [] = {1, // dispositivo 1 2, // acenda a luz // em que nível}; // enviar para o escravo digitalWrite (ENABLE_PIN, HIGH); // habilita o envio de sendMsg (fWrite, msg, sizeof msg); digitalWrite (ENABLE_PIN, LOW); // desativa o envio // recebe o byte de resposta buf [10]; byte recebido = recvMsg (fDisponível, fRead, buf, sizeof buf); digitalWrite (LED_PIN, recebido == 0); // liga o LED se houver erro
// enviar apenas uma vez por mudança bem-sucedida if (recebido) old_level = level;} // fim do loop
Você configura o transceptor RS485 para enviar ou receber. Normalmente está no modo de recepção e você muda para o modo de envio para enviar um "pacote" de dados. (Veja "habilitar envio" acima).
Agora o dispositivo endereçado transforma seu transceptor no modo "enviar" e responde. O código na biblioteca que escrevi tem um tempo limite, portanto, se esse escravo específico estiver morto, o tempo de recepção expirará. Você deve se lembrar disso no final do master e tentar se comunicar com ele com menos frequência. Ou você pode não se importar se o tempo limite for curto.
Slave
#include <SoftwareSerial.h> # inclui "RS485_protocol.h" SoftwareSerial rs485 (2, 3); // recebe o pino, transmite o byte pinconst ENABLE_PIN = 4; void fWrite (const byte what) {rs485.write (what); } int fAvailable () {return rs485.available (); } int fRead () {return rs485.read (); } configuração vazia () {rs485.begin (28800); pinMode (ENABLE_PIN, OUTPUT); // habilita a saída do driver} void loop () {byte buf [10]; byte recebido = recvMsg (fDisponível, fRead, buf, sizeof (buf)); if (recebido) {if (buf [0]! = 1) return; // não é meu dispositivo if (buf [1]! = 2) return; // byte de comando desconhecido msg [] = {0, // dispositivo 0 (mestre) 3, // acende a luz no comando recebido}; atraso (1); // dê ao mestre um momento para se preparar para receber digitalWrite (ENABLE_PIN, HIGH); // habilita o envio de sendMsg (fWrite, msg, sizeof msg); digitalWrite (ENABLE_PIN, LOW); // desativa o envio analogWrite (11, buf [2]); // definir o nível de luz} // fim se algo recebido} // fim do loop
Observação : o código de exemplo da minha página vinculada não lê o endereço da EEPROM, no entanto, isso é trivial de implementar.
Não consigo ver nenhuma razão em particular para você não interrogar os escravos com frequência. O que mais o mestre estaria fazendo? Você também pode configurar o mestre como um servidor HTTP para que possa falar com ele do seu laptop ou de alguma outra parte da casa.
Minha fiação:
Para demonstrar a ideia, configurei dois Unos, conectados por cerca de 8 m de fio de sino (nem mesmo par trançado, e certamente não blindado).
Um Uno estava executando o esboço da tabela ASCII padrão (a 9600 baud), seu pino Tx foi para o LTC1480, e os pinos A / B foram para o fio da campainha.
O outro Uno foi conectado como uma interface USB (Reset conectado ao aterramento) e estava apenas ecoando o que quer que chegasse no pino Tx para o USB.
Pelo que pude ver, funcionou perfeitamente.
Não vou precisar de nenhuma enquete, pois com a sua abordagem posso implementar um " contexto mestre único / escravo múltiplo "... mas com um mestre" móvel ". Isso está certo?
Minha resposta acima assumiu que os escravos eram mais propensos a falhar do que o mestre (não consigo imaginar por que isso aconteceria, mas talvez com base no fato de que há mais escravos do que mestres, e o mestre não controla coisas como luzes).
Acho meus Arduinos extremamente confiáveis ao fazer coisas simples (como destrancar uma porta quando um cartão RFID é apresentado).
Você poderia construir uma posição de recuo para os escravos. Afinal, se eles forem pesquisados a cada segundo e nenhuma pesquisa chegar, eles podem tentar assumir o controle como mestre, talvez em ordem crescente de número de dispositivo, para evitar conflitos. Parte dessa pesquisa do "novo mestre" poderia ser para verificar o "mestre original" para ver se ele estava pronto para retomar suas funções.
A biblioteca que descrevi em minha página vinculada contém verificação de erros, a ideia é que, ao verificar um CRC no pacote, você garante que não vai entrar no meio de um pacote e interpretar mal os dados nele .
Você também pode criar alguma aleatoriedade nos tempos de votação para resolver um impasse se dois escravos tentarem se tornar mestre ao mesmo tempo. Se um escravo falhasse, ele poderia esperar por um tempo aleatório (e crescente) antes de tentar novamente dar a outro escravo a chance de fazer isso.
Eu só queria observar que a chance de colisões de pacotes é bastante baixo. Você só envia pacotes quando um interruptor é pressionado ou quando uma luz precisa ser acesa.
Gerben está certo, mas eu ficaria preocupado se uma notificação sobre um interruptor tivesse passado despercebida. Uma solução possível aqui é, em vez de os escravos responderem com mudanças de estado a uma consulta, eles respondem com o estado atual. Então pode ser:
Master: Slave 3, qual é o seu status? Slave 3: Luzes 1 e 4 acesas, luzes 2 e 3 apagadas.
Não vou precisar de nenhuma enquete, já que com sua abordagem, posso implementar um contexto de "mestre único / escravo múltiplo" ... mas com um mestre "em movimento". Está certo?
Estive pensando um pouco sobre isso e acho que agora você poderia fazer um sistema que seja basicamente livre de mestre. Pode funcionar assim:
-
Cada dispositivo tem seu próprio endereço, que obtém da EEPROM (ou chaves DIP). por exemplo. 1, 2, 3, 4, 5 ...
-
Você escolhe um intervalo de endereços que vai usar (por exemplo, máximo de 10)
-
Quando o dispositivo é ligado, ele primeiro escuta outros dispositivos "falando" no barramento. Esperançosamente ele ouvirá pelo menos um outro (se não, veja abaixo).
-
Nós decidimos por um "pacote de mensagem" fixo, digamos de 50 bytes incluindo endereço, CRC, etc. . A 9600 baud, o envio levaria 52 ms.
-
Cada dispositivo obtém um "intervalo" de tempo e espera sua vez para falar com o barramento.
-
Quando seu intervalo de tempo chega, ele entra no modo de saída , e transmite seu pacote que inclui seu próprio endereço. Portanto, todos os outros dispositivos agora podem ler seu status (e agir de acordo com ele, se necessário). Por exemplo. o dispositivo 1 pode relatar que a chave 3 está fechada, o que significa que o dispositivo 2 deve acender uma luz.
-
Idealmente, você sabe que seu intervalo de tempo chegou porque o endereço do seu dispositivo é maior que o pacote que você acabou de ouvir. Por exemplo. Você é o dispositivo 3. Você acabou de ouvir o dispositivo 2 anunciar seu status. Agora é sua vez. Claro, você volta ao número máximo, então após o dispositivo 10 você volta para o dispositivo 1.
-
Se um dispositivo estiver faltando e não responder, você o dá ( digamos) meio intervalo de tempo para responder e, em seguida, assumir que está morto, e cada dispositivo no barramento agora assume que o próximo intervalo de tempo foi iniciado. (por exemplo, você ouviu o dispositivo 2, o dispositivo 3 deve responder, após 25 ms de inatividade, o dispositivo 4 pode agora responder). Esta regra dá a um dispositivo 25 ms para responder, o que deve ser suficiente mesmo se estiver atendendo a uma interrupção ou algo parecido.
-
Se vários dispositivos em sequência estiverem faltando, você conta um 25 ms de intervalo para cada dispositivo ausente, até chegar a sua vez.
-
Assim que você obtiver pelo menos uma resposta, o tempo é ressincronizado, então qualquer variação nos relógios seria cancelada.
A única dificuldade aqui é que após a energização inicial (o que pode acontecer simultaneamente se a energia para o prédio for perdida e depois restaurada), não há dispositivo transmitindo atualmente seu status e, portanto, nada para sincronizar.
Nesse caso:
-
Se depois de ouvir por tempo suficiente para que todos os dispositivos sejam ouvidos (por exemplo, 250 ms) e sem ouvir nada, o dispositivo assume provisoriamente que é o primeiro e faz uma transmissão. No entanto, possivelmente dois dispositivos farão isso ao mesmo tempo e, portanto, nunca se ouvirão.
-
Se um dispositivo não tiver ouvido falar de outro dispositivo, ele alternará o tempo entre as transmissões aleatoriamente (talvez semeando o gerador de número aleatório a partir do número do dispositivo, para evitar que todos os dispositivos "aleatoriamente" alternem as transmissões na mesma quantidade ).
-
Este escalonamento aleatório por períodos adicionais de tempo não importa, porque não há ninguém ouvindo de qualquer maneira.
-
Mais cedo ou mais tarde, um dispositivo terá uso exclusivo do barramento e os outros poderão sincronizar com ele da maneira usual.
Esta lacuna aleatória entre as tentativas de comunicação é semelhante ao que a Ethernet costumava fazer quando vários dispositivos compartilhavam um cabo coaxial.
Demonstração do sistema master-free
Este foi um desafio interessante, então eu montei uma demonstração de como fazer isso sem ter nenhum mestre específico, conforme descrito acima.
Primeiro você precisa configurar o endereço do dispositivo atual e o número de dispositivos na EEPROM, então execute este esboço, alterando o myAddress para cada ch Arduino:
#include <EEPROM.h>const byte myAddress = 3; const byte numberOfDevices = 4; void setup () {if (EEPROM.read ( 0)! = MyAddress) EEPROM.write (0, myAddress); if (EEPROM.read (1)! = numberOfDevices) EEPROM.write (1, numberOfDevices); } // fim do loop de setupvoid () {}
Agora faça o upload para cada dispositivo:
/ * Demonstração de controle de dispositivo RS485 multi-drop. Idealizado e escrito por Nick Gammon. Data: 7 de setembro de 2015 Versão: 1.0 Licença: Lançada para uso público. Para a biblioteca RS485_non_blocking, consulte: http://www.gammon.com.au/forum/?id=11428 Para JKISS32, consulte: http://forum.arduino.cc/index.php?topic=263849.0*/#include <RS485_non_blocking. h> # include <SoftwareSerial.h> # include <EEPROM.h> // os dados que transmitimos para cada um dos outros dispositivos {endereço de byte; chaves de byte [10];
status interno; } mensagem; const sem sinal longo BAUD_RATE = 9600; const float TIME_PER_BYTE = 1.0 / (BAUD_RATE / 10.0); // segundos por envio de um byteconst unsigned long PACKET_LENGTH = ((sizeof (message) * 2) + 6); // 2 bytes por byte de carga útil mais STX / ETC / CRCconst unsigned long PACKET_TIME = TIME_PER_BYTE * PACKET_LENGTH * 1000000; // microssegundos // byte serial de pinosconst de software RX_PIN = 2; const byte TX_PIN = 3; // transmitir habilitar byte XMIT_ENABLE_PIN = 4; // depurar pinsconst byte OK_PIN = 6; const byte TIMEOUT_PIN = 7; const byte SEND_PIN = 8; const byte SEARCHING_PIN = 9; const byte ERROR_PIN = 10; // pinos de ação (demo) const byte LED_PIN = 13; const byte SWITCH_PIN = A0; // tempos em microssegundosconst unsigned long TIME_BETWEEN_MESSAGES = 3000; unsigned long noMessagesTimeout; byte nextAddress; unsigned long lastMessageTime; unsigned long lastCommsTime; unsigned long randomTime; SoftwareSerial rs485 (RX_PIN, TX_PIN); // receba o pino, transmita o pino // em que estado estamos inenum {STATE_NO_DEVICES, STATE_RECENT_RESPONSE, STATE_TIMED_OUT,} estado; // callbacks para o RS485 não bloqueador librarysize_t fWrite (const byte what) {rs485.write (what); } int fAvailable () {return rs485.available (); } int fRead () {lastCommsTime = micros (); return rs485.read (); } // instância da biblioteca RS485RS485 myChannel (fRead, fAvailable, fWrite, 20); // from EEPROMbyte myAddress; // quem somos; byte numberOfDevices; // dispositivos máximos no barramento // Semente inicial para JKISS32static unsigned long x = 123456789, y = 234567891, z = 345678912, w = 456789123, c = 0; // Simple Random Number Generatorunsigned long JKISS32 () {long t; y ^ = y << 5; y ^ = y >> 7; y ^ = y << 22; t = z + w + c; z = w; c = t < 0; w = t & 2147483647; x + = 1411392427; retornar x + y + w;
} // fim de JKISS32 void Seed_JKISS32 (const unsigned long newseed) {if (newseed! = 0) {x = 123456789; y = semente nova; z = 345678912; w = 456789123; c = 0; }} // fim de Seed_JKISS32 void setup () {// depuração de impressões Serial.begin (115200); // serial do software para falar com outros dispositivos rs485.begin (BAUD_RATE); // inicializa a biblioteca RS485 myChannel.begin (); // depuração de impressões Serial.println (); Serial.println (F ("Começando")); myAddress = EEPROM.read (0); Serial.print (F ("Meu endereço é")); Serial.println (int (myAddress)); numberOfDevices = EEPROM.read (1); Serial.print (F ("O endereço máximo é")); Serial.println (int (numberOfDevices)); if (myAddress > = numberOfDevices) Serial.print (F ("** AVISO ** - o número do dispositivo está fora do intervalo, não será detectado.")); Serial.print (F ("Comprimento do pacote =")); Serial.print (PACKET_LENGTH); Serial.println (F ("bytes.")); Serial.print (F ("Tempo do pacote =")); Serial.print (PACKET_TIME); Serial.println (F ("microssegundos.")); // calcule quanto tempo para assumir que nada está respondendo noMessagesTimeout = (PACKET_TIME + TIME_BETWEEN_MESSAGES) * numberOfDevices * 2; Serial.print (F ("Tempo limite para nenhuma mensagem =")); Serial.print (noMessagesTimeout); Serial.println (F ("microssegundos.")); // configura vários pinos pinMode (XMIT_ENABLE_PIN, OUTPUT); // pinos de ação de demonstração pinMode (SWITCH_PIN, INPUT_PULLUP); pinMode (LED_PIN, OUTPUT); // pinos de depuração pinMode (OK_PIN, OUTPUT); pinMode (TIMEOUT_PIN, OUTPUT); pinMode (SEND_PIN, OUTPUT); pinMode (SEARCHING_PIN, OUTPUT); pinMode (ERROR_PIN, OUTPUT); // semeia o PRNG Seed_JKISS32 (myAddress + 1000); estado = STATE_NO_DEVICES; nextAddress = 0; randomTime = JKISS32 ()% 500000; // microssegundos} // fim da configuração // definir o próximo endereço esperado, agrupar no maximumvoid setNextAddress (const byte current) {nextAddress = current; if (nextAddress > = numberOfDevices)
nextAddress = 0; } // fim de setNextAddress // Aqui para processar uma mensagem de entrada processMessage () {// não podemos receber uma mensagem de nós mesmos // alguém deve ter dado a dois dispositivos o mesmo endereço if (message.address == myAddress) {digitalWrite ( ERROR_PIN, HIGH); while (true) {} // desistir} // não pode receber nosso endereço digitalWrite (OK_PIN, HIGH); // lida com a mensagem recebida, dependendo de quem é e dos dados nela // faz com que nosso LED corresponda à chave do dispositivo anterior em sequência if (message.address == (myAddress - 1)) digitalWrite (LED_PIN, message .interruptores [0]); digitalWrite (OK_PIN, LOW); } // fim de processMessage // Aqui para enviar nossa própria mensagem void sendMessage () {digitalWrite (SEND_PIN, HIGH); memset (&message, 0, sizeof mensagem); message.address = myAddress; // preencha outras coisas aqui (por exemplo, posições de switch, leituras analógicas, etc.) message.switches [0] = digitalRead (SWITCH_PIN); // agora envie digitalWrite (XMIT_ENABLE_PIN, HIGH); // habilita o envio de myChannel.sendMsg ((byte *) &message, sizeof message); digitalWrite (XMIT_ENABLE_PIN, LOW); // desativa o envio setNextAddress (myAddress + 1); digitalWrite (SEND_PIN, LOW); lastCommsTime = micros (); // contamos nosso próprio envio como atividade randomTime = JKISS32 ()% 500000; // microssegundos} // fim de sendMessage void loop () {// mensagem de entrada? if (myChannel.update ()) {memset (&message, 0, sizeof mensagem); int len = myChannel.getLength (); if (len > sizeof da mensagem) len = sizeof da mensagem; memcpy (&message, myChannel.getData (), len); lastMessageTime = micros (); setNextAddress (message.address + 1); processMessage (); estado = STATE_RECENT_RESPONSE; } // fim da mensagem recebido completamente // mudar os estados se houver um intervalo muito longo entre as mensagens if (micros () - lastMessageTime > noMessagesTimeout)
estado = STATE_NO_DEVICES; else if (micros () - lastCommsTime > PACKET_TIME) estado = STATE_TIMED_OUT; switch (state) {// há muito tempo que não se ouve nada? Assumiremos então o caso STATE_NO_DEVICES: if (micros () - lastCommsTime > = (noMessagesTimeout + randomTime)) {Serial.println (F ("Sem dispositivos.")); digitalWrite (SEARCHING_PIN, HIGH); enviar mensagem (); digitalWrite (SEARCHING_PIN, LOW); } pausa; // ouvimos de outro dispositivo recentemente // se for nossa vez, responda case STATE_RECENT_RESPONSE: // permitimos uma pequena lacuna, e se for nossa vez, enviamos nossa mensagem if (micros () - lastCommsTime > = TIME_BETWEEN_MESSAGES && myAddress == nextAddress) sendMessage (); quebrar; // um dispositivo não respondeu em seu tempo de slot, vá para o próximo caso STATE_TIMED_OUT: digitalWrite (TIMEOUT_PIN, HIGH); setNextAddress (nextAddress + 1); lastCommsTime + = PACKET_TIME; digitalWrite (TIMEOUT_PIN, LOW); estado = STATE_RECENT_RESPONSE; // fingir que obtivemos a quebra de resposta ausente; } // fim do estado de ativação} // fim do loop
Como está atualmente, se você fechar um interruptor em A0 (curto para terra) ele desligará um LED (pino 13) no próximo dispositivo mais alto da sequência. Isso prova que os dispositivos estão se comunicando. É claro que, na prática, você teria algo mais sofisticado no pacote que está sendo transmitido.
Descobri nos testes que o LED parecia acender e apagar instantaneamente.
Com todos os dispositivos desconectados, o primeiro conectado "procurará" outros dispositivos. Se você tem LEDs de depuração conectados como eu, você pode ver o LED "search" acender em intervalos aleatórios enquanto transmite seu pacote com intervalos que variam aleatoriamente. Depois de conectar um segundo, eles se acomodam e trocam informações. Testei com três conectadas ao mesmo tempo.
Provavelmente seria mais confiável com HardwareSerial - usei SoftwareSerial para ajudar na depuração. Algumas pequenas mudanças levariam a isso.
Esquema corrigido
Tela -fotos do código em ação
Essas imagens mostram o código funcionando. Primeiro, com apenas um dispositivo conectado:
Você pode ver pelos pulsos lá que o dispositivo está transmitindo seus dados de forma aleatória intervalos, para evitar o conflito contínuo com outro dispositivo que foi ligado no mesmo momento.
Agora vemos os blocos de dados de dois dispositivos, com lacunas aproximadamente do mesmo tamanho no meio. Eu configurei para quatro dispositivos, mas apenas dois estão presentes, então vemos dois blocos de dados e duas lacunas.
Agora, com três dispositivos online, vemos três blocos de dados e uma lacuna, pois o dispositivo ausente é contornado.
Se você está verificando os números, eles foram tirados com o a taxa de baud dobrou, como teste, para 19200 baud.
Teste de operação do cabo
Para um teste de hardware adequado, conectei os dispositivos ao meu UTP interno cabeamento. Eu tenho um cabo cat-5 que vai de várias salas até uma placa central. Indo de uma extremidade à outra da casa (uma corrida de comprimento razoável) ainda funciona bem. Para começar, há um cabo de 5 m entre o Arduino e a tomada da parede. Mais outro cabo de 5 m na outra extremidade. Depois, há cerca de 2 x 15 metros de distância das salas para a sala de comutação e, dentro dela, há um cabo curto de ponte para conectá-los.
Isso foi com as placas ainda programadas para funcionar a 19200 bauds.