Objetivos do projeto
De alguma forma, acabei construindo minha casa, um esqueleto. No meu luxo, não há gás e não é esperado em um futuro próximo; portanto, escolhi um esqueleto - tudo o resto, para mim, seria muito caro aquecer com eletricidade. Bem, também porque é uma das tecnologias mais baratas.
Ok, joguei canos pela casa, desliguei as baterias, uma caldeira, parecia quente, mas algo estava errado.
Depois de me ouvir, percebi que esse sapo não me agrada, que enquanto não estou em casa (12 a 16 horas por dia), o aquecimento funciona. E pode não funcionar, ligue apenas antes da chegada, pois o esqueleto tem uma ligeira inércia e permite aumentar rapidamente a temperatura. A mesma situação quando em algum lugar por um longo tempo para sair de casa. Bem, em geral, correr, girar a manivela da caldeira com mudanças de temperatura na rua de alguma forma não é kosher.
Ficou claro que, sem automação, em nenhum lugar a caldeira é a mais simples, mas possui contatos para conectar um relé de controle externo. Claro, você poderia comprar imediatamente uma caldeira com todas as funções necessárias, mas, para mim, essas caldeiras são de alguma forma desumanas. Além disso, eu queria me agachar com o cérebro, fazer xixi na alma, aprender um pouco de C, embora na versão do arduino.
Na verdade, sobre os requisitos:
- controle de temperatura do ponto de ajuste
- controle da temperatura do líquido de refrigeração, dependendo da temperatura externa ou manualmente
- fusos horários com configurações diferentes, mais frios de dia e mais quentes de noite
- modo automático, com transição automática dia-noite
- modo manual, sem transições automáticas, para o fim de semana
- modo não automático, onde você pode definir manualmente qualquer temperatura do líquido de refrigeração e ligar / desligar a caldeira
- controle de aquecimento localmente, a partir de botões e tela e através do site / aplicativo móvel
Foi no começo e depois sofri e acrescentei:
- controle de lâmpadas de rua (refletor LED)
- sistema de alarme baseado em sensor de movimento, sirene e poste
- medição da energia consumida pela caldeira por dia / mês / ano + para cada mês do ano
- modo de alarme apenas piscando lentamente uma lâmpada
- modo de sinalização piscando rapidamente uma lâmpada e bipes curtos de uma sirene
- modo de sinalização piscando rapidamente uma lâmpada e uivos constantes de uma sirene
O objetivo deste artigo é compartilhar experiências, descrever algo em russo que não consegui encontrar na Internet. Acho que este artigo será útil para iniciantes em Arduino que já estão um pouco familiarizados com programação, como coisas absolutamente básicas que não descrevi. Tentei escrever o código o mais claro possível, espero ter conseguido.
O que estava no começo
Inicialmente, o projeto foi implementado em um monte selvagem de Arduino Nano + ESP8266, mas o ESP não é um escudo, mas um dispositivo separado. Porque Sim, porque eu já tinha tudo isso, mas não havia dinheiro da palavra, então não queria comprar ferro novo em princípio. Por que o ESP não é como um escudo? Agora nem me lembro.
O Arduino conduziu todos os processos porque possuía a quantidade necessária de GPIO, e o ESP enviou todos os dados ao servidor Blynk, porque conhecia a Internet e não possuía GPIO suficiente. Eles se conectaram através do UART e enviaram JSON com dados um para o outro. O esquema é incomum, mas funcionou por um ano quase sem queixas. Qualquer pessoa interessada pode ver o codec .
Farei uma reserva imediatamente, não era muito habilidoso na época (e até agora gostaria de fazer melhor), portanto, é melhor que mulheres grávidas e crianças não assistam. Além disso, tudo foi escrito no IDE do Arduino, não será lembrado à noite, o que é bastante limitado em termos de refatoração, tudo é muito primitivo lá.
Ferro
Assim, um ano se passou, as finanças autorizadas a comprar o ESP32 devkit v1, que possui GPIO suficiente, podem acessar a Internet e geralmente um super controlador. Além das piadas, gostei muito dela no final do trabalho.
Lista de ferro:
- ESP32 devkit v1 noname China
- 3 sensores de temperatura DS18B20, temperatura interna, externa e temperatura do líquido de refrigeração nos tubos
- bloco de 4 relés
- sensor pir HC-SR501
Não vou desenhar um esquema, acho que tudo ficará claro nas macros com os nomes dos pinos.
Por que FreeRTOS e Arduino Core
Muitas bibliotecas estão escritas no Arduino, em particular o mesmo Blynk, para que você não se afaste do Arduino Core.
FreeRTOS porque permite organizar o trabalho de um pequeno pedaço de ferro semelhante ao trabalho de um controlador industrial completo. Cada tarefa pode ser movida para sua própria tarefa, parada, iniciada, criada quando necessário, excluída - tudo isso é muito mais flexível do que escrever uma longa porcaria de código Arduino, quando no final tudo é feito por sua vez na função loop.
Ao usar o FreeRTOS, cada tarefa será executada em um horário estritamente especificado, se apenas a energia do processador for suficiente. Pelo contrário, no Arduino, todo o código é executado em uma função, em um thread, se algo diminuir, o restante das tarefas será atrasado. Isso é especialmente perceptível ao gerenciar processos rápidos. Neste projeto, esse piscar de lanterna e o bipe da sirene serão discutidos abaixo.
Sobre lógica
Sobre as tarefas do FreeRTOS
→ Link para todo o codec do projeto
Portanto, ao usar o FreeRTOS, a função de configuração desempenha o papel da função principal, o ponto de entrada para o aplicativo, as tarefas do FreeRTOS (a seguir designadas) são criadas nele, a função de loop não pode ser usada.
Considere uma pequena tarefa para calcular a temperatura do líquido de refrigeração:
void calculate_water_temp(void *pvParameters) { while (true) { if (heating_mode == 3) {} else { if (temp_outside > -20) max_water_temp = 60; if (temp_outside <= -20 && temp_outside > -25) max_water_temp = 65; if (temp_outside <= -25 && temp_outside > -30) max_water_temp = 70; if (temp_outside <= -30) max_water_temp = 85; } vTaskDelay(1000 / portTICK_RATE_MS); } }
É declarada como uma função que deve levar _void pvParameters
, um loop sem fim é organizado dentro da função, usei while (true)
.
Um cálculo simples de temperatura é vTaskDelay(1000 / portTICK_RATE_MS)
(se o modo operacional permitir) e a tarefa é sacrificada pelo vTaskDelay(1000 / portTICK_RATE_MS)
por 1 segundo. Nesse modo, ele não consome tempo da CPU, as variáveis com as quais a tarefa trabalhou, em outras palavras, o contexto, são armazenadas na pilha para tirá-las de lá quando chegar a hora.
A próxima tarefa deve ser criada na instalação. Isso é feito chamando o método xTaskCreate
:
xTaskCreate(calculate_water_temp, "calculate_water_temp", 2048, NULL, 1, NULL);
Existem muitos argumentos, mas para nós, calcule_aqu_tempo_água é significativo - o nome da função que contém o código da tarefa e 2048 é o tamanho da pilha em bytes.
O tamanho da pilha inicialmente definiu todos como 1024 bytes, então calculei o método desejado digitando: se o controlador começar a cair com um estouro de pilha (como pode ser visto na saída em uart), apenas aumentei o tamanho da pilha em 2 vezes, se não ajudou, em 2 vezes e assim até que funcione. Obviamente, isso não economiza muito memória, mas o ESP32 possui o suficiente, no meu caso, você não poderia se preocupar com isso.
Você também pode especificar um identificador para tarefa - um identificador com o qual você pode controlar a tarefa após a criação, por exemplo - excluir. Este é o último NULL no exemplo. Um identificador é criado assim:
TaskHandle_t slow_blink_handle;
Em seguida, ao criar uma tarefa, um ponteiro para o xTaskCreate
passado para o parâmetro xTaskCreate:
xTaskCreate(outside_lamp_blinks, "outside_lamp_blynk", 10000, (void *)1000, 1, &slow_blink_handle);
E se queremos remover a tarefa, fazemos o seguinte:
vTaskDelete(slow_blink_handle);
Como isso é usado pode ser visto no código da panic_control
panic_control.
FreeRTOS Mutex Pros
O Mutex é usado para eliminar conflitos entre tarefas ao acessar recursos como uart, wifi etc. No meu caso, eu precisava de mutexes para wifi e acesso à memória flash.
Crie um link para o mutex:
SemaphoreHandle_t wifi_mutex;
Na setup
crie um mutex:
wifi_mutex = xSemaphoreCreateMutex();
Além disso, quando precisamos acessar o recurso de tarefa, ele recebe o mutex, permitindo que o restante das tarefas saiba que o recurso está ocupado e não há necessidade de tentar trabalhar com ele:
xSemaphoreTake(wifi_mutex, portMAX_DELAY);
portMAX_DELAY
- aguarde indefinidamente até que o recurso e o mutex sejam liberados por outras tarefas, todo esse tempo a tarefa será suspensa.
Depois de trabalhar com o recurso, fornecemos o mutex para que outros possam usá-lo:
xSemaphoreGive(wifi_mutex);
Você pode ver o código em mais send_data_to_blynk
na send_data_to_blynk
send_data_to_blynk.
Na prática, o não uso de mutexes não era perceptível durante a operação do controlador, mas durante a depuração JTAG, os erros desapareciam constantemente e desapareciam após o uso dos mutexes.
Breve descrição do tasok
get_temps
- recebendo temperatura dos sensores, a cada 30 segundos, mais frequentemente, não é necessário.
get_time_task
- obtém tempo dos servidores NTP. Anteriormente, chegou a hora do módulo RTC DS3231, mas ele começou a falhar após um ano de trabalho, então decidi me livrar dele. Decidi que isso não tem consequências especiais, principalmente o tempo afeta a mudança do fuso horário do aquecimento - dia ou noite. Se a Internet desaparecer durante a operação do controlador, o horário simplesmente congelará, o fuso horário permanecerá o mesmo. Se o controlador desligar e depois de ligar não houver Internet, o horário será sempre 0:00:00 - modo de aquecimento à noite.
calculate_water_temp
- considerado acima.
detect_pir_move
- recebendo um sinal de movimento do sensor HC-SR501. O sensor forma uma unidade lógica + 3.3V quando o movimento é detectado, o qual é detectado usando digitalRead
, a propósito, o pino de detecção desse sensor deve ser puxado para GND - pinMode(pir_pin, INPUT_PULLDOWN);
heating_control
- alternando os modos de aquecimento.
out_lamp_control
- controle de uma lâmpada de rua.
panic_control
- controla a sirene e o foco quando o movimento é detectado. Para criar o efeito de sirenes e luzes piscantes, são usadas tarefas separadas, siren_beeps
e siren_beeps
. Ao usar o FreeRTOS, o piscar e o bipe funcionam perfeitamente, exatamente nos intervalos definidos, outras tarefas não afetam seu trabalho, porque eles vivem em fluxos separados. O FreeRTOS garante que o código na tarefa seja executado no horário especificado. Ao implementar essas funções no loop
tudo funcionou não tão bem, porque influenciado pela execução de outro código.
guard_control
- controle dos modos de guarda.
send_data_to_blynk
- envia dados para o aplicativo Blynk.
run_blynk
- tarefa para iniciar o Blynk.run()
conforme exigido pelo manual para o uso do Blynk. Pelo que entendi, isso é necessário para obter dados do aplicativo para o controlador. Em geral, Blynk.run()
deve estar em um loop
, mas eu basicamente não queria colocar nada lá e a tornava uma tarefa separada.
write_setting_to_pref
- grave configurações e modos de operação para capturá-los após uma reinicialização. Sobre pref será descrito abaixo.
count_heated_hours
- contando o tempo de operação da caldeira. Fiz isso com simplicidade, se a caldeira estiver ligada no momento do início da tarefa (uma vez a cada 30 segundos), na memória flash o valor da chave desejada é aumentado em um.
send_heated_hours_to_app
- nesta tarefa, os valores são extraídos e multiplicados por 0,00833 (1/120 horas), as horas recebidas de operação da caldeira são enviadas para o aplicativo Blynk.
feed_watchdog
- alimenta o Watchdog. Eu tive que escrever watchdog, porque uma vez a cada poucos dias, o controlador pode congelar. O que está conectado não é claro, pode haver algum tipo de interferência na fonte de alimentação, mas o uso do watchdog resolve esse problema. Watchdog timer por 10 segundos, tudo bem se o controlador não estiver disponível por 10 segundos.
heart_beat
- tarefa com um pulso. Quando passo no controlador, quero saber se ele funciona bem. Porque na minha placa não há LED embutido, tive que usar o UART LED - instalar Serial.begin(9600);
e escreva uma sequência longa no UART. Funciona muito bem.
Nivelamento de desgaste ESP32 NVS
As descrições a seguir são bastante grosseiras, literalmente nos dedos, apenas para transmitir a essência do problema. Mais detalhes
O Arduino usa memória EEPROM para armazenar dados em memória não volátil. Essa é uma pequena memória na qual cada byte pode ser gravado e apagado separadamente, enquanto a memória flash é apagada apenas por setores.
Não há EEPROM no ESP32, mas geralmente há 4 Mb de memória flash na qual você pode criar partições para o firmware do controlador ou para armazenar dados do usuário. As seções para dados do usuário são de vários tipos - NVS, FATFS, SPIFFS. Ele deve ser selecionado com base no tipo de dado destinado à gravação.
Porque todos os dados que estão sendo escritos neste projeto são do tipo Int, escolhi NVS - Non-Volitile Storage. Esse tipo de partição é adequado para armazenar dados pequenos, geralmente substituíveis. Para entender o porquê, você deve aprofundar um pouco a organização do NVS.
Como a EEPROM e o FLASH, existem restrições na substituição de dados, os bytes na EEPROM podem ser substituídos de 100.000 a 1.000.000 de vezes e o setor FLASH é o mesmo. Se gravarmos dados uma vez por segundo, obteremos 60 segundos x 60 minutos x 24 horas = 86.400 vezes / dia. Ou seja, nesse modo, o byte durará 11 dias, o que é um pouco. Após o qual o byte ficará indisponível para escrita e leitura.
Para amenizar esse problema, as funções update()
put()
da biblioteca EEPROM do Arduino gravam dados apenas quando são alteradas. Ou seja, você pode escrever a cada segundo algumas configurações e códigos de modo que mudam muito raramente.
O NVS usa um método diferente de controlar o nivelamento do desgaste. Como mencionado acima, os dados no setor flash podem ser gravados em partes, mas somente o setor inteiro pode ser apagado. Portanto, a gravação de dados no NVS é realizada em um tipo de diário, este diário é dividido em páginas, que são colocadas em um setor da memória flash. Os dados são gravados em pares de chave: valor. De fato, é ainda mais fácil do que com a EEPROM, porque trabalhar com um nome significativo é mais fácil do que com um endereço na memória. Upd: o tamanho da chave não ultrapassa 15 caracteres!
Se você primeiro escrever o valor 1
na tecla somekey
tecla e depois escrever o valor 2
na mesma chave, o primeiro valor não será excluído, será marcado apenas como excluído (Apagado) e uma nova entrada será adicionada ao log:

