Nesta publicação, compartilharei minha experiência sobre a criação de um dispositivo IoT do zero: desde o surgimento de uma ideia e sua implementação em hardware até a criação de firmware para um controlador e uma interface da web para gerenciar um dispositivo criado via Internet.
Antes de criar este dispositivo, eu:
- Quase não entendi o circuito. Somente no nível dos princípios de trabalho
resistor / transistor ... Eu não tinha experiência em criar circuitos complicados. - Nunca projetou placas de circuito.
- Nunca componente SMD soldado. O nível do ferro de solda estava no nível dos fios de solda e algum tipo de relé.
- Eu nunca escrevi programas tão complexos para um microcontrolador. Toda a experiência foi no nível “acenda o LED no Arduino” e conheci o controlador ESP8266.
- Eu escrevi bastante C ++ para o "irmão mais velho", mas isso foi há mais de uma dúzia de anos atrás e tudo foi esquecido há muito tempo.
Obviamente, a experiência de trabalhar como programador (principalmente o Microsoft .NET) e o pensamento sistêmico me ajudaram a entender o tópico. Eu acho que o leitor será capaz. Links e artigos úteis sobre o mar da Internet. O mais, na minha opinião, interessante e ajudando a entender o tópico, trago o artigo.
Declaração do problema
Eu moro em uma casa particular perto de Minsk, e minha própria piscina, embora a mais simples, seja parte integrante do conjunto de “benefícios” que muitas pessoas que vivem em uma casa de campo recebem. Em nosso clima instável, verificou-se que nadar na piscina é desconfortável se estiver ao ar livre: a água esfria à noite e o tempo ventoso durante o dia não torna a natação confortável. No ano passado, com minhas próprias mãos, construí uma cúpula geodésica mais acima da piscina, montei uma colina e pendurei uma bungee - as crianças estão felizes.

Reportagem fotográfica da construção da cúpula no Flickr.
Este ano fui ainda mais longe e decidi organizar um aquecedor de piscina a partir de uma caldeira a gás,
que serve para aquecer a casa no inverno e aquecer a água quente no verão.
No verão, o circuito de "aquecimento" da caldeira com a ajuda de válvulas muda para aquecimento
piscina. A água da piscina é aquecida com a ajuda de um trocador de calor de titânio, cujo circuito primário passa o líquido refrigerante (água quente sem impurezas) do circuito de aquecimento e a água secundária da piscina, bombeada por uma bomba de recirculação do sistema de filtragem. Como eu uso a piscina com um clorador (muitos tópicos interessantes são descritos no ForumHouse ), a água contém um pouco de sal e é necessário um trocador de calor de titânio. Você não pode simplesmente pegar e deixar a água entrar diretamente na caldeira - caso contrário, você corroerá todos os canos com sal.

