Questão:
Como interconectar vários Arduinos com um Rpi para controlar home-lights / switches
Damiano Verzulli
2015-09-05 04:37:32 UTC
view on stackexchange narkive permalink

Ao planejar a infraestrutura de iluminação (interruptores e luzes) da minha nova casa (ainda em construção), optei por seguir o caminho da "rota automatizada" e devido à minha formação (sou um "velho" administrador de sistema / rede com habilidades de programação e bastante "paixão" e "defesa" de código aberto) Estou seriamente tentando implementá-lo com três Arduinos e um RPi2.

Devido ao número / localização dos botões e luzes na parede, gostaria de usar três MEGA fazendo interface com os botões de parede e as luzes das salas circundantes. Além disso, um RPi2 (ou semelhante) será usado como um "controlador" para programar adequadamente os MEGAs, quando necessário, e fazer a interface de alguns outros equipamentos (tela sensível ao toque; tablet / smartphone wi-fi; controles remotos; etc.) através do IP- rede.

O esquema que estou tentando implementar é semelhante a este:

Draft Schema onde você vê:

  • 9 luzes (L1 a L9), cada uma controlada por seu próprio relé dedicado (R1 a R9);
  • 7 botões de parede (WPB1 a WPB7), para serem usados ​​para ligar / desligue uma ou mais luzes;
  • 3 MEGAs com interface de botões e luzes de parede;
  • 1 RPi2, atuando como "supervisor" e "gateway de Internet / Ethernet".

Meu principal problema arquitetônico está relacionado à interconexão entre RPI2 e MEGAs. Como cada dispositivo estará localizado a várias dezenas de metros um do outro, terminei com duas únicas opções (por favor, corrija-me se eu estiver errado):

  1. Ethernet
  2. RS485

( BTW: estou excluindo explicitamente "conexões sem fio", pois já coloquei todos os tubos elétricos de uma forma "compatível". Em outras palavras: Gostaria de evitar tecnologias sem fio na "rede de controle" )

Quanto à Ethernet, vou escolhê-la como uma segunda opção, devido ao custo e à complexidade um pouco maiores ( precisa de um switch para ser ligado; problemas de cabeamento adicional; etc.).

Pesquisei extensivamente o " barramento RS485 " e descobri que é relativamente fácil - do ponto de vista físico - implementá-lo com dois fios em uma configuração multidrop.

Infelizmente, do "ponto de vista do aplicativo", as coisas são mais complexas, pois ele suporta apenas comunicações "half-duplex" e, pior ainda, não há provisão para evitar "colisões" durante a comunicação (é por isso que, provavelmente, o protocolo MODBUS --tipicamente empregado no barramento RS485 - fornece um cenário de "mestre único; escravos múltiplos").

Antes do perguntas, preciso adicionar outra restrição: Eu quero que a infraestrutura seja "o mais tolerante a falhas possível", especialmente em relação a problemas com o BUS e / ou com o "controlador" (o RPI2). Em outras palavras:

  • cada MEGA deve permitir acender suas próprias luzes quando um de seus próprios botões assim o exigir. Por exemplo:
    • se WPB1 controla L2 e L3, ele precisa estar funcionando mesmo se o BUS estiver quebrado ou o RPI2 estiver desligado;
    • se WPB3 controlar L4 e L9, então deveria o barramento / RPI2 está com problemas, apenas L4 será ligado quando WPB3 for pressionado;

Então, depois de tudo o que foi dito acima, aqui estão as minhas dúvidas:

  1. é um barramento multidrop RS485 de dois fios, uma escolha adequada para o meu cenário?

  2. se não, qual (possivelmente barato e simples) outras soluções posso investigar?

  3. se sim, qual é a lógica que preciso implementar nos MEGAs, como:

    • a) precisam atuar como "escravos", no que diz respeito ao RPI2, ao acender as luzes comandadas por botão conectado a outros MEGAs ou quando o RP2 decidir acender algumas luzes (por exemplo, por acesso remoto / Internet) ;
    • b) eles precisam atuar como "mestre", quando um de seus botões é pressionado e ... isso precisa ser comunicado ao RPI2 para enviar comandos a outros MEGAs para ligar luzes "hospedadas";
  4. quanto a 3b), em vez de ter MEGAs atuando como mestre quando um WPB é pressionado, posso implementar uma lógica de "pesquisa frequente" no RPI2? Se sim, qual é um valor razoável para tal pesquisa (1 pesquisa por segundo? 5 pesquisas por segundo? Muito? Muito baixo?)

Eu entendo que isso é uma pergunta muito ampla, mas eu realmente fiz muitas pesquisas e, apesar de muitas, muitas e muitas documentações on-line, não consegui encontrar uma resposta para essas perguntas.


