nfusões também. Tentarei não ser muito técnico.
Mas antes peço que olhe um Diagrama do Circuito de um daqueles módulos que vc disse ter o sinal "JD-VCC". Para simplificar, vamos considerar o módulo Relé com apenas "1 canal". O Diagrama está na figura a seguir:
(clique na figura para "zoom")
Na figura, vc pode observar que a sequência dos sinais no Circuito, foi alterada. A sequência dos pinos está ao lado da "foto" do módulo Relé, sendo os pinos numerados de 1 a 5. Mas esta numeração não é importante. O importante são os sinais em si, e no Circuito eu alterei esta sequência apenas para para facilitar o entendimento do funcionamento.
Vamos considerar 2 cenários: um onde o "JD-VCC" não está conectado ao "VCC", e um onde o "JD-VCC" está conectado ao "VCC" (via "jumper" encaixado nos dois terminais no conector). Então segue:
1) "JD-VCC" não conectado ao "VCC": se vc seguir o circuito iniciando pelo sinal "VCC", passando pelo Resistor R1, depois pelo LED interno do Opto-Acoplador (terminais 1 e 2 de U1), e finalmente pelo LED "LD1", então se chega ao sinal "IN1". Agora esqueça o restante do circuito, e se concentre nesse "caminho" que eu descrevi.
Se vc ligar o sinal "VCC" ao 5V do Arduino, e o sinal "IN1" a um pino de saída do Arduino, então tanto o LED do Opto-Acoplador, como o LED LD1 serão acionados quando se tem "LOW" no sinal "IN1", e caso tenhamos "HIGH" em "IN1" então ambos os LEDs ficarão desacionados. Assim dizemos que o acionamento é através do Nivel "LOW" (ou "0" se preferir).
Agora considere ligar o sinal "VCC" a um pino de saída do Arduino, e o sinal "IN1" ao GND do Arduino (NÃO é o GND da plaquinha do Relé!!!). Nesta condição, para que os LEDs sejam acionados, é necessário que tenhamos "HIGH" na saída do Arduino, e caso tenhamos "LOW" os LEDs estarão desacionados.
Conclusão: o sinal "VCC" é apenas o nome que deram ao sinal, mas de fato vc deve encará-lo apenas como o sinal que está ligado (via R1) ao "Anodo" do Opto-Acoplador (terminal 1 de U1). Assim, para que os LEDs sejam acionados, é preciso que este terminal tenha uma tensão elétrica positiva em relação ao sinal "IN1". Logo, se vc fixa o sinal "VCC" em 5V (do Arduino), então obrigatoriamente para acionar os LEDs, "IN1" deverá ser "LOW". Já se vc fixa o sinal "VCC" em 0V (o GND do Arduino), obrigatoriamente para acionar os LEDs "IN1" deverá ser "HIGH". Não tem outra forma.
Então observe, que vc pode escolher qual será o Nível Lógico ("LOW" ou "HIGH") que acionará o circuito, o que é portanto 100% flexível em relação a esta escolha do Nivel Lógico. E claro: o sinal "VCC" na plaquinha é apenas o nome que deram, e não significa que vc deve ligá-lo ao VCC do Arduino (5V). Obviamente, a escolha do nome "VCC" para o sinal, não foi muito feliz.
Mas há ainda outra questão: a isolação. Observe que o "caminho" do circuito que descrevi, em nenhum momento entra em contato elétrico com os demais sinais do Circuito, ou melhor: em nenhum momento aquele "caminho" entra em contato com o restante do Circuito que aciona diretamente a Bobina do Relé (R2, Q1, D1). Então dizemos que um circuito está isolado eletricamente do outro. Claro que existem distâncias físicas entre os componentes na plaquinha, e algumas são bem pequenas, talvez até menos de 1mm (isto irá variar de placa pra placa, já que existem muitos modelos no mercado). Os circuitos estão efetivamente isolados, mas o quanto é "forte" esta isolação, vai depender destas distâncias. Por exemplo: se vc aplicar entre os dois circuitos (pode ser entre quaisquer dois pontos entre os dois) uma tensão de 1000V (mil Volts), e a menor distância for de 1mm, provavelmente a isolação irá aguentar, mas se vc aplicar 2500V então poderá começar a "pular" faíscas elétricas entre os dois pontos (da mesma forma que pulam na "cabeça" da vela em um motor de carro). Este assunto é um tanto denso, e estou olhando pelo lado mais simples, pois na prática, a isolação dependerá de diversas características, como o material existente entre os dois pontos (no exemplo são dois: o material da placa e o "Ar"), e ainda existem variações a considerar na medição das distâncias (chamadas de "clearance").
Mas o que interessa aqui é que nas condições que descrevi para as ligações, existe isolação entre os circuitos de acionamento dos LEDs, e o restante do circuito que aciona a Bobina do Relé (D1, Q1, R2, e o transistor interno do Opto-Acoplador).
Claro, para que efetivamente o Relé seja acionado (quando vc acionar os LEDs) , vc precisa aplicar uma tensão entre o sinal "JD-VCC" e o sinal "GND", e esta tensão é a "nominal" para a Bobina do Relé. Ou seja, aplique a tensão entre "JD-VCC" e o "GND", e controle o "ON/OFF" do Relé através de um circuito isolado acionado pelo Arduino.
Logo, podemos completar a conclusão nesta "configuração": podemos escolher o Nivel Lógico que acionará o Relé, e ainda temos isolação elétrica entre o circuito do Relé e o Arduino. No entanto note que precisamos de uma Fonte "separada" para alimentarmos a Bobina do relé, e a tensão dessa Fonte será a mesma da Bobina do Relé, o que nos permite usar Relés com diferentes tensões da Bobina (sem estar limitado aos 5V do Arduino).
2) "JD-VCC" conectado ao "VCC": a primeira implicação óbvia, é que automaticamente já não temos mais a isolação elétrica entre os dois circuitos. A segunda, é que a tensão elétrica do sinal "VCC" será a mesma do sinal "JD-VCC". Mas como vimos, para acionarmos a Bobina do Relé, a tensão em "JD-VCC" deve ser em relação ao sinal "GND" da plaquinha. Então por uma questão de simplificar o controle, é conveniente que o sinal "GND" seja também conectado ao "GND" do Arduino, pois assim tanto o "VCC" (que está agora ligado ao "JD-VCC") como o "IN1" terão como referência uma mesma tensão (o GND do Arduino). E se a Bobina do Relé for de 5V, então parece conveniente também ligar o "VCC/JD-VCC" ao 5V do Arduino. E por consequência, obrigatoriamente o "IN1" deverá ser "LOW" para acionar o Relé (como vimos no item "1"), e não há outra possibilidade, uma vez que o "VCC" está ligado ao "JD-VCC" e este último tem que ser a alimentação "positiva" para a Bobina do Relé.
Conclusão: além de perdermos a isolação entre os dois Circuitos (Arduino e acionamento da Bobina do Relé), também não temos escolha em relação do Nível Lógico que liga o Relé, que agora deve ser obrigatoriamente "LOW". Qual a vantagem então? apenas uma: vc não precisa de uma Fonte "separada" para acionar a Bobina do relé, pois está aproveitando o 5V do Arduino para fazer isso, mas claro que agora também terá obrigatoriamente que usar Relés com Bobina de 5V (mas isto pode ser contornado se vc ao invés de ligar o "IN1" ao Arduino, usar um Transistor entre o Arduino e o "IN1").
E claro: a corrente "puxada" pela Bobina do Relé, será drenada do 5V do Arduino. Para a maioria dos Relés de 5V nas plaquinas, essa corrente é tipicamente de 70mA, ou seja: é significativa, e se vc tem daqueles módulos com dois Relés ou mais, cada Bobina irá puxar esta corrente do 5V do Arduino. Para um módulo com 4 Relés, a corrente "puxada" será 4x70mA = 280mA.
Devido a essa corrente "alta" para módulos com vários Relés, é comum que o "pessoal" use uma Fonte externa para o 5V do Arduino. Essa fonte alimentará o 5V do Arduino diretamente, e proverá a corrente necessária para as Bobinas dos diversos Relés no Sistema. Mas existe um problema que precisa ser considerado: não temos mais a isolação entre o Arduino e os circuitos da Bobina do Relé. Ok, mas qual o problema disso? Veja: devido à forma física como o Relé é construído, existe fácil acoplamento magnético entre a Bobina e os Contatos do Relé (óbvio né, afinal é o campo magnético da Bobina que fecha os Contatos do Relé). Assim, se algum "spike" de tensão ou corrente ocorre entre os contatos do Relé, devido ao acoplamento magnético estes "spikes" também induzem tensão nos terminais da Bobina, chegando portanto ao Arduino (já que agora não mais existe mais a isolação). Na maioria das vezes estes skipes não chegam a serem fortes o suficiente para danificar os circuitos do Arduino (embora isso seja possível), mas são fortes o suficiente para alterar Níveis Lógicos "estáticos" nos pinos do Arduino (não entrarei em detalhe de como ocorre isso, porque é muito técnico e exige muitos detalhes em conhecimento de circuitos de comutação). Então é comum que os próprios pinos do Arduino que acionam os Relés, mudem de estado, gerando acionamentos/desligamentos imprevistos dos Relés. Inclusive se o "spike" ocorre em um Relé, pode afetar qualquer outro pino do Arduino mesmo que este pino não seja usado para o controle de um Relé. Pode ser tão "bravo", que o próprio Processador do Arduino "se perde" na execução do código, sendo comum inclusive que sofra um Reset.
Devido às possibilidades de configuração da plaquinha do Relé, existem também uma série de outras consequências, mas não vou adentrar para não demorar mais no texto.
================================================================
No caso dessa sua placa de 8 relés, acho que vc já percebeu que o Fabricante fixou definitivamente o "JD-VCC" ao "VCC". Logo, a primeira consequência disto é que não há mais isolação entre o Arduino e os circuitos das Bobinas dos Relés. E porque isto foi feito? Ocorre que a maioria esmagadora das pessoas usa esses módulos de Relés com o "JD-VCC" sempre conectado ao "VCC" (ou seja, sem isolação), e por isso o Fabricante tomou a decisão de fazer essa conexão permanente no traçado do circuito. Claro, devido a esta falta da isolação, vc precisa ficar atento aos "spikes" que possam ocorrer nos contatos do Relé. Sobre isto, pesquise sobre "Snubbers" que são circuitos simples usados para minimizar a intensidade e portanto o efeito dos "spikes" (e assim impedir os problemas que descrevi sobre estes).
Mas vc deve se lembrar que quando o "JD-VCC" é conectado ao "VCC", não temos a opção de escolher o Nível Lógico que aciona os Relés. Ocorre que pra não perder esse "feature", o Fabricante da placa mudou ligeiramente o circuito que aciona o LED do Opto-Acoplador, de uma forma que agora vc consegue selecionar para cada Relé, qual o Nível Lógico de acionamento. E isto é feito naqueles pontos com "jumpers" selecionando "HIGH" ou "LOW", existentes na Placa. Apenas isso. Assim respondendo à sua última pergunta: isto não tem relação com "sobrecarga" no Arduino. É apenas uma seleção da lógica de acionamento.
E porque só existe o "DC+" e o "DC-" ? Simples: o "DC+" é o "JD-VCC" permanentemente conectado ao "VCC", e o "DC-" é o "GND" que existia na plaquinha que eu descrevi. Então obviamente, é obrigatório para esta placa que este "DC-" esteja ligado ao GND do Arduino. Mas existe um "truque" aqui: se vc tiver uma fonte de 5V alimentando diretamente o Arduino, pode minimizar os problemas de "spike", ligando o "DC+" com um fio indo diretamente ao 5V da Fonte (ou seja: sem "puxar" esta ligação da placa do Arduino). E para o "DC-" dois fios devem ser ligados: um indo diretamente ao GND da Fonte (igual feito para o "DC+", e com a mesma bitola deste), e um segundo fio (este pode ser mais fino) ligado entre o "DC-" e o GND do Arduino. Veja: isso não elimina os "spikes", mas minimiza os efeitos destes.
Como vc deve imaginar pelas explanações que postei aqui, o que vc descreveu sobre o comportamento ON/OFF da sua placa de 8 Relés, é absolutamente o que seria esperado.
Bem não entendi o que vc quis dizer sobre "ponte". Posso tentar imaginar que seria ligar direto com fio (sem usar os "jumpers") para fazer a seleção "HIGH" ou "LOW" para cada Rele. Mas não sei se é isto que vc quis dizer. Se for isso, então vc pode sim fazer a tal "ponte".
Mas talvez vc esteja se referindo a uma "ponte de relés" para acionar Motores, para controle ON/OFF e da direção da rotação. Se for isso, vc pode também fazer sem problemas (mas não se esqueça de pesquisar sobre os "Snubbers", pois pode precisar deles).
Espero ter ajudado.
Abrçs,
Elcids…
Adicionado por Elcids Chagas ao 9:47 em 20 março 2020
e-se garantir que os Níveis Lógicos sejam corretamente interpretados em cada ponta do cabo, e isso se refere tanto aos níveis de tensão, quanto às transições dos sinais digitais de LOW pra HIGH e de HIGH pra LOW (onde os tempos são chamados de "rise time" e "fall time" respectivamente) e os tempos mínimos que os sinais ficam em "LOW" ou em "HIGH".
No caso da Interface "One Wire", especificamente no caso Sensor DS18B20, isto pode ser resumido na figura mostrada a seguir que capturei do datasheet do sensor:
(clique na figura para "zoom")
O ponto fundamental a observar nesta figura, são os tempos de "slot", que nada mais são do que intervalos de tempo usados para sincronizar e garantir a correta interpretação dos bits usados na comunicação entre o DS18B20 e quem está se comunicando com sensor. E como se vê na figura anterior, são uma série de parâmetros, a maioria da ordem de algumas dezenas de micro-segundos.
Na figura anterior, a parte marcada na área na cor azul, é quando o estamos enviando um comando para o DS18B20. Já na área marcada na cor verde, é quando estamos lendo os dados fornecidos pelo DS18B20.
Especificamente, quando se está lendo os dados do DS18B20, o datasheet do mesmo faz algumas considerações a serem seguidas, e isto pode ser visto na figura a seguir:
(clique na figura para "zoom")
Na figura anterior, os pontos mais críticos eu marquei em rosa e em amarelo (são intervalos de tempo). Basicamente, a responsabilidade de seguir estas especificações, é da Biblioteca que vc está usando. Então acredita-se que esta Biblioteca siga isto corretamente. Mas como eu disse no início, os sinais digitais em cabos de longa distância, podem se degradar, tanto em termos de tensão quanto em termos de tempo, e aí reside a maioria dos problemas, pois é muito comum que as áreas que marquei em rosa e amarelo, se degradem causando a interpretação errada dos bits.
A recomendação, está em uma página do datasheet do DS18B20 que eu marquei na cor amarela mostrado na figura a seguir:
(clique na figura para "zoom")
Caso vc queira saber mais sobre as temporizações mostradas nas figuras, basta perguntar aqui no tópico.
2) segundo, deve-se prover a proteção adequada para os circuitos, em cada ponta do cabo. Ocorre que devido ao grande comprimento do cabo, aparecem "spikes" de tensão nas pontas (e claro, ao longo do cabo também). Estes "spikes" podem ter amplitudes muito maiores que as tensões de trabalho dos circuitos que estão conectados às pontas dos cabos. Por exemplo, se os sinais digitais usados são de 5V, facilmente podem aparecerem "spikes" de mais de 50V nas pontas. E aí já viu, né? Mesmo que estes "spikes" durem alguns micro-segundos ou nano-segundos, eles podem ser suficientes para danificar permanentemente os circuitos conectados às pontas dos cabos (e isto é bem fácil de ocorrer). Às vezes os "spikes" não danificam os circuitos de imediato, mas já viu o ditado: "água mole em pedra dura, tanto bate até que fura" (e acredite, neste caso ocorre mesmo um "furo" no componente eletrônico, claro, um furo microscópico que só é possível ver através de processos e instrumentos adequados).
Assim, para seu caso, proceda como no diagrama mostrado na figura a seguir:
(clique na figura para "zoom")
Vou fazer algumas considerações sobre alguns pontos desse circuito:
1) a tensão de alimentação enviada pelo cabo, é de 5V. Como vc está usando o ESP32, a melhor forma de se obter essa tensão, vai depender de como vc está alimentado a placa do ESP32. Por exemplo: se vc está alimentando via conector USB, então vc pode obter essa tensão a partir do pino "VIN" da plaquinha do ESP, pois embora a tensão ali presente seja um pouco menor que 5V (será algo próximo a 4.7V), ainda assim está dentro de uma margem aceitável para o Sistema. Mas se vc estiver alimentando a plaquinha do ESP justamente pelo pino "VIN", e esta tensão for de 5V, então pode usar este pino da mesma forma.
Observe no diagrama, que a tensão de alimentação, é enviada através de um "par trançado" do cabo CAT5 (no diagrama, é o par "laranja - branco/laranja"). O uso do "par trançado" para enviar a alimentação, é essencial, pois diversos ruídos de alta frequência serão filtrados com isso. Mas cuidado para não trocar o 5V com o GND em cada ponta do cabo (senão já sabe o que ocorre).
2) na região marcada com linha tracejada na cor azul, que fica do lado do ESP32, está o circuito que filtra o sinal digital "vindo" do DS18B20, e protege o pino do ESP32 contra "spikes" de alta-tensão existentes no cabo (que aparecem quando os sinais digitais mudam de estado). Este circuito não interfere quando é o ESP32 quem envia bits para o DS18B20. A proteção, é implementada tanto com o Resistor R1, quanto pelos Diodos D1 e D2. Observe que o Katodo de D1 está ligado aos 3.3V, ou seja, ligado à tensão de alimentação do ESP32.
O Resistor R2 (de valor 2k2), faz o papel do pullpup do Barramento "One Wire", mas do lado do ESP32. Ele é essencial para o funcionamento correto do Sistema (e haverá outro, já no lado do Sensor).
Observe que como marcado na figura, esta região em azul tracejado, deverá ficar o mais próxima possível da plaquinha do ESP32 (evite passar de 30 centímetros).
E veja que para o sinal de comunicação, é usado um fio simples (no diagrama, é o fio "branco/verde"). Ou seja: não use um "par trançado" com as pontas curto-circuitadas. Isso é para não aumentar a Capacitância entre este sinal e os demais fios (Alimentação e Blindagem). Assim escolha um fio e use apenas ele para o sinal de comunicação do "One Wire".
3) dentro da região tracejada na cor verde no lado do Sensor, temos uma área também na cor verde, onde estão os componentes R3, C2, e C3. Estes constituem um filtro para a alimentação do DS18B20. Este filtro é fundamental. Nele, R3 e C2 filtram oscilações de baixa frequência, enquanto que R3 e C3 filtram as de alta-frequência. Sem este filtro, não é possível garantir a estabilidade primária do sinal digital fornecido pelo DS18B20, quando estamos lendo os dados do mesmo.
Atenção à polaridade do Capacitor Eletrolítico C2 (o positivo está marcado na figura com um "+" na cor vermelha). A tensão de trabalho desse Capacitor deve ser acima de 16V, mas evite que seja muito alta (até 100V está de bom tamanho).
4) sobre R4, C4, D3, e D4, estes fazem a mesma coisa que aqueles componentes de mesmo valor fazem no lado do ESP32. Então não é preciso dizer mais nada sobre eles. Apenas notar que aqui, o Katodo de D3 está ligado aos 5V (no lado do ESP, o Katodo de D1 vai aos 3.3V).
E o Resistor R5 faz o mesmo que R2 faz do lado do ESP32. E estes dois Resistores são fundamentais.
5) notar que todo o circuito dentro da região tracejada na cor verde, deve ficar o mais próximo possível do Sensor DS18B20. Por isso, se possível, corte o cabo original que vem com o Sensor de forma a reduzir a distância em questão. Se a princípio vc não quiser fazer isso, tudo bem, pode manter o cabo do Sensor com o comprimento original, mas claro, se vc manter o cabo assim, faça vários testes pra ver se está tudo funcionando corretamente (ou seja, se o comprimento do cabo original não está afetando o funcionamento do Sistema) e de forma estável.
6) se o cabo CAT5 tiver malha de blindagem, NÃO ligue esta blindagem. Deixe desconectada nas duas extremidades do cabo. Embora a blindagem possa até eliminar algumas interferências, este benefício não compensa neste Sistema. Isto porque se vc ligar a blindagem ao GND, irá aumentar a Capacitância distribuída no cabo, especificamente entre o sinal do fio de comunicação e o GND, o que será péssimo para aquele sinal. Não queremos que esta Capacitância aumente, porque ela degrada o sinal de comunicação. Então deixe desligada a blindagem.
7) se existir um TERRA (da instalação da rede elétrica) no lado do DS18B20, não ligue este TERRA ao GND do circuito do DS18B20. Este tipo de ligação, tem alguns benefícios, mas não é necessário no seu Sistema, pois há também "malefícios" e por isso a princípio não compensa.
8) finalmente, atente para os tipos de alguns componentes. Especificamente, C1, C3, e C4, devem ser obrigatoriamente do tipo "Cerâmico" (NÃO serve de poliéster metalizado !!!). Atenção aos valores de C1 e C4, que são de 100pF (100 pico Farad). Não aumente além disso. Já C3 pode ter outro valor, desde que maior que 100nF (100 nano Farad, ou 100 kpF).
Já os Diodos D1 a D4, use preferencialmente do tipo "Schottky" (pode ser BAT85, BAT82, ou similares). Mas pode tentar também com o famigerado 1N4148. Já os 1N400x (os 1N4001, 1N4002, 1N4007, etc), nem pensar, pois são inadequados para esta aplicação.
Para detalhes do circuito, use o PDF que imprimi: "DS18B20_longa_distancia_01.pdf"
Ah sim: se possível use no seu código, o método de "leitura assíncrona" para o DS18B20. É muito mais eficiente em termos de tempo de processamento, e praticamente não bloqueia seu código. Mas isso é apenas uma recomendação. Se tiver alguma dúvida de como implementar isso, pergunte aqui (mas há exemplo na própria Biblioteca do Sensor).
Espero ter ajudado.
Abrçs,
Elcids…
Adicionado por Elcids Chagas ao 12:12 em 17 julho 2021
nosso caso é modbus serial, antes carregue esse sketch abaixo no seu arduino pegue um led e coloque na porta 3 do arduino a porta digital, dando tudo certo vc vai lá no watch list e marca 1 ou zero o o seu led vai acender e apagar via modbus serial é bem legal do resto é só configurar outros sensores que deve funcionar,
/* Modbus serial - RTU Slave Arduino Sketch Marcos Daniel Wiechert wiechertdaniel@yahoo.com.br Baseado na biblioteca de Juan Pablo Zometa : jpmzometa@gmail.com http://sites.google.com/site/jpmzometa/ and Samuel Marco: sammarcoarmengol@gmail.com and Andras Tucsni. As funções do protocolo MODBUS implementadas neste código: 3 - Read holding registers; 6 - Preset single register; 16 - Preset multiple registers. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The functions included here have been derived from the Modicon Modbus Protocol Reference Guide which can be obtained from Schneider at www.schneiderautomation.com. This code has its origins with paul@pmcrae.freeserve.co.uk (http://www.pmcrae.freeserve.co.uk) who wrote a small program to read 100 registers from a modbus slave. *//* * configure_mb_slave(baud, parity, tx_en_pin) * * configuração dos parametros da porta serial. * * baud: taxa de transmissão em bps (valores típicos entre 9600, 19200... 115200) * parity: seta o modo de paridade: * 'n' sem paridade (8N1); 'e' paridede impar (8E1), 'o' paridade par (8O1). * tx_en_pin: pino do arduino que controla a transmissão/recepção em uma linha RS485. * 0 or 1 desliga esta função (para rede RS232) * >2 para uma rede multiponto. */void configure_mb_slave(long baud, char parity, char txenpin);/* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * verifica se há qualquer pedido válido do mestre modbus. Se houver, * executa a ação solicitada * * slave: endereço do escravo (arduino) (1 to 127) * regs: uma matriz com os holding registers. Eles começam no endereço 1 (mestre ponto de vista) * Regs_size: número total de holding registers. * Retorna: 0 se não houver pedido do mestre, * NO_REPLY (-1) se nenhuma resposta é enviada para o mestre * Caso um código de exceção (1 a 4) em algumas exceções de modbus * O número de bytes enviados como resposta (> 4) se OK. */int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size);/* Aqui começa o código do exemplo *//* Parâmetros Modbus RTU de comunicação, o Mestre e os escravos devem usar os mesmos parâmetros */enum { COMM_BPS = 9600, /* baud rate */ MB_SLAVE = 1, /* endereço do escravo modbus */ PARITY = 'n' /* paridade */};/* registros do escravo (holding registers)* * Aqui ficam ordenados todos os registros de leitura e escrita* da comunicação entre o mestre e o escravo (SCADA e arduino) * */enum { MB_PINO_3, /* Controle do Led no pino 3 (desliga=0 liga=1) */ MB_REGS /* número total de registros do escravo */};int regs[MB_REGS];int ledPin3 = 3;void setup() { /* configura cominicação modbus * 9600 bps, 8N1, RS485 network */ configure_mb_slave(COMM_BPS, PARITY, 2); pinMode(ledPin3, OUTPUT); }void loop() { /* verifica se há solicitações do mestre */ update_mb_slave(MB_SLAVE, regs, MB_REGS); /* os valores dos registros são definidos pelo mestre modbus (SCADA) */ switch(regs[MB_PINO_3]) { case 1: digitalWrite(ledPin3, HIGH); break; default: /* apagado */ digitalWrite(ledPin3, LOW); } } /**************************************************************************** * INICIO DAS FUNÇÕES ESCRAVO Modbus RTU ****************************************************************************//* variaveis globais */unsigned int Txenpin = 2; /*Definir o pino usado para colocar o driver RS485 em modo de transmissão, utilizado somente em redes RS485 quando colocar em 0 ou 1 para redes RS232 *//* Lista de códigos de função modbus suportados. Se você implementar um novo, colocar o seu código de função aqui! */enum { FC_READ_REGS = 0x03, //Read contiguous block of holding register (Ler um bloco contíguo de registos) FC_WRITE_REG = 0x06, //Write single holding register (Escrever em um único registro) FC_WRITE_REGS = 0x10 //Write block of contiguous registers (Escrever em um bloco contíguo de registos)};/* Funções suportadas. Se você implementar um novo, colocar seu código em função nessa matriz! */const unsigned char fsupported[] = { FC_READ_REGS, FC_WRITE_REG, FC_WRITE_REGS };/* constantes */enum { MAX_READ_REGS = 0x7D, MAX_WRITE_REGS = 0x7B, MAX_MESSAGE_LENGTH = 256 };enum { RESPONSE_SIZE = 6, EXCEPTION_SIZE = 3, CHECKSUM_SIZE = 2 };/* código de exceções */enum { NO_REPLY = -1, EXC_FUNC_CODE = 1, EXC_ADDR_RANGE = 2, EXC_REGS_QUANT = 3, EXC_EXECUTE = 4 };/* posições dentro da matriz de consulta / resposta */enum { SLAVE = 0, FUNC, START_H, START_L, REGS_H, REGS_L, BYTE_CNT };/*CRC INPUTS: buf -> Matriz contendo a mensagem a ser enviada para o controlador mestre. start -> Início do loop no crc do contador, normalmente 0. cnt -> Quantidade de bytes na mensagem a ser enviada para o controlador mestre OUTPUTS: temp -> Retorna byte crc para a mensagem. COMMENTÁRIOS: Esta rotina calcula o byte crc alto e baixo de uma mensagem. Note que este CRC é usado somente para Modbus, não em Modbus PLUS ou TCP. ****************************************************************************/unsigned int crc(unsigned char *buf, unsigned char start,unsigned char cnt) { unsigned char i, j; unsigned temp, temp2, flag; temp = 0xFFFF; for (i = start; i < cnt; i++) { temp = temp ^ buf[i]; for (j = 1; j <= 8; j++) { flag = temp & 0x0001; temp = temp >> 1; if (flag) temp = temp ^ 0xA001; } } /* Inverter a ordem dos bytes. */ temp2 = temp >> 8; temp = (temp << 8) | temp2; temp &= 0xFFFF; return (temp);}/*********************************************************************** * * As seguintes funções constroem o frame de * um pacote de consulta modbus. * ***********************************************************************//* * Início do pacote de uma resposta read_holding_register */void build_read_packet(unsigned char slave, unsigned char function,unsigned char count, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[2] = count * 2;} /* * Início do pacote de uma resposta preset_multiple_register */void build_write_packet(unsigned char slave, unsigned char function,unsigned int start_addr, unsigned char count,unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = start_addr >> 8; packet[START_L] = start_addr & 0x00ff; packet[REGS_H] = 0x00; packet[REGS_L] = count;} /* * Início do pacote de uma resposta write_single_register */void build_write_single_packet(unsigned char slave, unsigned char function, unsigned int write_addr, unsigned int reg_val, unsigned char* packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = write_addr >> 8; packet[START_L] = write_addr & 0x00ff; packet[REGS_H] = reg_val >> 8; packet[REGS_L] = reg_val & 0x00ff;} /* * Início do pacote de uma resposta excepção */void build_error_packet(unsigned char slave, unsigned char function,unsigned char exception, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function + 0x80; packet[2] = exception;} /************************************************************************* * * modbus_query( packet, length) * * Função para adicionar uma soma de verificação para o fim de um pacote. * Por favor, note que a matriz pacote deve ser de pelo menos 2 campos mais do que * String_length. **************************************************************************/void modbus_reply(unsigned char *packet, unsigned char string_length) { int temp_crc; temp_crc = crc(packet, 0, string_length); packet[string_length] = temp_crc >> 8; string_length++; packet[string_length] = temp_crc & 0x00FF;} /*********************************************************************** * * send_reply( query_string, query_length ) * * Função para enviar uma resposta a um mestre Modbus. * Retorna: o número total de caracteres enviados ************************************************************************/int send_reply(unsigned char *query, unsigned char string_length) { unsigned char i; if (Txenpin > 1) { // coloca o MAX485 no modo de transmissão UCSR0A=UCSR0A |(1 << TXC0); digitalWrite( Txenpin, HIGH); delayMicroseconds(3640); // aguarda silencio de 3.5 caracteres em 9600bps } modbus_reply(query, string_length); string_length += 2; for (i = 0; i < string_length; i++) { Serial.print(query[i], BYTE); } if (Txenpin > 1) {// coloca o MAX485 no modo de recepção while (!(UCSR0A & (1 << TXC0))); digitalWrite( Txenpin, LOW); } return i; /* isso não significa que a gravação foi bem sucedida */}/*********************************************************************** * * receive_request( array_for_data ) * * Função para monitorar um pedido do mestre modbus. * * Retorna: Número total de caracteres recebidos se OK * 0 se não houver nenhum pedido * Um código de erro negativo em caso de falha ***********************************************************************/int receive_request(unsigned char *received_string) { int bytes_received = 0; /* FIXME: não Serial.available esperar 1.5T ou 3.5T antes de sair do loop? */ while (Serial.available()) { received_string[bytes_received] = Serial.read(); bytes_received++; if (bytes_received >= MAX_MESSAGE_LENGTH) return NO_REPLY; /* erro de porta */ } return (bytes_received);}/********************************************************************* * * modbus_request(slave_id, request_data_array) * * Função que é retornada quando o pedido está correto * e a soma de verificação está correto. * Retorna: string_length se OK * 0 se não * Menos de 0 para erros de exceção * * Nota: Todas as funções usadas para enviar ou receber dados via * Modbus devolver esses valores de retorno. * **********************************************************************/int modbus_request(unsigned char slave, unsigned char *data) { int response_length; unsigned int crc_calc = 0; unsigned int crc_received = 0; unsigned char recv_crc_hi; unsigned char recv_crc_lo; response_length = receive_request(data); if (response_length > 0) { crc_calc = crc(data, 0, response_length - 2); recv_crc_hi = (unsigned) data[response_length - 2]; recv_crc_lo = (unsigned) data[response_length - 1]; crc_received = data[response_length - 2]; crc_received = (unsigned) crc_received << 8; crc_received = crc_received | (unsigned) data[response_length - 1]; /*********** verificar CRC da resposta ************/ if (crc_calc != crc_received) { return NO_REPLY; } /* verificar a ID do escravo */ if (slave != data[SLAVE]) { return NO_REPLY; } } return (response_length);}/********************************************************************* * * validate_request(request_data_array, request_length, available_regs) * * Função para verificar se o pedido pode ser processado pelo escravo. * * Retorna: 0 se OK * Um código de exceção negativa em caso de erro * **********************************************************************/int validate_request(unsigned char *data, unsigned char length,unsigned int regs_size) { int i, fcnt = 0; unsigned int regs_num = 0; unsigned int start_addr = 0; unsigned char max_regs_num; /* verificar o código de função */ for (i = 0; i < sizeof(fsupported); i++) { if (fsupported[i] == data[FUNC]) { fcnt = 1; break; } } if (0 == fcnt) return EXC_FUNC_CODE; if (FC_WRITE_REG == data[FUNC]) { /* Para a função de escrever um reg único, este é o registro alvo.*/ regs_num = ((int) data[START_H] << 8) + (int) data[START_L]; if (regs_num >= regs_size) return EXC_ADDR_RANGE; return 0; } /* Para as funções de leitura / escrita de registros, este é o intervalo. */ regs_num = ((int) data[REGS_H] << 8) + (int) data[REGS_L]; /* verifica a quantidade de registros */ if (FC_READ_REGS == data[FUNC]) max_regs_num = MAX_READ_REGS; else if (FC_WRITE_REGS == data[FUNC]) max_regs_num = MAX_WRITE_REGS; if ((regs_num < 1) || (regs_num > max_regs_num)) return EXC_REGS_QUANT; /* verificará a quantidade de registros, endereço inicial é 0 */ start_addr = ((int) data[START_H] << 8) + (int) data[START_L]; if ((start_addr + regs_num) > regs_size) return EXC_ADDR_RANGE; return 0; /* OK, sem exceção */}/************************************************************************ * * write_regs(first_register, data_array, registers_array) * * escreve nos registradores do escravo os dados em consulta, * A partir de start_addr. * * Retorna: o número de registros escritos ************************************************************************/int write_regs(unsigned int start_addr, unsigned char *query, int *regs) { int temp; unsigned int i; for (i = 0; i < query[REGS_L]; i++) { /* mudar reg hi_byte para temp */ temp = (int) query[(BYTE_CNT + 1) + i * 2] << 8; /* OR com lo_byte */ temp = temp | (int) query[(BYTE_CNT + 2) + i * 2]; regs[start_addr + i] = temp; } return i;}/************************************************************************ * * preset_multiple_registers(slave_id, first_register, number_of_registers, * data_array, registers_array) * * Escreva os dados na matriz dos registos do escravo. * *************************************************************************/int preset_multiple_registers(unsigned char slave,unsigned int start_addr,unsigned char count, unsigned char *query,int *regs) { unsigned char function = FC_WRITE_REGS; /* Escrever em múltiplos registros */ int status = 0; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE]; build_write_packet(slave, function, start_addr, count, packet); if (write_regs(start_addr, query, regs)) { status = send_reply(packet, RESPONSE_SIZE); } return (status);}/************************************************************************ * * write_single_register(slave_id, write_addr, data_array, registers_array) * * Escrever um único valor inteiro em um único registo do escravo. * *************************************************************************/int write_single_register(unsigned char slave, unsigned int write_addr, unsigned char *query, int *regs) { unsigned char function = FC_WRITE_REG; /* Função: Write Single Register */ int status = 0; unsigned int reg_val; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE]; reg_val = query[REGS_H] << 8 | query[REGS_L]; build_write_single_packet(slave, function, write_addr, reg_val, packet); regs[write_addr] = (int) reg_val;/* written.start_addr=write_addr; written.num_regs=1;*/ status = send_reply(packet, RESPONSE_SIZE); return (status);}/************************************************************************ * * read_holding_registers(slave_id, first_register, number_of_registers, * registers_array) * * lê os registros do escravo e envia para o mestre Modbus * *************************************************************************/int read_holding_registers(unsigned char slave, unsigned int start_addr,unsigned char reg_count, int *regs) { unsigned char function = 0x03; /* Função 03: Read Holding Registers */ int packet_size = 3; int status; unsigned int i; unsigned char packet[MAX_MESSAGE_LENGTH]; build_read_packet(slave, function, reg_count, packet); for (i = start_addr; i < (start_addr + (unsigned int) reg_count); i++) { packet[packet_size] = regs[i] >> 8; packet_size++; packet[packet_size] = regs[i] & 0x00FF; packet_size++; } status = send_reply(packet, packet_size); return (status);}void configure_mb_slave(long baud, char parity, char txenpin){ Serial.begin(baud); switch (parity) { case 'e': // 8E1 UCSR0C |= ((1<<UPM01) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; case 'o': // 8O1 UCSR0C |= ((1<<UPM01) | (1<<UPM00) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UCSZ02) | (1<<USBS0)); break; case 'n': // 8N1 UCSR0C |= ((1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM01) | (1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; default: break; } if (txenpin > 1) { // pino 0 & pino 1 são reservados para RX/TX Txenpin = txenpin; /* definir variável global */ pinMode(Txenpin, OUTPUT); digitalWrite(Txenpin, LOW); } return;} /* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * verifica se há qualquer pedido válido do mestre modbus. Se houver, * executa a ação solicitada */unsigned long Nowdt = 0;unsigned int lastBytesReceived;const unsigned long T35 = 5;int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size) { unsigned char query[MAX_MESSAGE_LENGTH]; unsigned char errpacket[EXCEPTION_SIZE + CHECKSUM_SIZE]; unsigned int start_addr; int exception; int length = Serial.available(); unsigned long now = millis(); if (length == 0) { lastBytesReceived = 0; return 0; } if (lastBytesReceived != length) { lastBytesReceived = length; Nowdt = now + T35; return 0; } if (now < Nowdt) return 0; lastBytesReceived = 0; length = modbus_request(slave, query); if (length < 1) return length; exception = validate_request(query, length, regs_size); if (exception) { build_error_packet(slave, query[FUNC], exception, errpacket); send_reply(errpacket, EXCEPTION_SIZE); return (exception); } start_addr = ((int) query[START_H] << 8) + (int) query[START_L]; switch (query[FUNC]) { case FC_READ_REGS: return read_holding_registers(slave, start_addr, query[REGS_L], regs); break; case FC_WRITE_REGS: return preset_multiple_registers(slave, start_addr, query[REGS_L], query, regs); break; case FC_WRITE_REG: write_single_register(slave, start_addr, query, regs); break; }}…
int, os dois leds ficam piscando freneticamene, eu usei o seguinte codigo:
/* Modbus serial - RTU Slave Arduino Sketch Marcos Daniel Wiechert wiechertdaniel@yahoo.com.br Baseado na biblioteca de Juan Pablo Zometa : jpmzometa@gmail.com http://sites.google.com/site/jpmzometa/ and Samuel Marco: sammarcoarmengol@gmail.com and Andras Tucsni. As funções do protocolo MODBUS implementadas neste código: 3 - Read holding registers; 6 - Preset single register; 16 - Preset multiple registers. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The functions included here have been derived from the Modicon Modbus Protocol Reference Guide which can be obtained from Schneider at www.schneiderautomation.com. This code has its origins with paul@pmcrae.freeserve.co.uk (http://www.pmcrae.freeserve.co.uk) who wrote a small program to read 100 registers from a modbus slave. *//* * configure_mb_slave(baud, parity, tx_en_pin) * * configuração dos parametros da porta serial. * * baud: taxa de transmissão em bps (valores típicos entre 9600, 19200... 115200) * parity: seta o modo de paridade: * 'n' sem paridade (8N1); 'e' paridede impar (8E1), 'o' paridade par (8O1). * tx_en_pin: pino do arduino que controla a transmissão/recepção em uma linha RS485. * 0 or 1 desliga esta função (para rede RS232) * >2 para uma rede multiponto. */void configure_mb_slave(long baud, char parity, char txenpin);/* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * verifica se há qualquer pedido válido do mestre modbus. Se houver, * executa a ação solicitada * * slave: endereço do escravo (arduino) (1 to 127) * regs: uma matriz com os holding registers. Eles começam no endereço 1 (mestre ponto de vista) * Regs_size: número total de holding registers. * Retorna: 0 se não houver pedido do mestre, * NO_REPLY (-1) se nenhuma resposta é enviada para o mestre * Caso um código de exceção (1 a 4) em algumas exceções de modbus * O número de bytes enviados como resposta (> 4) se OK. */int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size);/* Aqui começa o código do exemplo *//* Parâmetros Modbus RTU de comunicação, o Mestre e os escravos devem usar os mesmos parâmetros */enum { COMM_BPS = 9600, /* baud rate */ MB_SLAVE = 1, /* endereço do escravo modbus */ PARITY = 'n' /* paridade */};/* registros do escravo (holding registers)* * Aqui ficam ordenados todos os registros de leitura e escrita* da comunicação entre o mestre e o escravo (SCADA e arduino) * */enum { MB_PINO_3, /* Controle do Led no pino 3 (desliga=0 liga=1) */ MB_REGS /* número total de registros do escravo */};int regs[MB_REGS];int ledPin3 = 3;void setup() { /* configura cominicação modbus * 9600 bps, 8N1, RS485 network */ configure_mb_slave(COMM_BPS, PARITY, 2); pinMode(ledPin3, OUTPUT); }void loop() { /* verifica se há solicitações do mestre */ update_mb_slave(MB_SLAVE, regs, MB_REGS); /* os valores dos registros são definidos pelo mestre modbus (SCADA) */ switch(regs[MB_PINO_3]) { case 1: digitalWrite(ledPin3, HIGH); break; default: /* apagado */ digitalWrite(ledPin3, LOW); } } /**************************************************************************** * INICIO DAS FUNÇÕES ESCRAVO Modbus RTU ****************************************************************************//* variaveis globais */unsigned int Txenpin = 2; /*Definir o pino usado para colocar o driver RS485 em modo de transmissão, utilizado somente em redes RS485 quando colocar em 0 ou 1 para redes RS232 *//* Lista de códigos de função modbus suportados. Se você implementar um novo, colocar o seu código de função aqui! */enum { FC_READ_REGS = 0x03, //Read contiguous block of holding register (Ler um bloco contíguo de registos) FC_WRITE_REG = 0x06, //Write single holding register (Escrever em um único registro) FC_WRITE_REGS = 0x10 //Write block of contiguous registers (Escrever em um bloco contíguo de registos)};/* Funções suportadas. Se você implementar um novo, colocar seu código em função nessa matriz! */const unsigned char fsupported[] = { FC_READ_REGS, FC_WRITE_REG, FC_WRITE_REGS };/* constantes */enum { MAX_READ_REGS = 0x7D, MAX_WRITE_REGS = 0x7B, MAX_MESSAGE_LENGTH = 256 };enum { RESPONSE_SIZE = 6, EXCEPTION_SIZE = 3, CHECKSUM_SIZE = 2 };/* código de exceções */enum { NO_REPLY = -1, EXC_FUNC_CODE = 1, EXC_ADDR_RANGE = 2, EXC_REGS_QUANT = 3, EXC_EXECUTE = 4 };/* posições dentro da matriz de consulta / resposta */enum { SLAVE = 0, FUNC, START_H, START_L, REGS_H, REGS_L, BYTE_CNT };/*CRC INPUTS: buf -> Matriz contendo a mensagem a ser enviada para o controlador mestre. start -> Início do loop no crc do contador, normalmente 0. cnt -> Quantidade de bytes na mensagem a ser enviada para o controlador mestre OUTPUTS: temp -> Retorna byte crc para a mensagem. COMMENTÁRIOS: Esta rotina calcula o byte crc alto e baixo de uma mensagem. Note que este CRC é usado somente para Modbus, não em Modbus PLUS ou TCP. ****************************************************************************/unsigned int crc(unsigned char *buf, unsigned char start,unsigned char cnt) { unsigned char i, j; unsigned temp, temp2, flag; temp = 0xFFFF; for (i = start; i < cnt; i++) { temp = temp ^ buf[i]; for (j = 1; j <= 8; j++) { flag = temp & 0x0001; temp = temp >> 1; if (flag) temp = temp ^ 0xA001; } } /* Inverter a ordem dos bytes. */ temp2 = temp >> 8; temp = (temp << 8) | temp2; temp &= 0xFFFF; return (temp);}/*********************************************************************** * * As seguintes funções constroem o frame de * um pacote de consulta modbus. * ***********************************************************************//* * Início do pacote de uma resposta read_holding_register */void build_read_packet(unsigned char slave, unsigned char function,unsigned char count, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[2] = count * 2;} /* * Início do pacote de uma resposta preset_multiple_register */void build_write_packet(unsigned char slave, unsigned char function,unsigned int start_addr, unsigned char count,unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = start_addr >> 8; packet[START_L] = start_addr & 0x00ff; packet[REGS_H] = 0x00; packet[REGS_L] = count;} /* * Início do pacote de uma resposta write_single_register */void build_write_single_packet(unsigned char slave, unsigned char function, unsigned int write_addr, unsigned int reg_val, unsigned char* packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = write_addr >> 8; packet[START_L] = write_addr & 0x00ff; packet[REGS_H] = reg_val >> 8; packet[REGS_L] = reg_val & 0x00ff;} /* * Início do pacote de uma resposta excepção */void build_error_packet(unsigned char slave, unsigned char function,unsigned char exception, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function + 0x80; packet[2] = exception;} /************************************************************************* * * modbus_query( packet, length) * * Função para adicionar uma soma de verificação para o fim de um pacote. * Por favor, note que a matriz pacote deve ser de pelo menos 2 campos mais do que * String_length. **************************************************************************/void modbus_reply(unsigned char *packet, unsigned char string_length) { int temp_crc; temp_crc = crc(packet, 0, string_length); packet[string_length] = temp_crc >> 8; string_length++; packet[string_length] = temp_crc & 0x00FF;} /*********************************************************************** * * send_reply( query_string, query_length ) * * Função para enviar uma resposta a um mestre Modbus. * Retorna: o número total de caracteres enviados ************************************************************************/int send_reply(unsigned char *query, unsigned char string_length) { unsigned char i; if (Txenpin > 1) { // coloca o MAX485 no modo de transmissão UCSR0A=UCSR0A |(1 << TXC0); digitalWrite( Txenpin, HIGH); delayMicroseconds(3640); // aguarda silencio de 3.5 caracteres em 9600bps } modbus_reply(query, string_length); string_length += 2; for (i = 0; i < string_length; i++) { Serial.print(query[i], BYTE); } if (Txenpin > 1) {// coloca o MAX485 no modo de recepção while (!(UCSR0A & (1 << TXC0))); digitalWrite( Txenpin, LOW); } return i; /* isso não significa que a gravação foi bem sucedida */}/*********************************************************************** * * receive_request( array_for_data ) * * Função para monitorar um pedido do mestre modbus. * * Retorna: Número total de caracteres recebidos se OK * 0 se não houver nenhum pedido * Um código de erro negativo em caso de falha ***********************************************************************/int receive_request(unsigned char *received_string) { int bytes_received = 0; /* FIXME: não Serial.available esperar 1.5T ou 3.5T antes de sair do loop? */ while (Serial.available()) { received_string[bytes_received] = Serial.read(); bytes_received++; if (bytes_received >= MAX_MESSAGE_LENGTH) return NO_REPLY; /* erro de porta */ } return (bytes_received);}/********************************************************************* * * modbus_request(slave_id, request_data_array) * * Função que é retornada quando o pedido está correto * e a soma de verificação está correto. * Retorna: string_length se OK * 0 se não * Menos de 0 para erros de exceção * * Nota: Todas as funções usadas para enviar ou receber dados via * Modbus devolver esses valores de retorno. * **********************************************************************/int modbus_request(unsigned char slave, unsigned char *data) { int response_length; unsigned int crc_calc = 0; unsigned int crc_received = 0; unsigned char recv_crc_hi; unsigned char recv_crc_lo; response_length = receive_request(data); if (response_length > 0) { crc_calc = crc(data, 0, response_length - 2); recv_crc_hi = (unsigned) data[response_length - 2]; recv_crc_lo = (unsigned) data[response_length - 1]; crc_received = data[response_length - 2]; crc_received = (unsigned) crc_received << 8; crc_received = crc_received | (unsigned) data[response_length - 1]; /*********** verificar CRC da resposta ************/ if (crc_calc != crc_received) { return NO_REPLY; } /* verificar a ID do escravo */ if (slave != data[SLAVE]) { return NO_REPLY; } } return (response_length);}/********************************************************************* * * validate_request(request_data_array, request_length, available_regs) * * Função para verificar se o pedido pode ser processado pelo escravo. * * Retorna: 0 se OK * Um código de exceção negativa em caso de erro * **********************************************************************/int validate_request(unsigned char *data, unsigned char length,unsigned int regs_size) { int i, fcnt = 0; unsigned int regs_num = 0; unsigned int start_addr = 0; unsigned char max_regs_num; /* verificar o código de função */ for (i = 0; i < sizeof(fsupported); i++) { if (fsupported[i] == data[FUNC]) { fcnt = 1; break; } } if (0 == fcnt) return EXC_FUNC_CODE; if (FC_WRITE_REG == data[FUNC]) { /* Para a função de escrever um reg único, este é o registro alvo.*/ regs_num = ((int) data[START_H] << 8) + (int) data[START_L]; if (regs_num >= regs_size) return EXC_ADDR_RANGE; return 0; } /* Para as funções de leitura / escrita de registros, este é o intervalo. */ regs_num = ((int) data[REGS_H] << 8) + (int) data[REGS_L]; /* verifica a quantidade de registros */ if (FC_READ_REGS == data[FUNC]) max_regs_num = MAX_READ_REGS; else if (FC_WRITE_REGS == data[FUNC]) max_regs_num = MAX_WRITE_REGS; if ((regs_num < 1) || (regs_num > max_regs_num)) return EXC_REGS_QUANT; /* verificará a quantidade de registros, endereço inicial é 0 */ start_addr = ((int) data[START_H] << 8) + (int) data[START_L]; if ((start_addr + regs_num) > regs_size) return EXC_ADDR_RANGE; return 0; /* OK, sem exceção */}/************************************************************************ * * write_regs(first_register, data_array, registers_array) * * escreve nos registradores do escravo os dados em consulta, * A partir de start_addr. * * Retorna: o número de registros escritos ************************************************************************/int write_regs(unsigned int start_addr, unsigned char *query, int *regs) { int temp; unsigned int i; for (i = 0; i < query[REGS_L]; i++) { /* mudar reg hi_byte para temp */ temp = (int) query[(BYTE_CNT + 1) + i * 2] << 8; /* OR com lo_byte */ temp = temp | (int) query[(BYTE_CNT + 2) + i * 2]; regs[start_addr + i] = temp; } return i;}/************************************************************************ * * preset_multiple_registers(slave_id, first_register, number_of_registers, * data_array, registers_array) * * Escreva os dados na matriz dos registos do escravo. * *************************************************************************/int preset_multiple_registers(unsigned char slave,unsigned int start_addr,unsigned char count, unsigned char *query,int *regs) { unsigned char function = FC_WRITE_REGS; /* Escrever em múltiplos registros */ int status = 0; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE]; build_write_packet(slave, function, start_addr, count, packet); if (write_regs(start_addr, query, regs)) { status = send_reply(packet, RESPONSE_SIZE); } return (status);}/************************************************************************ * * write_single_register(slave_id, write_addr, data_array, registers_array) * * Escrever um único valor inteiro em um único registo do escravo. * *************************************************************************/int write_single_register(unsigned char slave, unsigned int write_addr, unsigned char *query, int *regs) { unsigned char function = FC_WRITE_REG; /* Função: Write Single Register */ int status = 0; unsigned int reg_val; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE]; reg_val = query[REGS_H] << 8 | query[REGS_L]; build_write_single_packet(slave, function, write_addr, reg_val, packet); regs[write_addr] = (int) reg_val;/* written.start_addr=write_addr; written.num_regs=1;*/ status = send_reply(packet, RESPONSE_SIZE); return (status);}/************************************************************************ * * read_holding_registers(slave_id, first_register, number_of_registers, * registers_array) * * lê os registros do escravo e envia para o mestre Modbus * *************************************************************************/int read_holding_registers(unsigned char slave, unsigned int start_addr,unsigned char reg_count, int *regs) { unsigned char function = 0x03; /* Função 03: Read Holding Registers */ int packet_size = 3; int status; unsigned int i; unsigned char packet[MAX_MESSAGE_LENGTH]; build_read_packet(slave, function, reg_count, packet); for (i = start_addr; i < (start_addr + (unsigned int) reg_count); i++) { packet[packet_size] = regs[i] >> 8; packet_size++; packet[packet_size] = regs[i] & 0x00FF; packet_size++; } status = send_reply(packet, packet_size); return (status);}void configure_mb_slave(long baud, char parity, char txenpin){ Serial.begin(baud); switch (parity) { case 'e': // 8E1 UCSR0C |= ((1<<UPM01) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; case 'o': // 8O1 UCSR0C |= ((1<<UPM01) | (1<<UPM00) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UCSZ02) | (1<<USBS0)); break; case 'n': // 8N1 UCSR0C |= ((1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM01) | (1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; default: break; } if (txenpin > 1) { // pino 0 & pino 1 são reservados para RX/TX Txenpin = txenpin; /* definir variável global */ pinMode(Txenpin, OUTPUT); digitalWrite(Txenpin, LOW); } return;} /* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * verifica se há qualquer pedido válido do mestre modbus. Se houver, * executa a ação solicitada */unsigned long Nowdt = 0;unsigned int lastBytesReceived;const unsigned long T35 = 5;int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size) { unsigned char query[MAX_MESSAGE_LENGTH]; unsigned char errpacket[EXCEPTION_SIZE + CHECKSUM_SIZE]; unsigned int start_addr; int exception; int length = Serial.available(); unsigned long now = millis(); if (length == 0) { lastBytesReceived = 0; return 0; } if (lastBytesReceived != length) { lastBytesReceived = length; Nowdt = now + T35; return 0; } if (now < Nowdt) return 0; lastBytesReceived = 0; length = modbus_request(slave, query); if (length < 1) return length; exception = validate_request(query, length, regs_size); if (exception) { build_error_packet(slave, query[FUNC], exception, errpacket); send_reply(errpacket, EXCEPTION_SIZE); return (exception); } start_addr = ((int) query[START_H] << 8) + (int) query[START_L]; switch (query[FUNC]) { case FC_READ_REGS: return read_holding_registers(slave, start_addr, query[REGS_L], regs); break; case FC_WRITE_REGS: return preset_multiple_registers(slave, start_addr, query[REGS_L], query, regs); break; case FC_WRITE_REG: write_single_register(slave, start_addr, query, regs); break; }}
é este codigo que estou usando no arduino, me parece que a rede ta funcionando, afinal na pesquisa de nó aparece 1 nó, a ideia era acender um led e apagalo pelo scadabr mas mesmo detectando a rede não consegui seta o led
…
Então antes que o mês se acabe e fique parecendo que abandonei o tópico em questão, irei postar esta implementação, juntamente com a respectiva simulação do funcionamento.
Mas antes das descrições referentes ao código, é preciso apresentar uma particularidade sobre o Hardware. Foi feita a implementação usando um Display LCD 16x2 via Interface I2C, conforme o Hardware originalmente descrito, e a grande vantagem dessa implementação é que diversos pinos do Arduino ficam “livres” para serem usados em outras funções. Mesmo assim também fiz uma implementação com o mesmo Display LCD 16x2, porém via Interface Paralela “original” do Display. E claro, nesta última implementação são “consumidos” muito mais pinos do Arduino. Mas eventualmente alguns irão preferir essa implementação via Interface Paralela, pois ela deixa a execução do código bem mais ágil. Além disso a Simulação do I2C no Proteus, tem alguns problemas e eventualmente trava (isto irá variar de computador para computador), e estes problemas não ocorrem com a Interface Paralela. Então já fica a recomendação: para quem for fazer a Simulação para avaliação do funcionamento, é melhor usar a versão com Interface Paralela, principalmente se for deixar a simulação executando por diversos minutos ou mesmo horas.
Mas para facilitar a coisa toda, no próprio código é possível selecionar qual Interface está sendo usado para o Display. Esta seleção está descrita mais adiante.
Então, inicio a apresentação do Hardware usado na simulação para teste do código, nas duas versões para a Interface com o Display. Nas duas figuras a seguir, são mostradas respectivamente a versão com Interface I2C para o Display, e a versão com Interface Paralela:
(clique na figura para “zoom”)
(clique na figura para “zoom”)
Como se vê nas duas figuras anteriores, além dos dois Sensores de Fluxo (ou vazão), do Sensor de giro da Roda do Trator (que na prática é uma chave tipo “Fim de Curso”), do Display LCD 16x2, e do Botão “Reiniciar” (ou “Iniciar/Reiniciar”), há também no Sistema, um LED “Alive”, um Botão chamado “Pausa” (ou “Pausa/Continua”), e um Buzzer (no código pode-se selecionar um Buzzer “passivo” ou um “ativo”).
Para cada um dos três sensores, há também um Circuito de Interface composto por Resistores/Capacitores, cuja função é promover uma proteção adicional para o Arduino, além de Filtrar ruídos e evitar “bouncing” mecânico (principalmente no “Fim de Curso” usado como sensor de giro da Roda do Trator). É altamente recomendável que estes circuitos sejam usados, principalmente porque os três sensores provavelmente estarão distantes do Arduino e conectados via cabos com comprimento considerável.
A função do LED “Alive” é indicar que o Sistema está operando. Mas ele também indica se o Sistema está “pausado” ou não. Quando o Sistema está pausado, o LED “Alive” pisca duas vezes por segundo. Quando o Sistema está medindo as vazões, o LED “Alive” pisca a cada três segundos. Estes períodos podem ser facilmente alterados no código.
E o Botão “Pausa/Continua”, permite que se pause a medição da vazão. Acionando novamente este Botão, a medição da vazão é retomada. Isto pode ser usado por exemplo, quando o Trator chega ao fim da área linear a ser pulverizada, e é preciso manobrar o Trator para posicioná-lo melhor para continuar a pulverização dentro do mesmo hectare. E claro, durante a manobra, a pulverização pode ser interrompida para evitar desperdício, e neste caso o giro da Roda não seria contabilizado já que o Botão “Pausa” foi acionado.
Já o Buzzer, é usado para fazer sinalizações sonoras, como por exemplo justamente quando o Botão “Pausa/Continua” é acionado, pois uma sinalização sonora é mais eficiente que uma sinalização visual para notificar uma alteração imediata. Claro, dependendo do ruído sonoro no ambiente, pode ser necessário usar um sinalizador sonoro mais “forte” que um simples Buzzer, mas isto é uma substituição simples.
E como já disse anteriormente neste tópico, para a Simulação usei circuitos que geram sinais similares aos dos Sensores de Fluxo e ao “Fim de Curso” (e neste caso sendo acionado de forma cíclica pela Roda do Trator).
Prints da execução da Simulação são mostrados na duas figuras seguintes, para cada versão da Interface do LCD (I2C e paralela):
(clique na figura para “zoom”)
(clique na figura para “zoom”)
Observar que no “Terminal do Arduino” é constantemente exibida a Vazão individual de cada Sensor de Fluxo, em Litros por minuto. É também exibida a vazão em Litros por giro completo da Roda do Trator, sendo esta vazão já efetiva, ou seja, já considera a diferença entre a vazão dos dois Sensores de Fluxo.
No LCD, é exibida a vazão instantânea em Litros por Hectare, que é atualizada a cada giro completo da Roda do Trator. Também é exibida a vazão total acumulada, em Litros.
Como um “feature” a mais, momentaneamente e rapidamente, é exibido no LCD também o tempo que já dura a pulverização, em horas, minutos, e segundos. Assim, o LCD fica trocando entre exibir os dados da vazão e o tempo decorrido. O período que dura cada exibição é facilmente alterado no código. As duas figuras a seguir mostram o detalhe de cada exibição no LCD:
(clique na figura para “zoom”)
(clique na figura para “zoom”)
Na Simulação, deixei as vazões setadas em 8 Litros/minuto e 3,5 Litros/minuto para os dois Sensores de Fluxo. Mas isso pode ser alterado facilmente nos dois Potenciômetros TP10 e TP20 que fazem parte do circuito que faz a emulação dos sensores. Os Frequencímetros junto a estes circuitos, são usados para verificar a taxa de pulsos, e assim conferir se o código está medindo corretamente (sendo que cada 55 Hz equivale a 10 Litros por minuto, então basta se fazer uma “regra de três”).
Já para o “Fim de Curso” que é acionado a cada giro da Roda do Trator, um circuito pouca coisa mais complexo, emula a função na Simulação. Da mesma forma, um Potenciômetro é usado, neste caso para alterar a velocidade de giro da Roda. Um LED “amarelo” indica que a Roda completou um giro e que o “Fim de Curso” foi acionado. O medidor digital ali presente (semelhante ao Frequencímetro usado nos Sensores de Fluxo), indica o tempo decorrido a cada giro da Roda. Neste caso eu deixei setado cada giro em torno de 10 segundos, mas pode ser facilmente alterado no Potenciômetro TP30. A figura a seguir é um “print” do momento em que o “Fim de Curso” é acionado, indicado pelo acionamento momentâneo do LED “amarelo”:
(clique na figura para “zoom”)
Nestes circuitos que emulam os Sensores de Fluxo e o “Fim de Curso”, as chaves ali existentes, permitem que se pare a vazão nos Sensores de Fluxo, e também o “giro da Roda” (ou seja, parar o Trator).
Os valores dos Resistores e Capacitores nos Circuitos de Interface dos Sensores e “Fim de Curso”, foram calculados adequadamente para cada tipo de "sinal real”. Os Capacitores C2 e C3 devem ser do tipo Cerâmico. Já C1 pode ser do tipo Eletrolítico comum. E os Resistores podem ser comuns, com 5% de tolerância, e podem ser de baixa potência (por exemplo 1/8 W).
Agora seguem as descrições sobre as configurações no código para o Arduino, uma vez que o funcionamento do Sistema já foi descrito anteriormente neste tópico.
Como a “área básica” coberta pela pulverização depende do comprimento da Barra Pulverizadora, e do comprimento linear da circunferência da Roda do Trator, as definições mostradas na figura a seguir estão relacionadas a estes dois parâmetros, e estão logo no início do código:
(clique na figura para “zoom”)
Assim basta alterar estes dois parâmetros conforme cada caso. Observar que o comprimento da Barra é especificado em metros. Já para se obter o comprimento linear da circunferência da Roda do Trator, basta que seja especificado o raio da Roda, que está definido no código em centímetros. A especificação do raio da Roda facilita a medição na prática (e o código calcula o comprimento linear automaticamente).
Ainda relacionado à vazão, há a definição do “Fator de Vazão”, conforme mostrado na figura a seguir. O texto na figura explica como este Fator é usado no código, dispensando descrever isso aqui novamente:
(clique na figura para “zoom”)
Caso o Botão “Pausar/Continuar” não seja usado no Sistema, ele pode ser desabilitado no código, no trecho mostrado na figura a seguir:
(clique na figura para “zoom”)
Já o tipo da Interface usada para o Display LCD, e o número de Colunas x Linhas deste Display, podem ser especificados no código conforme mostrado nas duas figuras a seguir:
(clique na figura para “zoom”)
(clique na figura para “zoom”)
Se a Interface for I2C, também deve ser definido o Endereço do LCD no barramento I2C, conforme mostrado na figura a seguir:
(clique na figura para “zoom”)
Observar que está marcado em verde na figura anterior, a correspondente pinagem do LCD em relação ao Módulo I2C usado no Hardware. Normalmente isto é padrão para interfaces baseadas no CI PCF8574 (ou o PCF8574A), e não necessita ser alterado.
Mas se a Interface usada para o LCD for a “paralela”, então as conexões dos pinos do LCD aos pinos do Arduino, devem ser especificadas, conforme mostrado na figura a seguir:
(clique na figura para “zoom”)
E se o LED “Alive” ou o Buzzer não existirem no Sistema, eles podem ser individualmente desabilitados no código, nos dois trechos mostrados a seguir:
(clique na figura para “zoom”)
(clique na figura para “zoom”)
Um detalhe importante referente a todos os pontos no código onde um “elemento” de Hardware é habilitado ou desabilitado: se o elemento é desabilitado, então não é necessário alterar nenhuma outra configuração deste elemento, pois não será configurado o pino correspondente ao elemento, e dessa forma mesmo que as funções correspondentes ainda estejam sendo chamadas no código, elas não terão efeito pois o controle do elemento no código já está “preparado” para isto. Então basta somente desabilitar o elemento.
Segue o código para o Arduino: "Vazao_Pulverizador_01.zip"
Seguem os arquivos para Simulação no Proteus: "Simulacao_Proteus.zip"
Assim encerro minha participação neste tópico, mas fico à disposição para quaisquer dúvidas a respeito.
Abrçs,
Elcids …
Adicionado por Elcids Chagas ao 7:01 em 25 abril 2020
er no monitor serial que ele faz leituras estranhas mesmo que não haja qualquer objeto na frente do sensor, o codigo parece legal, o cara que desenvolveu teve a doçura de fazer alguns samples do ping antes de validar como leitura... como ainda estou engatinhando trouxe aos senhores essa questão.
então senhores caso tenham paciência, o que os senhores me sugerem fazer para diminuir as leituras falsas?
segue o código.
#include <NewPing.h> // This library helps makes our readings more accurate
const int TRIGGER_PIN = 5;
const int ECHO_PIN = 6;
const int ledPin = 9;
const int motor = 10;
const int buzzer = 11;
const int MAX_DISTANCE = 300; // Set maximum distance which can be measured to 300cm
const int SAMPLE_INTERVAL = 25; // Take a sample reading every 25milliseconds
const int PING_ITERATIONS = 3; // Take 3 readings before returning the average of the 3
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);//Create a new instance of the object
int systemState = LOW; //Make sure everything is off
long previousMillis = 0;
long interval;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
pinMode(buzzer, OUTPUT);
pinMode(motor, OUTPUT);
}
void loop() {
float d1 = getDistance(); // call the function which measures the distance
/* Map the distance measured to the number of milliseconds that LED, buzzer and motor
should stay on or off. VERY IMPORTANT! */
interval = map(d1, 0, 250, 0, 2000);
if(d1>0 && d1<=250) //Only do something if distance measured falls within this range
{
changeStateWithoutDelay(interval); //Call function to turn LED, Buzzer or motor on/off
}
else{
digitalWrite(ledPin, LOW);
digitalWrite(buzzer, LOW);
digitalWrite(motor, LOW);
}
Serial.print(d1); Serial.print("cm \t");
//delay(100); //Uncomment this line to make the Serial monitor print out stuff more slowly
Serial.print(interval); Serial.print("milliseconds \t\n");
Serial.println();
}
//Function which measures distance
float getDistance() {
float t = sonar.ping_median(PING_ITERATIONS); // microseconds
float d = sonar.convert_cm(t); // centimeters
return d;
}
// Function which turns LED,buzzer and motor on or off based on how far sensor is from the obstacle
void changeStateWithoutDelay(int interval)
{
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > interval) {
// save the last time you turned everything off
previousMillis = currentMillis;
// if the LED, buzzer and motor is off turn it on and vice-versa:
if (systemState == LOW)
systemState = HIGH;
else
systemState = LOW;
// set the LED, buzzer and motor with the systemState of the variable:
digitalWrite(ledPin, systemState);
digitalWrite(buzzer, systemState);
digitalWrite(motor, systemState);
}
}…
qual era seu problema, por dois motivos: 1) estes problemas são algo comum de ocorrer, quando não se utiliza a abordagem adequada, e portanto são bem conhecidos, fazendo com que eu já imagine o que está ocorrendo; 2) neste momento, estou tentando ter uma visualização melhor do panorama do seu Sistema, quais recursos e elementos existem nele, como eles interagem entre si, e como este Sistema deve se comportar.
Logo, o objetivo neste momento é elucidar o seu Sistema, clarificando alguns aspectos do funcionamento dele, e relacionar comportamentos e efeitos colaterais, em abordagens e cuidados mais comuns que se deve tomar em elementos individuais. Posteriormente, podemos abordar diretamente as tratativas, de forma que sejam incisivas e com muito alta confiabilidade.
Iniciando com suas informações preliminares, podemos fazer um Diagrama em Blocos do seu Sistema, ainda assim com algum caráter genérico, pois não estou fixando a quantidade de PCFs:
(clique na figura para "zoom")
Observe que apesar do Barramento I2C poder ter velocidade de 400 KHz, marquei como sendo de 100 Khz, pois você está conectando apenas os PCF8574, os quais tem a limitação de até 100 Khz no I2C (conforme datasheet).
Sobre o ESP-01, marquei como rodando a 80MHz (mais usado), já que vc não disse a velocidade (80 ou 160 MHz).
Sobre a configuração de I/Os dos PCFs, está um tanto genérica (apenas saídas, apenas entradas, e um "mix" de saídas/entradas em um mesmo PCF). O total de PCFs neste Sistema, é "n".
Finalmente, como provavelmente você estará acionando (On/Off) dispositivos ligados às saídas digitais, e provavelmente lendo status ("On/Off" ou "1/0") via entradas digitais, então marquei desta forma no diagrama.
Então, vamos comentar cada um dos itens que enumerei no post anterior, de acordo com as informações que vc forneceu, e acrescentando um panorama sobre cada um:
1) você informou estar usando neste momento apenas um PCF, mas que acabará por ter quatro (e talvez até mais, já que especificações de Projeto podem mudar mesmo durante o andamento das coisas). Bem, isto é o primeiro e principal indicativo de que vc tem de fato uma encrenca, pois um controle adequado deste único PCF no Sistema, não deveria de forma alguma resultar em problema.
O PCF8574 vai até 100KHz (no I2C). Você pode até aumentar esta velocidade (apenas via código), e provavelmente funcionará (talvez até uns 200KHz, ou até mais), porém estará indo contra o especificado no Datasheet do PCF8574 o que seria um dos primeiros passos para o "dark side".
Para análise, vamos considerar as transações mais simples do I2C, onde em um ciclo com um dispositivo, ou é enviado um Byte, ou é lido um Byte deste dispositivo. Isto pode ser bem observado na Especificação I2C (ver neste link da NXP: I2C Bus Specification), conforme mostrado na figura a seguir:
Veja que para a transmissão mais simples do "Master" (Arduino) para o "Device" (neste caso o PCF, no I2C chamado de "Slave"), ou seja para enviar apenas um Byte ao "Device", teríamos 20 bits (faça a contagem na figura anterior: "Fig 11"). Façamos o cálculo de quanto tempo leva isso no I2C@100KHz: tempo de envio = 20/100000 = 200 us (duzentos micro segundos, ou 0.2 mili segundos).
Na direção contrária, ou seja para ler um Byte do "Device", confira (na "Fig 12") e veja que são os mesmos 20 bits. Logo também gasta-se 200us, ou 0.2 ms.
Observe que estas transações são cerca de 1500 vezes mais lentas que um I/O convencional no Arduino UNO (instruções "OUT" e "IN" do AVR@16MHz), e cerca de 250 vezes mais lentas que uma transação SPI (SCLK@10MHz). Tendo estas comparações em mente, permite-se avaliar abordagens das soluções e seus resultados preliminares.
Se essas transações estivessem sendo gerenciadas por DMA, não haveria muito que se preocupar com estes tempos. Mas Raphael, não é o caso no seu Sistema (o UNO não tem DMA). No seu Sistema, a forma como funciona a "Wire" força o código a acompanhar bem de perto a transação I2C que ocorre no Hardware, o que tem um impacto direto na performance de execução do código. Se vc abrir a "wire.c" e "twi.c", vai ficar espantado com toda a tratativa burocrática que é feita para fazer a cadência de controle do I2C. Claro, isto tem seus motivos (filosofia), mas não vou entrar nesta questão.
Eu ainda não medi o impacto sobre a execução do código no UNO, que essa burocracia toda do I2C tem. Mas quando tiver algum tempo irei medir, a fim de ter dados mais precisos sobre isto. Independente disso, dando uma rápida olhada no código interno das Libs "wire.c" e "twi.c" (isso sem contar a Lib dos próprios dispositivos, como por exemplo um RTC que usa I2C), sabemos que não podemos esperar nenhum código executando como um "foguete" no UNO (que já não é "rápido" por natureza).
Para cada PCF que vc utiliza, uma transação ocorre para atualizar as saídas ou para ler entradas. Se um PCF tem um "mix" de entradas e saídas, então para cada PCF assim, pode-se dizer que ocorrerá no mínimo duas transações (uma para ler, outra para escrever). Então, aqui já temos um ponto para abordar na tratativa, onde considera-se também o gerenciamento da taxa de atualização dos PCFs (o que já deu pra perceber, tem tudo a ver).
2) Você disse estar usando 9600 bps entre Arduino e ESP. Não é porque seu Sistema trabalha e controla "elementos" lentos (como Motores, Relés, Chaves, etc), que seu Sistema deve ser "taxado" para baixo em termos de velocidade de comunicação. A taxa de 9600 é considerada uma das mais baixas. Será ela adequada neste caso? Isto depende do que mais há no seu Sistema, e do impacto que estas "outras coisas" tem na execução do seu código.
Nessa velocidade de 9600, um Byte será transmitido ou recebido em cerca de 1ms (faça a s contas: [1 start bit + 8 bits dados + 1 stop bit]/9600 = 10/9600 = ~1ms) . Parece rápido, principalmente porque o Hardware da UART do Atmega328 (assim como no ESP) gerencia o streaming de bits na transmissão/recepção (claro, no I2C também é assim). Porém, vc não pode esquecer que assim como ocorria no I2C, também há uma burocracia associada à Serial, e ela não deve ser ignorada (esse é um erro quase fatal, dependendo das suas necessidades de transferência de dados e do Protocolo usado).
Para mostrar parte desta "burocracia", sem entrar em análise de código em Libs, vamos dar uma olhada na função "Main.c" do Arduino, que é a "mãezona" que cadencia a execução de seu código no Arduino. Lá esperamos encontrar um código de inicialização, um código em loop infinito, e algumas coisinhas mais. Vejamos:
Vemos que há algumas funções de inicialização ("init", "initVariant", etc), uma das quais é o "setup" do Arduino que conhecemos bem. Depois disso, o código do Arduino entra no "for(;;)", que é um loop infinito. Dentro desse "for", encontramos o famoso "loop" do Arduino, que o mundo Ocidental e Oriental adora como se ele fosse um Faraó do Egito ou Deus Asteca.
Uma vez dentro da função "loop", temos "total" controle (pelo menos é o que todo mundo pensa, né?). Mas note, o que ocorre depois que se encerra um ciclo de execução da função "loop": há um "if", onde o código no Arduino vai dar tratamento para eventos da Serial ("serialEventRun"). Isto é apenas parte da burocracia que mencionei sobre a Serial. E vc não tem como evitá-la: ela vai consumir clocks de execução do seu Processador. Pode parecer que isto é um prelúdio do fim pras nossas esperanças. Mas não é.
Sabendo adequar de forma favorável a temporização do seu Sistema, e tendo uma taxa de bits também adequada (Serial), você pode garantir um funcionamento "suave", sem trancos e barrancos. Neste caso específico da Serial, é quase imprescindível que o Protocolo entre Arduino e ESP tenha sido implementado de forma adequada. Do contrário...
3) como vc informou que a "massa de dados" entre Arduino e ESP é mínima e esporádica, isso é uma enorme boa notícia para vc, pois implica que é relativamente muito tranquilo e fácil de se fazer a temporização adequada que mencionei acima (item "2"), de forma a garantir o funcionamento "suave" sem trancos e barrancos no seu Sistema. Se no entanto a "massa de dados" fosse centenas de bytes por transação, aí a coisa mudaria de figura, mas não é este o seu caso.
A questão de haver comunicação esporádica, acrescenta ao seu Sistema, um elemento que chamamos tecnicamente de "Evento Assíncrono", o que significa que durante o funcionamento do seu Sistema e respectiva temporização, ele deve ser capaz de tratar qualquer evento relacionado à comunicação entre o Arduino e ESP, independente do momento em que isto ocorra, e de forma eficiente, pois do contrário poderá prejudicar qualquer outra tarefa que esteja sendo executada no momento em que o evento assíncrono ocorre, comprometendo a confiabilidade do funcionamento.
Para isto, muitas vezes é preciso não somente olhar com cuidado as tarefas de gerenciamento da comunicação, mas também olhar com mais atenção ainda, as demais tarefas sendo executadas, e analisar qual seria o impacto se uma destas tarefas fosse "interrompida" por um evento assíncrono, ou analisar se o gerenciamento necessário para tratar esse evento pode aguardar até que a tarefa seja concluída (ou pelo menos a parte "crítica" dela).
Neste ponto, vai aparecer muita gente aqui (e por aí afora) dizendo pra vc: "ah, usa logo um RTOS que resolve seu problema". Já vi muita coisa semelhante sendo dita. De fato, é quase certo que o uso de um RTOS ajuda muito nisso. Mas é o que vc procura? ou será que vc quer não somente ver seu Sistema funcionando, mas também ter o prazer de dominar as técnicas adequadas e poder sentir que aquilo tudo está nas suas mãos. Aí é com vc, né Raphael?
Mas já te digo: é relativamente fácil implementar seu Sistema sem precisar de um RTOS, e ainda assim ele se manter simples e eficiente. Além disso, o ESP já funciona (mesmo que vc não perceba e não use explicitamente) sobre a "casca" de um RTOS, e isso não é a solução escancarada na nossa cara, e nem foi até o momento a solução para seu Sistema. Mas e quanto ao Arduino UNO que tem baixa performance de execução, um RTOS funcionará de forma eficiente nele? essa é outra questão que não abordarei, para não destoar mais ainda aqui, mas vc pode pensar (ou pesquisar) sobre isso, sem esquecer que o Arduino não é apenas Hardware e Código, ele é todo um Sistema, com uma infinidade de dispositivos e módulos disponíveis no mercado para uso imediato, em uma Plataforma aberta, com abrangência praticamente imensurável. Atente pra isto: qualquer coisa funciona no Windows? sim, desde que esta coisa se adeque e respeite as regras do Windows. O mesmo vale para um RTOS (o Windows é um deles).
4) vc disse que está usando um Protocolo "padrão" entre Arduino e ESP, mas não disse se há algum "handshaking" nesse processo, e por conta disso tenho que desconfiar que vc não está usando um Protocolo de fato, uma vez que qualquer um deles bem sedimentado, tem "handshaking". Acredito que o que vc está entendendo como Protocolo, é o enviar e receber Bytes via Serial do Arduino e ESP. Mas isso não é um Protocolo, isso é o "streaming" de dados entre Hardware/Firmware.
Usar um Protocolo, mesmo que seja um muito simples e desenvolvido por vc, é quase imprescindível para que seu Sistema funcione de forma confiável, e novamente: sem trancos e barrancos. Há duas coisas antagônicas entre si a respeito de Protocolos de Comunicação: primeiro, eles são uma chatice (porque cada um faz o seu e quer que ele seja engolido guela abaixo de todos, tornando o Mundo cada vez mais complicado), e segundo: eles são sensacionais (porque vc tem a liberdade de fazer o que é adequado para vc, eventualmente sem depender de nenhum outro Protocolo já zanzando por aí).
Pelo que vc informou no item 3, sobre a "massa de dados", parece claro que vc precisa de um Protocolo simples e eficiente. E felizmente, isto é bem fácil de se fazer (dada a simplicidade dos elementos no seu Hardware), e tornaria o seu Sistema praticamente 100% confiável. E claro, isto contribuiria para a execução suave do seu código, mesmo que diversas tarefas estejam sendo executadas nele (a única questão mais delicada e que exige mais atenção, que vejo neste momento, é a Gravação de dados na EEPROM, mas dando a tratativa adequada, não há com que se preocupar).
5) ok, eu já imaginava que seria algo assim como vc descreveu, devido à informações que vc passou no item "3". Aqui há duas abordagens semelhantes. A primeira é que o recebimento de dados via ESP, força vc a atualizar de forma sistemática (e como se fosse em "tempo real"), os PCFs configurados como saídas. Algo tranquilo, mas que vincula o "Evento de Recepção" via ESP, à atualização pontual dos PCFs, e este vínculo tem que ser bem tratado para não causar um "lag de tempo" estranho ao mundo real. A segunda, é que dados lidos dos PCFs devem ser enviados ao ESP via Serial. Vc não deixou claro, se os dados são enviados rotineiramente em uma taxa constante (e portanto interpretados exclusivamente por quem receber estes dados), ou se uma mudança de estado nos Ports de Entrada dos PCFs, dispara um evento que deve ser interpretado e sinalizado através de envio de dados ao ESP via Serial. Claro que a tratativa para estes dois casos é semelhante, mas não é igual, porque uma envia dados numa taxa constante enquanto a outra envia dados devido a um "Evento Assíncrono" (nota: o fato de o "Wire.requestFrom" estar dentro do "loop" do Arduino, não é conclusivo sobre isso). Eu ressaltei esta diferença, porque se for um evento que exige interpretação e cause efeitos mais complexos e pesados em termos de processamento, deve-se tentar desvincular a leitura de dados (do PCF), da tratativa de eventos relacionados aos dados, e do envio de dados propriamente dito (claro: sempre que isto for possível). Não se assuste achando que isso parece complicado, pois não é.
Sobre o uso do "Wire.requestFrom", tudo bem. Eu tenho usado I2C no Arduino apenas através das Libs de módulos, como por exemplo de RTCs e Displays, e estas Libs já fazem uma tratativa própria, a qual quase sempre não possui a melhor performance. E elas raramente tem algum "callback" que possa ser setado (possivelmente elas usam algum "callback", mas restringem seu uso dentro delas, de forma que a implementação da Classe torna isso "invisível" para nós).
Um "callback" geralmente está associado a alguma Interrupção do Processador, de forma que é chamado como resultado de algum "hook" que esteja ou dentro da respectiva ISR, ou após o término de um ciclo de execução da função "loop" do Arduino (neste caso, dentro da ISR é setada uma "flag" indicadora de que o "callback" deve ser executado após o "loop" do Arduino). E não se iluda achando que porque vc não conectou o pino "INT#" do PCF a um pino de IRQ do Arduino, não estão ocorrendo Interrupções resultantes do funcionamento do I2C. Veja o capítulo sobre "TWI" no datasheet (versão "full") do Atmega328, e vai obter a resposta a isso (claro, a resposta é bem óbvia, mas se puder ler e aprender sobre isso...).
Mas por que estou falando sobre isto? Simples: se puder usar alguma forma de "callback" (seja onde for que ele seja chamado), vc poderá setar uma "flag" dentro desse "callback", sinalizando assim um Evento ocorrido ou detectado, e dessa forma estaria tratando o evento de forma assíncrona à ocorrência dele, mas ainda assim rápido o suficiente para parecer "suave" ao mundo real. E claro: o tratamento assíncrono a esta Evento (indicado pela "flag" setada), estaria em perfeita harmonia com todos os outros elementos existentes no código do seu Sistema (incluindo o próprio envio/recepção via Serial para o ESP). Observe que nem sempre é adequado fazer processamento dentro de um "callback", porque muitas vezes vc não sabe de onde ele será chamado. Então usando uma "flag", vc pode isolar a tratativa com total segurança, encaixando-a numa tarefa que concorra de forma controlada com as demais tarefas no seu Sistema, e assim obtendo um funcionamento "suave".
Por favor: não pense que isto é complicado. Não é. Quando se faz a implementação disto, se constata que é simples (e depois o trabalho pesado fica por conta das Máquinas). É com técnicas assim que se projeta Máquinas para o mundo real (ou pelo menos deveria ser, já que algumas por aí afora funcionam a trancos e barrancos e eventualmente tem comportamentos esquisitos). Pode parecer complicado, mas é apenas aparente, como já dizia nosso grande e saudoso mestre Bêda Marques.
A coisa pode parecer se resumir em apenas escrever dados nos PCFs e ler dados dos PCFs, mas pode haver muito mais coelhos nesta cartola do que parecia inicialmente. Mas isso não significa a existência de algo complicado (como eu disse antes, não é).
Continua no post seguinte, para não estourar a capacidade de texto de um post único.
…
Adicionado por Elcids Chagas ao 18:06 em 17 março 2019