Se você tentar ler os dados com somekey
último valor dessa chave será retornado. Porque Como o log é comum, os valores das diferentes chaves são armazenados próximos um do outro à medida que são gravados.
A página tem um status Vazio - vazio, sem entradas, dados ativos estão sendo gravados no momento, Cheio - está cheio, você não pode gravar nele. Assim que a página fica sem espaço, ela é de
Ativo vai para Cheio e a próxima página Vazia se torna Ativa.

Tanto quanto eu entendi na documentação no site da Espressif e em vários fóruns, a limpeza de páginas começa quando as páginas gratuitas chegam ao fim. Para ser mais preciso, de acordo com isso , o apagamento ocorrerá quando restar apenas 1 página grátis.
Se a página precisar ser limpa, os registros atuais (Não Apagados) serão movidos para outra página e a página será substituída.
Assim, a operação de gravação e apagamento para cada página específica é bastante rara, quanto mais páginas - com menos frequência. Com base nisso, aumentei o tamanho da partição NVS para 1 MB. Na minha taxa de gravação, isso é suficiente por 170 anos, o que geralmente é suficiente. O redimensionamento da seção NVS será o próximo.
Para um trabalho conveniente com o NVS, o ESP32 para Arduino Core possui uma útil biblioteca de Preferências escrita, como trabalhar com ele está aqui .
Um pouco sobre o VisualGDB
Assim que comecei a trabalhar com o Arduino IDE, fiquei imediatamente surpreso com a funcionalidade miserável em comparação com o Visual Studio. Eles dizem que o VS também não é uma fonte, embora me convenha, mas escrever algo com mais de 50 linhas no IDE do Arduino é dolorosamente doloroso e dolorosamente longo. Assim, surgiu a questão de escolher um IDE para o desenvolvimento. Porque Estou familiarizado com o VS, estabeleci-me no VisualGDB .
Após o IDE do Arduino, o desenvolvimento do ESP32 é simplesmente um refúgio. Qual é a transição para a definição, a busca por chamadas no projeto e a capacidade de renomear a variável.
Alterando a tabela de partição ESP32 com o VisualGDB
Como mencionado acima, a tabela pode ser alterada com a partição ESP32; consideraremos como isso pode ser feito.
A tabela é editada como um arquivo CSV, por padrão, o VisualGDB grava a seguinte tabela:
Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x140000, app1, app, ota_1, 0x150000,0x140000, spiffs, data, spiffs, 0x290000,0x170000,
Aqui vemos uma seção no NVS, duas seções para aplicativos e mais algumas seções. Das nuances, é possível notar que o app0 (seu aplicativo) deve sempre ser gravado no deslocamento 0x10000, iniciando no endereço zero, caso contrário, o carregador de inicialização não o detectará. Além disso, as compensações devem ser selecionadas para que as seções não se "sobreponham". A tabela de partição em si é gravada no deslocamento 0x8000. Como você pode ver, o tamanho do NVS neste caso é 0x5000 - 20KB, o que não é muito.
Modifiquei a tabela de partição da seguinte maneira:
Name, Type, SubType, Offset, Size, Flags app0, app, ota_0, 0x10000, 0x140000, nvs, data, nvs, , 1M, otadata, data, ota, , 0x2000, spiffs, data, spiffs, , 0x170000,
Não se esqueça de adicionar uma grade antes de Name; se você usar esta tabela, precisará que esta linha seja considerada um comentário.
Como você pode ver, o tamanho do NVS é aumentado para 1 MB. Se você não especificar compensações, a seção começará imediatamente após a anterior, portanto, basta indicar o deslocamento apenas para app0. Os arquivos CSV podem ser editados no bloco de notas como txt e altere a permissão para csv para o arquivo salvo.
Em seguida, a tabela de partição deve ser convertida em um binário, porque entra no controlador neste formulário. Para fazer isso, execute o conversor:
c:\Users\userName\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\tools\gen_esp32part.exe part_table_name.csv part_table_name.bin
. O primeiro parâmetro é o seu CSV, o segundo parâmetro é o binário de saída.
O binário resultante deve ser colocado em c:\Users\userName\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\tools\partitions\part_table_name.csv
, após o qual é necessário especificar que foi ele quem foi levado para criar a solução e nenhuma tabela de partição padrão. Você pode fazer isso escrevendo o nome da sua tabela no arquivo c:\Users\userName\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.3\boards.txt
. No meu caso, este é esp32doit-devkit-v1.build.partitions=part_table_name
Após essas manipulações, o VisualGDB ao criar o aplicativo pegará exatamente sua tabela de partições e a colocará em
~project_folder_path\Output\board_name\Debug\project_name.ino.partitions.bin
, de onde ele já será despejado no quadro.
Depurador JTAG CJMC-FT232H
Até onde eu sei, este é o depurador mais barato que pode funcionar com o ESP32, custou-me cerca de 600 rublos, existem muitos no Aliexpress.