Atualização 1

  • em termos de números totais, terei 31 luzes a serem controladas por 46 botões de parede, mais ou menos igualmente distribuídos em 4 distintos painéis;
  • Quanto à escolha de MEGA (vs. UNO), escolhi MEGA devido ao maior número de PINs de E / S. Simplesmente escolhi o quadro com o número máximo de PINs;
  • Quanto ao RPI2, optei por empregar um "computador" adequado (em vez de um microcontrolador adicional), porque quero uma espécie de "desacoplamento" entre a "rede de controle físico" e a "Interface de gerenciamento do usuário". Em outras palavras, eu quero que o gerenciamento de botões / relais físicos seja feito por um dispositivo tipo PLC (Arduino: inicialização muito rápida; tipo de desempenho em tempo real; muito confiável; fatores de "computação" menores / nenhum externo que introduzem atrasos / problemas); enquanto, ao mesmo tempo, toda a interface do usuário é gerenciada por um computador real onde posso facilmente escrever serviços da web HTTP poderosos e / ou "lógicas" realmente complexas, usando tecnologias e linguagens que não se encaixam bem com o Arduino (mais tarde --muito mais tarde--, pretendo adicionar vários tablets android 150 $ full-HD 10.1 "por toda a casa, que irão atuar como" clientes "sem fio para o que precisa ser um" poderoso "(em termos de capacidades de computação) servidor web; Também pretendo adicionar vários sensores [temperatura; umidade; contatos; medidores de energia; etc.] cujos dados precisam ser armazenados por motivos de tendência / arquivamento). Portanto, pensei em RPi2, do que posso pode ser facilmente substituído por algo mais poderoso, se necessário.
You'll probably want something connected to the RPi that can do 9-bit communications on RS-485. Linux doesn't support 9-bit UARTs so you'd have to play ugly tricks with the parity bit without it.
E se eu conectar, via RS485, apenas ARDUINOs? Quer dizer, o problema que você está mencionando é estritamente introduzido ao conectar o RPI ao RS485, certo? Estou perguntando, porque com base também na resposta @nick-gammon abaixo, provavelmente irei encerrar o barramento RS485 em um Arduino adicional, e ter a interconexão entre RPI e Arduino usando Ethernet (e não RS485). Nesse caso, o RPI não precisará falar RS485 e o problema deve desaparecer, certo?
I just wanted to note that the chance of packet collisions is rather low. You only send packets when a switch is pressed, or when a light needs to turn on. Unless you live inside a disco, this only happens a few times a day per light/switch.
@Gerben: com meus dois filhos (2,5 e 5,5), posso garantir que essa probabilidade NÃO é tão baixa quanto você poderia esperar em condições "normais" :-). Piadas à parte, considere que se tudo correr bem, pretendo adicionar MUITOS sensores / dispositivos adicionais a serem consultados via "tráfego de ônibus" (sensores de temperatura / umidade [um por sala]; abrir / fechar contatos para portas / janelas [pelo menos dois para cada janela]; medidores de energia elétrica e medidores de uso de água provavelmente gerando "pulsos" a serem coletados de alguma forma. Então, novamente, quando a "colisão" acontecer, meus MEGAs "sobreviverão"? Ou eles ' vou queimar?
Quatro respostas:
Nick Gammon
2015-09-05 06:00:09 UTC
view on stackexchange narkive permalink

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:

RS485 wiring


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

RS485 demo

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

RS485 moving master schematic


Tela -fotos do código em ação

Essas imagens mostram o código funcionando. Primeiro, com apenas um dispositivo conectado:

RS485 - one device

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.


RS485 - two devices

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.


RS485 - three devices

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.

1st) Thanks VERY MUCH for your answer and RS-485 tutorial. I'll surely use it ; 2nd) as for MEGA and RPIs involvement, I've updated the OP. Please, refer to it for details; 3rd) as for "_I can't see any particular reason why you could not interrogate the slaves quite frequently_", is a 10-polls per second, on a 9600 baud rate, a reasonable choice? And finally: if I understood correctly, I will **not** need any poll at all, as with your approach I can implement a "single-master/multiple-slave" context... but with a "moving" master. Is this right?
Veja a resposta atualizada.
Veja a resposta alterada que demonstra um conceito de mestre em movimento.
`is a 10-polls per second, on a 9600 baud rate, a reasonable choice?` - in my current test (see screenshots) I am testing 4 devices repeatedly in 75 ms at 19200 baud, so that is 13 status reports a second. Basically that means one device should react to a status change (eg. a switch closure) from another device in 1/10th of a second. Your initial question mentioned 4 devices, so you should be able to get similar results.
poderia me dizer onde conseguiu o clipe de fio branco que está na imagem com o fundo vermelho (o clipe está preso ao feixe de fio)
Ebay: https://www.ebay.com/itm/400846299509. Pesquise por `10x 2p Spring Connector fio sem solda sem parafuso`.
Gil
2015-10-19 04:40:40 UTC
view on stackexchange narkive permalink