Passando pelo trocador de calor, o transportador de calor aquecido pela caldeira a uma temperatura de cerca de 70-90 ° C libera calor da água da piscina, aquecendo-a alguns graus. O líquido de refrigeração em si esfria algumas dezenas de graus e retorna à caldeira para ser novamente
aquecido. A proporção entre o resfriamento da água da caldeira e o aquecimento da água da piscina depende de muitos fatores: a capacidade do trocador de calor e a velocidade da circulação da água nos circuitos primário e secundário.
Os tubos conectados da piscina ao trocador de calor são tubos de polietileno comuns, aqueles que
atualmente usado para fornecer água fria a residências particulares. Barato, capacidade de suportar pressão decente, ausência de corrosão - essas são as principais vantagens de tais tubos. Para todos, sem exceção, tubos de polietileno, a temperatura de operação é limitada a 40 graus Celsius. Em princípio, isso é mais do que suficiente para a piscina.
No entanto, existe uma alta probabilidade de emergência, caso a bomba
a recirculação da água da piscina irá parar por algum motivo e a caldeira continuará a aquecer o trocador de calor: nesse caso, a água no circuito secundário do trocador de calor aumentará rápido o suficiente para a temperatura do circuito primário, o que significa que as seções dos tubos de polietileno adjacentes ao trocador de calor derreterão e a água da piscina inundará todo o espaço ao redor.
Deve ser possível proteger o superaquecimento do trocador de calor.
Solução rápida
Para resolver esse problema, um sensor de fluxo operando com o princípio do efeito hall foi incluído no circuito do circuito de recirculação de água da piscina. Além disso, sensores de temperatura localizados no circuito secundário
trocador de calor, forneça um segundo nível de defesa, rastreando o possível superaquecimento.
É impossível controlar o superaquecimento apenas por sensores de temperatura: o sistema possui uma grande inércia: após uma parada repentina de água no circuito da piscina, a
desligando a caldeira, a temperatura continua a subir por algum tempo, pois a caldeira ainda conduz a água aquecida ao longo do circuito por inércia, impedindo o superaquecimento de “eu mesmo, meu amado”.
Portanto, é importante responder o mais rápido possível: interromper o fluxo de água no circuito
piscina.
Foi utilizado um sensor de fluxo. A caixa de plástico e a falta de contato do sensor com a água permitem que ele seja usado em água salgada.
Sensores de temperatura, foi decidido usar o Dallas DS18B20, eles são fáceis de conectar várias peças ao mesmo tempo em um barramento de 1 fio .

Foi decidido pendurar um par de sensores na entrada e na saída do secundário e do primário
circuito: total de 4 sensores. Uma vantagem adicional dessa abordagem é
a capacidade de monitorar os parâmetros do sistema: você pode monitorar quanto o líquido de arrefecimento no circuito primário é resfriado e quanta água da piscina é aquecida no circuito secundário. Então - para monitorar a otimização do aquecimento e prever o tempo de aquecimento.
Localização dos sensores nos trocadores de calor e nos tubos de entrada Parâmetros do dispositivo
O primeiro protótipo do dispositivo foi construído com base no Arduino Uno e lançado com sucesso.

