ce Routine" = Rotina de Serviço da Interrupção), é gerada pelo TIMER1, que é programado logo no início da execução do código, para gerar uma IRQ ("Interrupt Request" = Requisição de Interrupção) 150 vezes por segundo (ou a cada 6.67 mili-segundos). Uma observação: o TIMER1 do Processador do UNO, tem 4 fontes de Interrupção diferentes, e destas 4 fontes 3 poderiam ser utilizadas para gerar as IRQs para cadenciar a varredura do Display, mas neste Sistema estamos usando a Interrupção gerada pelo COMPA ("Compare Match A").
A programação do TIMER1 para gerar as IRQs é feita na função "config_Display_Refresh", e embora esta seja extremamente simples, também é muito específica já que programa o Hardware do TIMER1, e por isso não irei falar sobre ela aqui (o código está funcionalmente comentado e descreve as operações realizadas). Mas caso alguém tenha alguma dúvida, pergunte aqui mesmo neste tópico.
A ISR do "TIMER1 COMPA" é mostrada na figura a seguir:
(clique na figura para "zoom")
Esta ISR tem três etapas:
1) atualizar o Display, executando assim a varredura do mesmo, que é a área marcada na cor verde na figura anterior. Para isto é usada a variável local "DISPLAY_sel" (que é do tipo "static", e portanto retém seu valor mesmo depois que a rotina termina sua execução), a qual indica qual Display será "ligado" neste momento (e por 6.67ms quando a próxima IRQ ocorrer). Quando a ISR é executada pela primeira vez (e apenas na primeira vez!), "DISPLAY_sel" estará com o valor "1", indicando que o Display "1" será "ligado" e exibirá o valor correspondente ao mesmo. Então conforme cada statement "case" do "switch" é chamada a função "set_Digito_Display" passando como parâmetros a variável correspondente ao Display atualmente sendo "ligado" (ou "SENHA_IN_1", ou "SENHA_IN_2", ou "SENHA_IN_3"), e também o número correspondente ao Display (ou "1", ou "2", ou "3"). Assim esta função "set_Digito_Display" irá setar nos pinos correspondentes aos bits "A", "B", "C", e "D" do Decoder 4511, o valor binário correspondente ao valor a ser exibido (dado por "SENHA_IN_1", "SENHA_IN_2", ou "SENHA_IN_3"), mantendo ligado apenas o Display especificado ("1", "2", ou "3"), e deixando desligado os demais. Mais à frente veremos como a função "set_Digito_Display" faz isso.
2) selecionar o Display que será "ligado" na próxima execução da ISR (que ocorrerá daqui a 6.67ms), que é a área marcada na cor azul na figura anterior. Para isso, simplesmente é incrementada a variável "DISPLAY_sel". Mas como esta variável deve ir de 1 a 3, se o incremento ultrapassar "3", então a variável é reciclada para o valor "1", assim selecionando o Display "1" para a próxima IRQ.
3) executar o "HOOK" correspondente a esta ISR, que é a área marcada na cor amarela na figura anterior. Um "HOOK" nada mais é do que uma chamada a uma função "genérica", e esta função pode ser alterada a qualquer momento pelo código que controla o Sistema. No entanto, neste Sistema este "HOOK" é setado uma única vez, logo no início da execução do código, para apontar a função "atualiza_Status_Botoes", e assim mantido durante todo o funcionamento do Sistema (ou seja, o "HOOK" não é mais alterado). Portanto, na área marcada na cor amarela, simplesmente será chamada a função "atualiza_Status_Botoes", a qual fará a varredura dos Botões e atualizará os status dos mesmos. A função que seta este "HOOK" é a "config_Botoes", que já foi mostrada quando falamos sobre a configuração dos Botões. Todas estas funções são extremamente simples e estão muito organizadas, então vale a pena vc dar uma olhada nas mesmas, pois certamente o aprendizado será significativo.
Então após a execução destas 3 etapas, a ISR termina, e o Processador volta a executar o código "normal" no "foreground". A ISR será executada repetidamente, a cada 6.67ms, onde o próximo Display será ligado para exibir seu valor correspondente, e também será feita a varredura dos Botões para a atualização dos status dos mesmos.
Vamos dar uma olhada rápida na função "set_Digito_Display", a qual recebe como parâmetros o valor a ser exibido num dos Displays e o indicador de qual é este Display. Esta função pode ser vista na figura a seguir:
(clique na figura para "zoom")
O valor a ser exibido é especificado através do parâmetro "DIGITO_N", o qual deve estar entre "0" e "9", uma vez que corresponde a um Dígito da Senha. Por este motivo, o Display só será atualizado se o valor do Dígito estiver dentro da faixa correta (menor ou igual a "9"), o que pode ser constatado no statement "if" na linha 541 na figura anterior.
Mas antes disso, temos a execução da função "set_Display_OFF", cujo objetivo é desligar todos os Displays, o que é feito através dos pinos que controlam os Transistores Q1, Q2, e Q3. A função "set_Display_OFF" é extremamente simples e pode ser vista na figura a seguir:
(clique na figura para "zoom")
Então no início da função "set_Digito_Display" temos que todos os três Displays são desligados. Portanto se o valor do "DIGITO_N" estiver fora da faixa permissível, como resultado temos que todos os Displays desligam, o que de certa forma nos sinaliza que alguma coisa está errada no código. Isto pode ser entendido quase como um teste de sanidade. Em outras palavras, o teste "if" pode ser eliminado, desde que seu código mantenha os valores dos Dígitos dentro da faixa correta. Mas a boa prática de programação, aconselha que se mantenha o teste "if" da linha 541.
Na figura que mostra a função "set_Digito_Display", existem 4 áreas marcadas na cor verde. Cada uma dessas 4 áreas, testa um bit do valor "DIGITO_N", e reproduz esse bit nos pinos "A" a "D" do Decoder 4511. Dessa forma, o valor do "DIGITO_N" será traduzido pelo Decoder 4511, para seu equivalente em 7 segmentos. O teste para saber se cada bit está em "0" ou "1" é feito através da operação AND (o "&" da Linguagem C/C++) com os valores 0x01 (para teste do bit "0"), 0x02 (teste do bit "1"), 0x04 (teste do bit "2"), e finalmente 0x08 (teste do bit "3"). Então conforme o resultado destes testes, ou "HIGH" ou "LOW" será setado nos pinos "A" a "D" do 4511. Ou seja, a reprodução direta e simples de um valor binário de 4 bits.
Mas em qual dos Displays este valor aparecerá? Isto é dado pelo parâmetro "DISPLAY_select", que especifica a qual dos Displays corresponde o valor "DIGITO_N". Logo o parâmetro "DISPLAY_select" pode assumir valores de 1 a 3, indicando assim em qual Display "aparecerá" o valor decodificado pelo 4511. Dentro do statement "switch" (na linha 575), há 3 áreas marcadas na cor azul, e cada uma testa se "DISPLAY_select" é um dos três Displays. Como se vê pela lógica extremamente simples, somente o Display especificado pelo parâmetro "DISPLAY_select" será ligado. E a função termina.
E o processo de varredura continua indefinidamente, na taxa de 150 Hz.
Aqui, para não demorar mais, não irei falar sobre detalhes da leitura/processamento dos status dos Botões (embora a lógica ainda seja simples). Mas qualquer dúvida é só perguntar.
Mas há um ponto muito importante a verificar. Quanto tempo todas as operações executadas na ISR, levam para serem executadas??? A forma mais simples de verificar isso é medindo. Então o que eu fiz foi usar um pino "sobrando" no UNO para gerar um pulso digital que corresponda à praticamente o tempo de duração da ISR. No caso, usei o pino 8, embora isso não apareça no circuito nem no código, pois fiz isso em uma cópia do Projeto apenas para a medição. Simplesmente, a primeira coisa que é feita na ISR, é setar o pino 8 em "HIGH", e a última coisa feita na ISR é setar o pino em "LOW". Isto fornece excelente precisão na medição do tempo gasto na ISR. O resultado pode ser visto na figura a seguir, capturada do Osciloscópio "virtual" do Proteus:
(clique na figura para "zoom")
Como se vê na figura anterior, são gastos cerca de 80 µs na ISR, e isto inclui a varredura do Display e dos três Botões. E qual percentual este tempo corresponde em relação ao tempo que sobra para a execução do código "normal" (no "foreground")??? Simples: basta dividir 80µs por 6.67ms (o ciclo da ISR = 1 / 150Hz), e então multiplicar por 100%, e teremos o percentual desejado. Vejamos então: 80µs / 6.67ms = 0.012, e o percentual será 100% x 0.012 = 1.2%. Ou seja, arredondando temos que todo o processamento da ISR corresponde a cerca de 1% do processamento total do Sistema. A coisa então é muito eficiente (mas como eu disse no início do post, algum cuidado especial deve ser tomado se forem acrescentados ao Sistema dispositivos "OneWire" ou semelhantes a DHT11 ou DHT22").
Aproveitando, pode-se verificar o ciclo da ISR, bastando apenas ajustar a escala de tempo do Osciloscópio "virtual" do Proteus, conforme pode ser visto na figura a seguir:
(clique na figura para "zoom")
E conforme esperado, o ciclo é de 6.67ms, correspondente aos 150 Hz.
Sobre a inicialização do Sistema
Dado ao objetivo deste tópico, não há muito para falar sobre a inicialização do Sistema. Mas qualquer dúvida sobre este processo, basta perguntar. Vejamos a função "setup" do Arduino, que é obrigatória nesta Plataforma, e é mostrada na figura a seguir:
(clique na figura para "zoom")
Ali, há apenas três funções de inicialização sendo executadas.
A primeira é a "config_LEDs", que simplesmente configura como saídas, os pinos dos LEDs "OK" e "ERRO", e também garante que inicialmente estes LEDs estejam desligados.
A segunda é a "config_Botoes", já mostrada aqui no post. Ela configura como entradas, os pinos dos Botões, e inicializa os status de cada Botão. Também nesta função, como já dito, é setado o "HOOK" existente na ISR do "TIMER1 COMPA" para que seja feita a varredura dos Botões e atualização dos status dos mesmos (via função "atualiza_Status_Botoes"). Observar que, o "HOOK" só será executado após a configuração do TIMER1, o que é feito logo a seguir.
A terceira e última função no "setup", é a "init_Display_Control". Ela primeiro configura e inicializa todos os pinos relacionados com o controle dos Displays (os pinos conectados ao Decoder 4511 e os de ON/OFF dos Displays via Transistores Q1 a Q3 do circuito). Então ela configura o TIMER1 para gerar as Interrupções que cadenciam a varredura dos Displays na taxa de 150Hz (incluindo a execução do "HOOK" para tratamento dos Botões).
Sobre a entrada e verificação da Senha
A inserção (ou entrada) da Senha, é controlada através de uma Máquina de Estados, e somente 3 estados foram necessários para todo o controle.
Mas antes de falar sobre a execução desta Máquina, vamos ver duas funções muito simples, que são utilizadas para decrementar ou incrementar os valores dos dígitos da Senha quando a mesma está sendo "inserida" via Botões "1" e "2". Também veremos a função que verifica se a Senha inserida está correta. Estas funções são mostradas na figura a seguir:
(clique na figura para "zoom")
A primeira função, de nome "decrementa_Digito_Senha", irá decrementar o valor do parâmetro "valor_DIGITO". Notar que o valor é do tipo byte, sendo que o "&" logo após a palavra "byte" indica que o parâmetro é passado para a função "por referência", ou seja, a função irá alterar a variável original correspondente ao parâmetro fornecido. O funcionamento é muito simples: a função verifica se o valor é "0", e caso seja, ele é setado para "9", já que a faixa de valores deve ir de 0 a 9. Mas se o valor for outro, ele simplesmente é decrementado.
A segunda função, de nome "incrementa_Digito_Senha", tem o mesmo mecanismo, porém irá incrementar o parâmetro "valor_DIGITO", também fornecido "por referência". Mas aqui é verificado se o valor é "9", e caso seja, ele é setado em "0". Do contrário, o valor é simplesmente incrementado.
A terceira e última função, a "verifica_SENHA", verifica se está correta a Senha atualmente inserida. Como a Senha inserida é constituída pelos dígitos individuais dados pelas variáveis "SENHA_IN_1", "SENHA_IN_2", e "SENHA_IN_3", é preciso "juntar" esses três dígitos em um único valor e então proceder à verificação se a Senha está correta. Como "SENHA_IN_1" tem o peso das centenas, este dígito deve ser multiplicado por 100. E como "SENHA_IN_2" tem o peso das dezenas, este dígito deve ser multiplicado por 10. E como "SENHA_IN_3" tem o peso das unidades, já está com o valor adequado para constituir a Senha atualmente inserida (e que está sendo exibida no Display). Isto é feito na linha 957 da função, onde as três parcelas são somadas. Então basta comparar o valor resultante com a Senha do Sistema, o que é feito na linha 959 da função, sendo o resultado desta comparação ("true" ou "false") informado no retorno da função.
Como dito, uma Máquina de apenas três estados, controla todo o mecanismo de entrada da Senha e verificação da mesma. Pode parecer difícil de visualizar ou acreditar nisso, mas o processo é muito simples conforme veremos a seguir.
Primeiro, vamos ver a definição dos três estados da Máquina de Estados, o que é feito em uma lista enumerada mostrada na figura a seguir:
(clique na figura para "zoom")
Notar que cada estado definido, está marcado com uma cor diferente (verde, azul, e laranja). Isto facilitará visualizar na Máquina de Estados, justamente a execução de cada um destes estados. Notar também que junto às definições, está a descrição do que é feito em cada estado, embora o próprio nome destes já deixe isso praticamente óbvio.
A Máquina de Estados está implementada na função "loop" do Arduino. Vejamos sua estrutura, o que é mostrado na figura a seguir:
(clique na figura para "zoom")
Observar as três regiões marcadas com as mesmas três cores correspondentes aos três estados da Máquina (definidos anteriormente na lista enumerada). Cada região corresponde à execução de um único estado. Quando a função "loop" é executada (e sabemos que ela é executada de forma cíclica), somente um dos estados será executado, e quem determina isso é a variável local de nome "estado_MAQ" (definida na área marcada na cor amarela na figura anterior), que é do tipo byte e também "static" (indicando que seu valor é mantido mesmo quando a função termina sua execução). Esta variável então assumirá apenas um dos três estados definidos. Mas quando o "loop" é executado pela primeira vez (e somente na primeira vez!!!) essa variável já indica o estado "SENHA_iniciar", o que garante que a Máquina tem um estado definido quando o Sistema inicia (ver área marcada em amarelo na figura).
E o próximo estado da Máquina, dependerá do estado atual em que ela se encontra e do que acontece neste estado. Vejamos então o funcionamento desta Máquina, conforme descrito a partir do seu estado inicial, o que é descrito a seguir.
No estado "SENHA_iniciar", que começa na linha 1021, as três variáveis "SENHA_IN_1", "SENHA_IN_2", e "SENHA_IN_3", que armazenam os valores que são mostrados nos três Displays, são zeradas, o que implica que veremos "000" nos Displays. Já a variável "DISPLAY_SENHA" é setada com valor "1", o que significa que o Display "1" está atualmente selecionado para a entrada de dígito da Senha. Ainda neste estado, os dois LEDs, "OK" e "ERRO" são desligados. Então a variável "estado_MAQ" é setada na linha 1032, para que a Máquina siga para o estado "SENHA_inserir".
No estado "SENHA_inserir" (que começa na linha 1038) é onde ocorrem todas as operações relacionadas à entrada de Senha. Na linha 1039, o teste "if" verifica através da função "verifica_BOTAO_trigger_ON" se o Botão "1" foi acionado, e caso isso tenha ocorrido, o "switch" na linha 1041 testa a variável "DISPLAY_SENHA" e conforme o valor desta (1, 2, ou 3) teremos que ou a variável "SENHA_IN_1", ou a "SENHA_IN_2", ou a "SENHA_IN_3", será decrementada, já que o Botão "1" tem esta funcionalidade.
Mas se o Botão "1" não foi acionado, então o "else if" na linha 1061 verifica se o Botão "2" foi acionado, e de forma semelhante ao já descrito, uma das variáveis dos Displays será incrementada, já que o Botão "2" tem esta funcionalidade.
Mas se o Botão "2" não foi acionado, então o "else if" na linha 1083 verifica se o Botão "3" foi acionado, e caso isto tenha ocorrido, então é selecionado o próximo Display através do incremento da variável "DISPLAY_SENHA". Mas aqui há mais coisas pra fazer, pois se "DISPLAY_SENHA" foi incrementada para além de "3" (ou seja, foi para "4"), significa que foi concluída a inserção dos 3 dígitos da Senha, o que é testado na linha 1087. Neste caso, deve ser verificado se a Senha inserida está correta, o que é testado na linha 1089 via função "verifica_SENHA" (que já analisamos), e se esta função retorna "true" então significa que a senha está correta, e neste caso o LED "OK" é ligado (na linha 1091). Mas se a função retornar "false", então a Senha inserida está incorreta, e o LED "ERRO" é ligado (na linha 1095). Independente se a Senha está correta ou não, como a entrada de Senha foi concluída, o Sistema deve aguardar antes de reiniciar todo o processo de entrada de Senha, e para isto na linha 1098 a variável "estado_MAQ" é setada para seguir para o estado "SENHA_aguardar".
Uma vez no estado "SENHA_aguardar" (que começa na linha 1105), novamente é aguardado que o Botão "3" seja acionado. Porém quando isto for detectado, simplesmente a Máquina de Estados é direcionada para o estado "SENHA_iniciar", o que reinicia todo o processo de inserção de Senha.
Como se vê, o processo é simples. Uma coisa importante a notar, é que a lógica mais complexa usada no processamento dos estados, é o "if / else" (o "switch" também é uma forma "sintética" de "if /else"). Isto é uma das coisas que garante a extrema confiabilidade do funcionamento da Máquina de Estados, implicando que a mesma seja praticamente livre de bugs (que são muito fáceis de ocorrer em lógicas malucas ou complexas), e ainda mantendo uma grande simplicidade.
O código e arquivos para Simulação estão neste link: "Senha_7_seg_01.zip"
Link para ver ou fazer download do vídeo da Simulação: "video"
A simulação ajuda a testar mais rapidamente o Sistema, permitindo se ver o funcionamento do Sistema sem precisar montar o circuito. Além disso possibilita que aqueles que não tem o Hardware em mãos, possam testar o Sistema e aprender com isso, fazendo suas experiências.
Em caso de dúvidas, não deixe de perguntar.
Espero ter ajudado.
Abrçs,
Elcids…
Adicionado por Elcids Chagas ao 13:54 em 15 julho 2021
ensor abre, e ele deve ser fechado quando a balança chegar a 250g. E quando clico no segundo botão o sensor abre e deve ser fechado quando a balança chegar a 500g. Juntei codigos que encontrei na net.. mas esta acontecendo o seguinte problema, quando em qualquer um dos dois botões, o sensor se fecha quando a balança fecha em 250g.
Será que conseguem me ajudar com essa programação?
Segue em anexo,
#include <SPI.h>
#include <Ethernet.h>
//Configurações do Ethernet Shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192,168,1, 90 }; // ip que o arduino assumirá
byte gateway[] = { 192,168,1, 1 }; // ip do roteador
byte subnet[] = { 255, 255, 0, 0 };
// String que representa o estado dos dispositivos
char Luz[7] = "0000L#";
EthernetServer server(80); // Cria o servidor na porta 8081
// String onde é guardada as msgs recebidas
char msg[7] = "0000L#";
float loadA = 0.173; //kg
int analogvalA = 159; // Leitura analógico tomado com uma carga sobre a célula de carga
float loadB = 1.035; // kg
int analogvalB = 834; // Leitura analógico tomado com uma carga sobre a célula de carga
// Carregar o esboço de novo, e confirmar , que a partir da saída serial lendo agora quilos está correta , usando suas cargas conhecidos
float analogValueAverage = A3;
// Quantas vezes fazemos leituras ?
long time = 0; //
int timeBetweenReadings = 200; // Nós queremos uma leitura a cada 200 ms;
float load;
float analogToLoad(float analogval){
// Usando um mapa - função personalizada, porque a função arduino mapa padrão utiliza apenas int
float load = mapfloat(analogval, analogvalA, analogvalB, loadA, loadB);
return load;
}
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void setup() {
Ethernet.begin(mac, ip, gateway, subnet);
server.begin();
pinMode(A4,OUTPUT);
}
void loop() {
botao1();
botao2();
}
void botao1(){
EthernetClient client = server.available();
int analogValue = analogRead(A3);
// Executando média - Nós suavizamos as leituras um pouco
analogValueAverage = 0.99*analogValueAverage + 0.01*analogValue;
load = analogToLoad(analogValueAverage);
// SE receber um caracter...
if (client) {
// guarda o caracter na string 'msg'
msg[1]=msg[2];
msg[2]=msg[3];
msg[3]=msg[4];
msg[4]=msg[5];
msg[5]=msg[6];
msg[6] = client.read();
if (msg[6]=='#') {
switch(msg[5]) {
case 'R':
// Se receber o comando 'R#' envia de volta o status dos
// dispositivos. (Que é a string 'Luz')
client.write(Luz);
break;
case 'P':
digitalWrite(A4, HIGH);
break;
}
}
}
if(load > 0.250 && load <0.500)
{
digitalWrite(A4, LOW);
}
}
void botao2(){
EthernetClient client = server.available();
int analogValue = analogRead(A3);
// Executando média - Nós suavizamos as leituras um pouco
analogValueAverage = 0.99*analogValueAverage + 0.01*analogValue;
load = analogToLoad(analogValueAverage);
// SE receber um caracter...
if (client) {
// guarda o caracter na string 'msg'
msg[1]=msg[2];
msg[2]=msg[3];
msg[3]=msg[4];
msg[4]=msg[5];
msg[5]=msg[6];
msg[6] = client.read();
if (msg[6]=='#') {
switch(msg[5]) {
case 'R':
// Se receber o comando 'R#' envia de volta o status dos
// dispositivos. (Que é a string 'Luz')
client.write(Luz);
break;
case 'G':
digitalWrite(A4, HIGH);
break;
}
}
}
if(load > 0.500)
{
digitalWrite(A4, LOW);
}
}
…
Adicionado por Luiz Felipe ao 13:45 em 23 novembro 2015