Lembre-se de que RS485 não é um protocolo, é uma definição de uma camada de transporte físico. Com o Mega que você escolheu, você pode usar 1,2,3 serial e executá-los em modo full duplex na rede RS485, por exemplo você pode receber o que enviar.

Eu o tenho operando em uma configuração multimestre. Cada mega, quando transmite, também recebe o que acabou de enviar e pode determinar byte a byte se precisa retroceder. O atraso antes de voltar à linha é determinado pelo endereço do nó e quando o barramento fica disponível.

O código usará a função de escrita em vez da função de impressão, permitindo que cada byte seja verificado. (Com o tempo provavelmente farei isso via software e emularia o CAN ou apenas usarei um controlador de lata). Sim, 8 bits funcionam bem, o nono bit é um bit de paridade ou outros usos, dependendo de quem o está definindo.

Meu sistema de teste está operando com pacotes e os envia neste formato:

| Comprimento | Destino | Origem | Sequência | Comando | Sinalizadores | Dados | Verificar

e espera um ACK em resposta. Esta é a primeira passagem relativamente simples e não tem códigos ilegais. Todos os bytes são Hex, mas você pode usar o que quiser.

Isso me dá Carrier Sense, acesso múltiplo, mas com arbitragem destrutiva. Se você quiser que o CSMANDA use apenas uma camada física CAN ou bit bang nos dados que você envia, você pode arbitrar bit a bit. Não será muito diferente no software além da parte de transmissão.

Estou planejando usar o RPi com Linux como o nó principal. Com dados de 8 bits, funcionará perfeitamente. Comecei há cerca de duas semanas com essa configuração e até agora está indo bem. Estou operando a 9600 baud e tenho muito tempo livre.

Gil

Como você configurou as coisas para detectar colisões? O chip específico que você está usando para a interface RS-485 retorna os dados detectados no link na linha RX ao mesmo tempo em que você envia os dados na linha TX e ele os transmite?
Sthing
2015-10-30 02:21:25 UTC
view on stackexchange narkive permalink

Se você quiser usar o protocolo de Nick Gammon, pode achar isso útil: https://github.com/Sthing/Nick-Gammon-RS485

É minha implementação do protocolo em Python (para uso em um RaspberryPi). Eu só testei python-para-python ainda, testar o código de Nick Gammon é o próximo na minha lista de tarefas.

Ultimamente, tenho tido sucesso em testar o protocolo sem bloqueio RS485 de Nick Gammon, entre três MEGAs em um cenário "um mestre, dois escravos". Encontrei vários problemas, principalmente introduzidos pelo uso de seriais físicos (e não de SoftwareSerials). Felizmente, consegui consertar tudo. Publiquei o código em execução (com alguns outros detalhes) aqui: https://github.com/verzulli/arduino-smart-home - Atualmente, estou trabalhando ativamente nesse projeto e pretendo atualizá-lo nas próximas semanas.
user2024827
2018-04-08 08:53:19 UTC
view on stackexchange narkive permalink

Acho que o protocolo CDBUS para RS485 é exatamente o que você deseja, ele introduz um mecanismo de arbitragem que evita automaticamente conflitos como o barramento CAN. Posso até transferir stream de vídeo por meio dele:

enter image description here Vídeo completo: https://youtu.be/qX5dh4wcfSk

O Raspberry Pi pode gerar vídeo de visualização e controle de comando ao mesmo tempo. Podemos monitorar o processo de reconhecimento no PC. Quando forem encontrados problemas, é conveniente saber o motivo e ajustar os parâmetros, e desconectar o PC não afetará a operação de demonstração.

enter image description here Além disso, o Raspberry Pi pode acessar a internet a qualquer momento pelo PC, e é fácil de atualizar o software e por controle remoto.

Detalhes sobre o CDBUS:

Atualização: Conecte o controlador CDCTL-Bx a um Arduino: arduino and cdctl

A menção de vídeo é irrelevante para a pergunta feita e o assunto Arduino deste site e, portanto, atrapalha um pouco. Esse volume de dados pode causar problemas para sistemas com recursos limitados, a menos que sejam gerenciados com elegância. Se você quiser propor este esquema, deve explicar como ele é adequado para o problema da questão, e ** para um Arduino ** ... se realmente for.
É o mesmo para o Arduino, substitua o Raspberry Pi Zero no meu diagrama por um Arduino, ou seja, adicione uma placa de blindagem externa do controlador RS485 para o Arduino, comunique-se através da interface I2c ou SPI.


Estas perguntas e respostas foram traduzidas automaticamente do idioma inglês.O conteúdo original está disponível em stackexchange, que agradecemos pela licença cc by-sa 3.0 sob a qual é distribuído.
Loading...