Mas então ficou claro que eu gostaria de mais. Aqueceu 16 metros cúbicos de água, mesmo
alguns graus não é rápido. E eu gostaria de monitorar diretamente os parâmetros de aquecimento do trabalho, ligar / desligar. Mas, ao mesmo tempo, seria interessante filmar horários de aquecimento, por exemplo, por dia.
Bem, como já estamos recebendo um dispositivo IoT, por que não controlamos ao mesmo tempo a ativação remota do clorador de piscina e bombeamos para ele?
Termos de Referência
Então, decidiu-se desenvolver um dispositivo - um controlador de piscina multifuncional. Ele deve ser capaz de:
- Para controlar o aquecimento da piscina através do trocador de calor, ligue / desligue a caldeira a gás para aquecer a água.
- Evite o superaquecimento do trocador de calor, monitorando a presença de um fluxo de água da piscina no circuito secundário e a temperatura excessiva do circuito secundário.
- Exibir estatísticas de aquecimento em tempo real (temperatura na entrada e saída de ambos os circuitos).
- Registre (log) os valores de temperatura na memória flash. Exibir dados para
um certo período na forma de um gráfico. - Usando um relé, consiga ligar / desligar as bombas da piscina e o clorador.
- Gerencie todos os parâmetros do dispositivo remotamente através do servidor micro-web embutido.
Houve também a tentação de ferrar Blink, MQTT. Mas a partir desses "sinos e assobios" na primeira fase
Foi decidido recusar. E ainda mais, eu não gostaria de assumir a possibilidade de controle em algum lugar externo. O servidor da web embutido para meus propósitos é suficiente. E a segurança é garantida pelo fato de que você pode entrar na rede doméstica do mundo externo apenas por meio de uma VPN.
Hardware
Como controlador, decidiu-se usar o barato e popular ESP8266. Era perfeito para meus propósitos, exceto por uma coisa: combinar os níveis de sinal dos sensores de 5 volts com a lógica do controlador de 3,3 volts. Em princípio, os sensores de Dallas parecem funcionar a 3 volts, mas eu tenho uma linha bastante longa do controlador aos sensores, cerca de 7 metros. Portanto, é melhor aumentar a tensão.
Foi determinado que é necessário ter o hardware:
- Controlador ESP8266 ou seu irmão mais velho, o ESP32 (como um módulo DevKit ).
- Alinhamento dos níveis de sinal para sensores.
- O regulador de energia é uma parte de 5 volts do circuito.
- Módulo de controle de relé.
- Relógio RTC + memória flash para registro.
- O display LCD de 2 linhas mais simples para exibir os valores atuais dos sensores e o status do dispositivo e do relé.
- Vários botões físicos para controlar o estado do dispositivo sem acesso via web.
Muitos componentes da lista são vendidos como módulos para o Arduino e muitos módulos são compatíveis com a lógica 3.3v. No entanto, eu não queria "congestionar" tudo isso na tábua de pão com feixes de arame, porque quero ter um belo "dispositivo" bonito. Sim, e pelo dinheiro dado aos chineses pelos módulos, você pode desenhar e encomendar completamente sua placa de circuito impresso individual, e a expectativa de sua chegada será compensada por uma instalação relativamente rápida e confiável.
Mais uma vez, observo que esta é minha primeira experiência em circuitos e no design do hardware de tais coisas. Eu tive que estudar muito. Afinal, na minha especialidade, estou um pouco longe dos microcontroladores. Mas fazer tudo "de joelhos" não permitiu o espírito de perfeccionismo que vive em mim.
Diagrama de circuito
Há um grande número de programas no mercado que permitem desenhar um circuito e uma placa de circuito impresso. Sem experiência nesta área, gostei imediatamente do EasyEDA - um editor on-line gratuito que permite pintar um diagrama de circuito, verificar se nada foi esquecido e se todos os componentes têm conexões, desenhar uma placa de circuito impresso e solicitar imediatamente sua produção.
A primeira dificuldade que encontrei: existem muitas opções para o controlador DevKit ESP8266 ou ESP32, algumas delas diferem na localização dos pinos e em sua finalidade, e outras até em largura. Foi decidido desenhar o circuito para que fosse possível colocar o DevKit de qualquer largura e com qualquer localização dos terminais, e nas laterais dele - 2 filas de pares de orifícios de jumpers e, posteriormente, cablagem para conectar os terminais necessários, com relação ao controlador adquirido especificamente.
Coloque sob o controlador e 2 linhas de jumpers emparelhados: JH1 e JH2 no diagrama:

A localização dos pinos da entrada 5v e da saída 3.3v da fonte de alimentação do estabilizador interno, bem como do GND, pareceu-me a mesma para o DevKit diferente, mas ainda assim decidi jogar pelo seguro e também torná-los jumpers: JP1, JP2, JP3 no diagrama.
Decidi assinar os jumpers conectando-os aos componentes do circuito com funções que eles provavelmente executarão.
E aqui está a aparência do DevKit ESP8266, que eu finalmente comprei e instalei Aqui, D1 (GPIO5) e D2 (GPIO4) são responsáveis pelo barramento I2C, D5 (GPIO14) por 1 fio, D6 (GPIO12) - por receber pulsos do sensor de fluxo.
Diagrama do circuito:

(imagem clicável)
Apesar da presença a bordo do ESP8266 de um regulador de potência embutido para 3.3v, ainda precisamos ter 5 volts para alimentar os sensores e o LCD e 12 volts para alimentar o relé. Decidiu-se fazer a placa alimentar 12 volts e colocar o regulador de tensão AMS1117-5.0 na entrada, fornecendo os 5 volts desejados na saída.
Para combinar os níveis de sinal no barramento de 1 fio, usei um transistor de efeito de campo BSS138 c com "pull-ups" de tensão em ambos os lados.