Quando você conecta o depurador, o Windows instala nele drivers inadequados que precisam ser alterados usando o programa Zadig, tudo é simples lá, não vou descrevê-lo.
Ele se conecta ao ESP32 devkit-v1 da seguinte maneira:
FT232H - ESP32
AD0 - GPIO13
AD1 - GPIO12
AD2 - GPIO15
AD3 - GPIO14
Em seguida, em Project -> VisualGDB Project Properties
é necessário fazer as seguintes configurações:

Depois clique em Teste. Às vezes, acontece que a conexão não é estabelecida na primeira vez, o processo parece congelar e é necessário interromper e repetir o teste. Se tudo estiver em ordem, o processo de teste da conexão leva cerca de 5 segundos.
Normalmente, montei o projeto e o carreguei via USB para o próprio ESP32 (não pelo depurador), após o qual comecei a depuração usando Debug -> Attach to Running Embedded Firmware
. No código, você pode definir pontos de interrupção, examinar os valores das variáveis no momento da quebra e, na janela Debug -> Windows -> Threads
, é possível ver em qual código do FreeRTOS o código parou, o que é útil se ocorrer um erro durante a depuração. Essas funções do depurador foram suficientes para eu trabalhar confortavelmente.
Quando comecei a trabalhar com o NVS, a depuração era constantemente interrompida por erros obscuros. Pelo que entendi, isso ocorre porque o depurador precisa criar algo como um despejo na seção NVS padrão, mas neste momento o NVS já está sendo usado pelo controlador. Obviamente, isso pode ser contornado com a criação de 2 partições NVS, uma com o nome padrão para depuração e a outra para suas próprias necessidades. Mas não havia nada complicado lá, no código adicionado, funcionou pela primeira vez, então eu não o verifiquei.
Falhas ESP32
Como qualquer dispositivo com o Aliexpress, minha placa ESP32 tinha a sua, mas não foi descrita em nenhum lugar. Quando ela chegou, alimentei alguns periféricos que trabalhavam no I2C a partir da placa, mas depois de algum tempo, a placa começou a reiniciar se algum equipamento em uso ou mesmo um capacitor estivesse conectado à perna de + 5V. Por que isso é completamente incompreensível.
Agora, alimento a placa da carga chinesa 0.7A, os sensores ds18b20 do pé da placa 3.3V e o relé e o sensor de movimento de outra carga de 2A. Obviamente, o pé GND da placa está conectado aos pinos GND do restante do ferro. Barato e alegre é a nossa opção.
Sobre os resultados do projeto
Tive a oportunidade de controlar com flexibilidade o aquecimento da casa, economizando dinheiro e suor ganhos. No momento, se o aquecimento mantiver 23 graus o dia todo a -5 - -7 fora, será algo em torno de 11 horas de operação da caldeira. Se durante o dia para manter 20 graus e quente para 23 apenas à noite, então já são 9 horas de operação da caldeira. A capacidade da caldeira é de 6 kW, com um preço atual de quilowatts de 2,2 rublos, ou seja, cerca de 26,4 rublos por dia. A duração da estação de aquecimento em nossa área é de 200 dias, a temperatura média na estação de aquecimento é de apenas cerca de -5 graus. Assim, obtemos cerca de 5000r de economia para a estação de aquecimento.
O custo do equipamento não excede 2000r, ou seja, os custos serão repelidos em alguns meses, sem mencionar o fato de que um sistema pronto dessa automação custaria pelo menos 20.000r. Outra coisa é que passei cerca de uma semana de puro tempo de trabalho escrevendo firmware e depuração, mas, no decorrer do trabalho, por exemplo, finalmente percebi o que os ponteiros estão em C ++ e tive muitas outras experiências (por exemplo, a experiência de muitas horas de depuração de falhas incompreensíveis). E é difícil superestimar a experiência, como você sabe.
Capturas de tela do aplicativo móvel Blynk:



Obviamente, o código no projeto não é uma obra-prima, mas eu o escrevi nas condições de falta de tempo e concentrei-me principalmente na legibilidade. Simplesmente não há tempo para refatorar. Em geral, tenho muitas desculpas por que meu código é tão assustador, mas esse é o meu favorito, por isso vou me debruçar sobre ele, não vou aprofundar o assunto.
Se meu rabisco ajudar alguém, ficarei sinceramente feliz. Ficarei feliz em quaisquer comentários e sugestões.