Muito bom sobre a correspondência de níveis está escrito no artigo Correspondência de níveis lógicos de dispositivos 5V e 3.3V .
Para combinar os níveis de sinal do sensor de fluxo, usei apenas um divisor de tensão entre os resistores. O sensor de fluxo é simplesmente um dispositivo coletor aberto . Alguns sensores já podem ter um resistor de pull-up embutido, isso deve ser considerado:

Azul no diagrama é uma designação esquemática do conjunto do sensor de fluxo. À direita do conector estão os divisores de tensão selecionados por mim para ter um nível máximo de 3,3 volts na saída.
No barramento I2C, desliguei o relógio em tempo real DS3231SN e a memória flash AT24C256C para armazenar logs. A memória flash incorporada no ESP8266 não é adequada, pois possui um pequeno número de ciclos de reescrita (10 mil versus 1 milhão para o AT24Cxxx, de acordo com as fichas técnicas).
O controle do relé é organizado em vários chips PCF8574AT e ULN2803A.

O primeiro chip é um expansor de porta de microcontrolador I2C. O status da saída ou entrada ativa PCF8574AT é selecionado selecionando um endereço no barramento I2C.
O chip possui alguns recursos interessantes, bem descritos no artigo expansor de porta I2C PCF8574 .
O chip não pode controlar diretamente a carga (relé). Para isso, é utilizada uma matriz de transistor ULN2803A. Há uma característica: a matriz pode facilmente puxar suas saídas com uma carga para o solo, o que significa que, se uma tensão de alimentação for aplicada ao segundo polo do relé, a corrente fluirá através do enrolamento do relé e os contatos do relé fecharão. Infelizmente, com essa inclusão, obtemos um efeito colateral: o valor do sinal do controlador é invertido e todos os relés "clicam" quando o circuito é ligado. Ainda não descobri como remover esse recurso.
Mais informações sobre o chip são descritas aqui .
O expansor de porta PCF8574AT também pode ser usado como entrada: botões de hardware podem ser pendurados em algumas entradas, lendo seus valores no barramento I2C. No diagrama, os pinos 4-7 podem ser usados para ler o status dos botões. O principal é não esquecer de ativar programaticamente o aperto interno das pernas correspondentes para nutrição.
Ao mesmo tempo, deixei a fiação na matriz do transistor, caso você queira repentinamente conectar relés adicionais. Para possíveis conexões, eu trouxe todos os fios para os conectores (mais precisamente, para os orifícios sob eles onde os fios podem ser soldados ou o conector DIP padrão de 2,54 mm pode ser soldado).
O pino do expansor de porta INT pode ser usado para responder rapidamente ao pressionamento de um botão. Ele pode ser conectado a uma porta livre no controlador e definir o gatilho de interrupção para alterar o estado desse pino.
O visor LCD de duas linhas também é controlado pelo expansor PCF8574AT. O ponto principal: o monitor é alimentado por 5 volts, enquanto o próprio monitor é controlado pela lógica de 3 volts. A propósito, os adaptadores Arduino padrão para I2C não são projetados para voltagem dupla. Eu encontrei a ideia dessa conexão em algum lugar da Internet, infelizmente, perdi o link, então não cito a fonte.
Placa de circuito
Ao projetar a placa, as peças comuns com pernas ocupam muito espaço e muitas fichas no design DIP não são fáceis de encontrar. Depois de ler na Internet que a instalação SMD não é tão complicada e, com a habilidade adequada, consome ainda menos tempo, decidi projetar a placa para peças SMD. E não me enganei. Era uma placa-mãe compacta e bonita, onde eu colocava facilmente tudo o que precisava. As peças SMD, com um bom ferro de solda, fluxo e solda, se mostraram realmente muito fáceis de montar.
No quadro, adicionei algumas margens quadradas de furos para prototipagem, se de repente eu quiser soldar outra coisa.
Fiz uma placa de circuito impresso medindo 97x97 mm. Cabe facilmente em uma caixa elétrica de corte padrão. Além disso, placas com tamanhos inferiores a 100x100 são baratas de fabricar. A produção de um lote mínimo de 5 placas, de acordo com o layout desenvolvido, custa 5 dólares, e a entrega na Bielorrússia custa outros 9 dólares.

O design do quadro está no site da EasyEDA e está disponível para todos.
Observo que na foto do controlador abaixo aparece a primeira amostra da placa, na qual eu "torci" muitas coisas desnecessárias e desnecessárias (na esperança de usar esse lote mínimo de 5 placas em outros projetos). Aqui e no EasyEDA, publiquei uma versão "limpa" de todas essas coisas desnecessárias.

Fotos dos dois lados do quadroFrente:

Verso:

Parte do software
Para programar o microcontrolador, devido ao atraso na forma de um protótipo no Arduino Uno, foi decidido usar o ambiente do Arduino com o
ESP8266 Arduino Core instalado. Sim, você pode usar
Lua no ESP8266, mas eles dizem que há travamentos. Eu, dada a função crítica desempenhada, não gostaria de fazê-lo.
O ambiente do Arduino em si parece um pouco desatualizado para mim, mas, felizmente, há uma
extensão para o Visual Studio do Visual Micro. O ambiente permite que você use as dicas de código do IntelliSence, salte rapidamente para as declarações de funções, refatore o código: em geral, tudo o que o ambiente dos computadores "adultos" permite. A versão paga do Visual Micro também permite que você depure convenientemente o código, mas fiquei satisfeito com a opção gratuita.
Estrutura do projeto
O projeto consiste nos seguintes arquivos:
Estrutura do projeto no Visual Studio Ficheiro | Nomeação |
---|
WaterpoolManager.ino
| Declaração de variáveis e constantes básicas. Inicialização. Laço principal.
|
HeaterMainLogic.ino
| A lógica básica do controle do relé da caldeira (de acordo com a temperatura) e dos relés auxiliares.
|
Sensors.ino
| Ler dados do sensor
|
Settings.ino
| Configurações do dispositivo, salvando-as na memória flash do controlador
|
LCD.ino
| Saída de informações no LCD
|
ClockTimer.ino
| Leitura de relógio RTC ou simulação de relógio
|
Relays.ino
| Controle de relé on / off
|
ButtonLogic.ino
| Lógica de reação ao estado dos botões de hardware
|
ReadButtonStates.ino
| Ler estados dos botões de hardware
|
EEPROM_Logging.ino
| Registro de dados do sensor na EEPROM
|
WebServer.ino
| Servidor web incorporado para gerenciamento de dispositivos e exibição de status
|
Páginas da Web
| As páginas do servidor da Web são armazenadas nesta pasta.
|
index.h
| A página principal para exibir o status do dispositivo. Lendo o estado atual com chamada ajax. Atualize a cada 5 segundos.
|
loggraph.h
| Exibe um log de dados do sensor e estados de retransmissão em um gráfico. A biblioteca jqPlot é usada - toda a construção ocorre no lado do cliente. A solicitação para o controlador vai apenas para um arquivo binário - cópias de dados da EEPROM.
|
logtable.h
| também, mas na forma de uma tabela
|
settings.h
| Gerenciando configurações do dispositivo: definindo limites para temperatura, fluxo de água, frequência do registro de dados
|
time.h
| Configuração da hora atual
|
| Bibliotecas
|
EepromLogger.cpp
| Biblioteca de Logs Flash
|
EepromLogger.h
|
crc8.cpp
| 8- CRC
|
crc8.h
|
TimeSpan.cpp
|
|
TimeSpan.h
|
OneWire tempSensAddr. . ( 4 ):
while (ds.search(tempSensAddr[lastSensorIndex]) && lastSensorIndex < 4) { Serial.print("ROM ="); for (byte i = 0; i < 8; i++) { Serial.print(' '); Serial.print(tempSensAddr[lastSensorIndex][i], HEX); } if (OneWire::crc8(tempSensAddr[lastSensorIndex], 7) != tempSensAddr[lastSensorIndex][7]) { Serial.print(" CRC is not valid!"); } else lastSensorIndex++; Serial.println(); } ds.reset_search(); lastSensorIndex--; Serial.print("\r\nTemperature sensor count: "); Serial.print(lastSensorIndex + 1, DEC); , (). Serial LCD : // Read sensor values and print temperatures ds.reset(); ds.write(0xCC, TEMP_SENSOR_POWER_MODE); // Request all sensors at the one time ds.write(0x44, TEMP_SENSOR_POWER_MODE); // Acquire temperatures delay(1000); // Delay is required by temp. sensors char tempString[10]; for (byte addr = 0; addr <= lastSensorIndex; addr++) { ds.reset(); ds.select(tempSensAddr[addr]); ds.write(0xBE, TEMP_SENSOR_POWER_MODE); // Read Scratchpad tempData[addr] = ds.read() | (ds.read() << 8); // Read first 2 bytes which carry temperature data int tempInCelsius = (tempData[addr] + 8) >> 4; // In celsius, with math rounding Serial.print(tempInCelsius, DEC); // Print temperature Serial.println(" C"); }
De acordo com a folha de dados, os sensores requerem um atraso de pelo menos 750 ms entre solicitar um valor de temperatura e receber uma resposta do sensor. Portanto, o código introduziu um atraso com uma pequena margem.
No entanto, esse atraso, quando todo o dispositivo está apenas aguardando uma resposta, é aceitável no início, mas é absolutamente inapropriado esperar sempre com a pesquisa regular dos sensores. Portanto, o seguinte código complicado foi escrito, chamado a cada 50 ms por timer:
#define TEMP_MEASURE_PERIOD 20 // Time of measuring, * TEMP_TIMER_PERIODICITY ms #define TEMP_TIMER_PERIODICITY 50 // Periodicity of timer calling, ms timer.attach_ms(TEMP_TIMER_PERIODICITY, tempReadTimer); int tempMeasureCycleCount = 0; void tempReadTimer() // Called many times in second, perform only one small operation per call { tempMeasureCycleCount++; if (tempMeasureCycleCount >= TEMP_MEASURE_PERIOD) { tempMeasureCycleCount = 0; // Start cycle again } if (tempMeasureCycleCount == 0) { ds.reset(); ds.write(0xCC, TEMP_SENSOR_POWER_MODE); // Request all sensors at the one time ds.write(0x44, TEMP_SENSOR_POWER_MODE); // Acquire temperatures } // Between phases above and below should be > 750 ms int addr = TEMP_MEASURE_PERIOD - tempMeasureCycleCount - 1; if (addr >= 0 && addr <= lastSensorIndex) { ds.reset(); ds.select(tempSensAddr[addr]); ds.write(0xBE, TEMP_SENSOR_POWER_MODE); // Read Scratchpad tempData[addr] = ds.read() | (ds.read() << 8); // Read first 2 bytes which carry temperature data } }
No início de cada ciclo tempMeasureCycleCount, os sensores são solicitados a ler seus valores. Após a passagem de cerca de 50 desses ciclos (e no total são 50 * 20 = 1000 ms = 1 s), o valor de cada sensor é lido, um de cada vez. Todo o trabalho é dividido em pedaços para que o código executado na interrupção do timer não demore muito tempo no controlador.
O valor do sensor de fluxo é calculado da seguinte forma. Ao interromper o pino no qual o sensor está pendurado, aumentamos o valor do contador de carrapatos provenientes do sensor de fluxo:
pinMode(FLOW_SENSOR_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), flow, RISING); // Setup Interrupt volatile int flow_frequency; // Flow sensor pulses int flowMeasureCycleCount = 0; void flow() // Flow sensor interrupt function { flow_frequency++; }
No mesmo cronômetro em que os sensores de temperatura são pesquisados, uma vez por segundo, pegamos esse valor de escala e o convertemos em litros usando a constante FLOW_SENSOR_CONST, cujo valor pode ser encontrado nas características do sensor:
flowMeasureCycleCount++; if (flowMeasureCycleCount >= 1000 / TEMP_TIMER_PERIODICITY) { flowMeasureCycleCount = 0; litersInMinute = (flow_frequency / FLOW_SENSOR_CONST); // Pulse frequency (Hz) = FLOW_SENSOR_CONST*Q, Q is flow rate in L/min. flow_frequency = 0; // Reset Counter }
Registrando dados de sensores e status do dispositivo
No desenvolvimento do mecanismo de registro, o fato de o dispositivo poder ser desligado repentinamente, ou seja, em quase qualquer momento. Quando você para de gravar, devemos restaurar tudo o que foi gravado até o último momento. Ao mesmo tempo, não podemos reescrever constantemente a mesma área da memória flash (por exemplo, um determinado título em um determinado local, lembrando o endereço da última gravação), para evitar a "limpeza" acelerada da unidade flash nesse local.
Após alguma “acumulação”, o seguinte modelo de gravação foi inventado e implementado:

Cada registro é um registro que contém informações sobre o valor atual do fluxo de água, as temperaturas do sensor e o status do dispositivo codificado no byte (bits individuais indicam se o relé está ligado ou não, se o aquecimento está ativado ou não):
struct LogEvent { unsigned char litersInMinute = 0; unsigned char tempCelsius[4]{ 0, 0, 0, 0 }; unsigned char deviceStatus = 0; }
Após cada registro, há um byte de soma de verificação CRC , indicando se o registro foi gravado corretamente e, em geral, se pelo menos algo foi gravado nesse local de memória.
Como seria muito caro registrar dados no horário atual ( registro de data e hora ) para cada registro em termos de volume, os dados são organizados em grandes blocos, com N registros em cada um. O registro de data e hora de cada bloco é registrado apenas uma vez, para o restante - é calculado com base em informações sobre a frequência do registro.
unsigned int logRecordsInBlock = 60 * 60 / loggingPeriodSeconds; // 1 block for hour unsigned int block_size = sizeof(Block_Header) + logRecordsInBlock * (record_size + crcSize); unsigned int block_count = total_storage_size / block_size;
Por exemplo, com uma frequência de registro de uma vez a cada 30 segundos, teremos 120 entradas em um bloco e o tamanho do bloco será de cerca de 840 bytes. No total, podemos encaixar 39 blocos na memória de uma unidade flash de 32 kilobytes de tamanho. Com essa organização, verifica-se que cada bloco inicia em um endereço estritamente definido na memória, e “percorrer” todos os blocos não é um problema.
Dessa forma, com uma interrupção repentina no registro durante o último desligamento do dispositivo, teremos um bloco inacabado (ou seja, no qual alguns dos registros estão ausentes). Quando o dispositivo está ligado, o algoritmo procura o último cabeçalho de bloco válido (timestamp + crc). E continua a gravar, começando no próximo bloco. A gravação é realizada ciclicamente: o último bloco substitui os dados do bloco mais antigo.
Ao ler, todos os blocos são lidos sequencialmente. Blocos inválidos (aqueles que não passam no CRC para registro de data e hora) são totalmente ignorados. Os registros em cada bloco são lidos até a reunião do primeiro registro inválido (ou seja, aquele em que a gravação foi cortada pela última vez se o bloco não tiver sido gravado inteiramente). O resto é ignorado.
Para cada registro, o tempo atual é calculado com base no registro de data e hora do bloco e no número de série do registro no bloco.
LCD
O dispositivo usa um display QC1602A, capaz de exibir 2 linhas de 16 caracteres. A primeira linha exibe as informações atuais sobre os valores atuais dos sensores: vazão e temperaturas. Se o limite especificado for excedido, um ponto de exclamação aparecerá próximo ao valor. A segunda linha mostra o status do relé de aquecimento e da bomba, bem como o tempo decorrido desde que o aquecimento foi ligado ou desligado. A cada 5 segundos, o display na segunda linha mostra brevemente os limites atuais. As fotos da exibição em vários modos são mostradas no final da publicação.
Gráficos
Quando solicitado pelo servidor da web interno, os dados de log são lidos em formato binário usando JavaScript:
var xhttp = new XMLHttpRequest(); xhttp.open("GET", "logs.bin", true); xhttp.responseType = "arraybuffer"; xhttp.onprogress = updateProgress; xhttp.onload = function (oEvent) { var arrayBuffer = xhttp.response; if (arrayBuffer) { var byteArray = new Uint8Array(arrayBuffer); … }}; xhttp.send(null);
Lê-los em algum formato não-binário popular, como o ajax, seria um luxo inadmissível para o controlador, principalmente por causa da grande quantidade que o servidor http interno teria que retornar.
Pelo mesmo motivo, a biblioteca JavaScript jqPlot é usada para construir gráficos e os próprios arquivos da biblioteca JS são carregados a partir de CDNs populares.
Um exemplo da programação do dispositivo:

Vê-se claramente que, por volta das 9:35, o dispositivo foi ligado para aquecimento, a caldeira começou a aquecer gradualmente o circuito de aquecimento (sensores T3, T4), após o que a temperatura do circuito da piscina começou a aumentar (sensores T1, T2). Por volta das 10:20, a caldeira passou a aquecer água quente em casa, a temperatura do circuito de aquecimento caiu. Depois de mais 10 minutos, a caldeira voltou a aquecer a água da piscina. Às 10:50 ocorreu um acidente: a bomba de circulação de água na piscina foi desligada de repente. O fluxo de água caiu acentuadamente para zero, o relé de aquecimento desligado (linha pontilhada vermelha na 2ª tabela), impedindo o superaquecimento. Mas o dispositivo ainda permaneceu em um estado de aquecimento (linha vermelha no 2º gráfico). I.e. se a bomba fosse ligada novamente e as temperaturas estivessem normais, o dispositivo retornaria ao aquecimento. Observo que após uma parada de emergência da bomba, as temperaturas no circuito de água da piscina (T1, T2) começaram a aumentar acentuadamente devido ao superaquecimento do trocador de calor. E se não fosse por um desligamento acentuado da caldeira, haveria problemas.
Servidor da Web incorporado
Para se comunicar com o mundo exterior, é usada a classe padrão ESP8266WebServer . Quando o dispositivo é iniciado, ele é inicializado como um ponto de acesso com a senha padrão especificada em #define AP_PASS. Uma página da web é aberta automaticamente para selecionar uma rede wi-fi disponível e inserir uma senha. Após digitar a senha, o dispositivo reinicia e se conecta ao ponto de acesso especificado.
Dispositivo acabado
O dispositivo acabado foi colocado em uma caixa de corte padrão para a fiação. Um orifício para o LCD foi cortado e orifícios para os conectores.

Fotos da fachada do dispositivo em diferentes modosCom a exibição do tempo decorrido após a ativação:

Com os limites exibidos:

Conclusão
Concluindo, quero dizer que, ao desenvolver um dispositivo como esse, adquiri grande experiência com circuitos, design de PCB, habilidades de instalação de componentes SMD, na arquitetura e programação de microcontroladores, lembrei-me de C ++ quase esquecido e manuseio cuidadoso da memória e outros recursos limitados do controlador. O conhecimento de HTML5, JavaScript e habilidades de depuração de scripts no navegador também foram úteis em certa medida.
Essas habilidades e o prazer recebido durante o desenvolvimento do dispositivo são os principais benefícios obtidos. E os códigos-fonte do dispositivo, diagrama de circuito, placas de circuito impresso - use, modifique. Todos os códigos-fonte do projeto estão no GitHab. Hardware em um projeto público no EasyEDA. Coletei dados sobre os chips usados no projeto em uma unidade de rede .