пʼятниця, 8 січня 2021 р.

Запускаємо FreeRTOS на ESP32. Створення задач. Видалення задач. Призупинення виконання задачі і її відновлення. Семафори MUTEX - справжня бійня за периферію

 Передмова


Оволодіння вмінням працювати з FreeRTOS надає великі переваги в проектах на мікроконтролерах. Двох-ядерний  мікроконтролер ESP32 дозволяє з легкістю спробувати цю операційну систему для мікроконтролерів. Складемо макет та напишемо невеличкий приклад коду. Де будемо надсилати дані для різних пристроїв по одному SPI з різних задач, використаємо семафор, створимо декілька задач, призупинимо/відновимо роботу задачі, видалимо задачу з пам'яті.

Приклад

Складіть макет схеми як на малюнку. Створімо новий проект в Platformio, дамо проекту назву "ESP32_HC595_MAX7219_FreeRTOS". Для роботи з драйвером "max7219" завантажте цю бібліотеку, та додайте її до свого проекту, який щойно створили. Тепер можемо додати демонстраційний код:

#include <Arduino.h>
#include <SPI.h>
#include "max7219.h"

#define CS_HC595    5
#define CS_MAX7219  15
#define KEY_PIN     0
#define LED_BLUE    2

#define CS_HC595_SET()  digitalWrite(CS_HC595, LOW)
#define CS_HC595_RESET()  digitalWrite(CS_HC595, HIGH)

void Task0(void* parameters);
void Task1(void* parameters);
void Task2(void* parameters);
void Task3(void* parameters);
void Task4(void* parameters);
void Task5(void* parameters);

bool keyFlag = false;
bool task4Active = true;

TaskHandle_t Task2Handle = NULL;

SemaphoreHandle_t mutexSPI;
SemaphoreHandle_t mutexSerial;

Max7219 max7219 = Max7219(CS_MAX7219, 8, 9);

void setup() 
{
  Serial.begin(115200);
  Serial.println("Serial is OK!");
  SPI.begin(18, 21, 23);
  max7219.Begin();

  mutexSPI = xSemaphoreCreateMutex();
  mutexSerial = xSemaphoreCreateMutex();

  xTaskCreate(Task0, "Task0", 2048, NULL, 1, NULL);
  xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 1, NULL, 0);
  xTaskCreatePinnedToCore(Task2, "Task2", 2048, NULL, 1, &Task2Handle, 1);
  xTaskCreate(Task3, "Task3", 2048, NULL, 2, NULL);
  xTaskCreate(Task4, "Task4", 2048, NULL, 1, NULL);
  xTaskCreate(Task5, "Task5", 2048, NULL, 1, NULL);
}

void loop() 
{
  
}

void Task0(void* parameters)
{
  xSemaphoreTake(mutexSerial, portMAX_DELAY);
  Serial.printf("[%8lu] Run Task0 only once: %d\r\n", millis(), xPortGetCoreID());
  xSemaphoreGive(mutexSerial);

  vTaskDelete(NULL);
}

void Task1(void* parameters)
{
  //TODO: код що виконується раз при запускі задачі
  pinMode(CS_HC595, OUTPUT);
  CS_HC595_RESET();
  
  while (1)
  {
    //TODO: код що виконується безкінечно
    for (size_t i = 0; i < 8; i++)
    {
      xSemaphoreTake(mutexSerial, portMAX_DELAY);
      Serial.printf("[%8lu] Run Task1 on Core: %d\r\n", millis(), xPortGetCoreID());
      xSemaphoreGive(mutexSerial);
      
      xSemaphoreTake(mutexSPI, portMAX_DELAY);
      CS_HC595_SET();
      SPI.transfer(1 << i);
      CS_HC595_RESET();
      xSemaphoreGive(mutexSPI);

      vTaskDelay(500 / portTICK_PERIOD_MS);
    }
  }
}

void Task2(void* parameters)
{
  while (1)
  {
    max7219.Clean();
    max7219.DecodeOn();

    xSemaphoreTake(mutexSerial, portMAX_DELAY);
    Serial.printf("[%8lu] Run Task2 on Core: %d\r\n", millis(), xPortGetCoreID());
    xSemaphoreGive(mutexSerial);
    
    xSemaphoreTake(mutexSPI, portMAX_DELAY);
    max7219.PrintNtos(8, millis(), 8);
    xSemaphoreGive(mutexSPI);

    vTaskDelay(973 / portTICK_PERIOD_MS);
  }  
}

void Task3(void* parameters)
{
  pinMode(KEY_PIN, INPUT);

  while (1)
  {
    if(!digitalRead(KEY_PIN) && !keyFlag)
    {
      keyFlag = true;

      xSemaphoreTake(mutexSerial, portMAX_DELAY);
      Serial.printf("[%8lu] KEY is pressed!\r\n", millis());
      xSemaphoreGive(mutexSerial);      
    }

    if (digitalRead(KEY_PIN) && keyFlag)
    {
      keyFlag = false;

      xSemaphoreTake(mutexSerial, portMAX_DELAY);
      Serial.printf("[%8lu] KEY is unpressed!\r\n", millis());
      xSemaphoreGive(mutexSerial);

      if (task4Active)
      {
        task4Active = false;
        vTaskSuspend(Task2Handle);
      }
      else
      {
        task4Active = true;        
        vTaskResume(Task2Handle);
      }           
    }

    vTaskDelay(50 / portTICK_PERIOD_MS);
  }
  
}

void Task4(void* parameters)
{ 
  uint8_t brightness[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 
                           0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01 };
  
  while (1)
  {
      for (size_t i = 0; i < sizeof(brightness) / sizeof(brightness[0]); i++)
      {
        xSemaphoreTake(mutexSPI, portMAX_DELAY);
        max7219.SetIntensivity(brightness[i]);
        xSemaphoreGive(mutexSPI);

        vTaskDelay(150 / portTICK_PERIOD_MS); 
      }            
  }  
}

void Task5(void* parameters)
{
  ledcSetup(0, 5000, 8);
  ledcAttachPin(LED_BLUE, 0);

  uint8_t n = 0;
  bool direction = false;

  while (1)
  {  
    if (!direction && n == 255)    
    {
      direction = true;
    }
    
    if (direction && n == 0)
    {
      direction = false;
    }
    
    ledcWrite(0, n);

    if (!direction)
    {
      n++;
    }
    else
    {
      n--;
    }
    
    vTaskDelay(10 / portTICK_PERIOD_MS); 
  }
}

Компілюємо, заливаємо, відкриваємо Serial-monitor, спостерігаємо за роботою.

Файли

Файли прикладів можна завантажити з репозиторію GitHub

Відео-посібник



середа, 16 грудня 2020 р.

Blynk: підключення до точки WiFi та серверу Blynk, очікування WiFi точки, перепідключення до WiFi та серверу Blynk, робота основного коду без блокування при очікування з'єднання для ESP32/ESP8266

Передмова

Помітив, що багато початківців, які хочуть створити для себе IoT пристрій на базі WiFi мікроконтролера ESP8266 або ESP32 з системою Blynk, стикаються з такою проблемою як:

  • не виконання коду, поки пристрій не підключиться до точки WiFi;
  • відсутность спроб відновити з'єднання у разі втрати зв'язку з мережею;
  • блокування коду при втраті зв'язку з сервером Blynk.
Як не вирішити ці проблеми пристрій і залишиться поробкою вихідного дня для спробувати. Якийсь путній і завершений пристрій створити з таким підходом неможливо. Бібліотека Blynk потужна і універсальна бібліотека, до якої входять і інші бібліотеки, такі як робота з таймерами, датою, керування підключеннями, і багато іншого. Але! Без власного, продуманого коду створити завершений пристрій, який можна вже і виводити на ринок, або хоча б, щоб він був зручний для власного користування, для дому, для сім'ї - не побудувати. Так що пропоную все ж таки попрацювати над власним кодом, який працює з мережею, та усуває недолік блокування власного коду у разі втрати з'єднання з сервером Blynk. Та досить балачок, давайте розпочнемо, бо насправді не все так складно.

Проблематика

Ми можемо взяти за основу будь який приклад Blynk і вже навколо нього "наростити" свій функціонал. Але це буде не зовсім вірно. Тому пишемо в першу чергу код, який буде, наш модуль WiFi, завжди тримати на зв'язку з точкою WiFi. Та при втраті підключення до Blynk зробити логіку, яка дозволить не блокувати основний код, та періодично намагатись під'єднатись до серверу Blynk.

Поширений приклад для підключення до WiFi нам пропонують в такому вигляді:

#include <ESP8266WiFi.h>

void setup()
{
  Serial.begin(115200);
  Serial.println();

  WiFi.begin("network-name", "pass-to-network");

  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {}

В цьому прикладі далі циклу while (WiFi.status() != WL_CONNECTED), поки не буде з'єднання з точкою WiFi, програма не виконається. Нам потрібно, щоб спроби приєднатись до точки WiFi були постійними, а код в циклі loop() виконувався та контролював стан з'єднання з точкою WiFi. 

А поширений приклад для підключення до Blynk нам пропонують в такому вигляді:

#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "YourAuthToken";

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "YourNetworkName";
char pass[] = "YourPassword";

void setup()
{
  // Debug console
  Serial.begin(9600);

  Blynk.begin(auth, ssid, pass);
  // You can also specify server:
  //Blynk.begin(auth, ssid, pass, "blynk-cloud.com", 80);
  //Blynk.begin(auth, ssid, pass, IPAddress(192,168,1,100), 8080);
}

void loop()
{
  Blynk.run();
  // You can inject your own code or combine it with other sketches.
  // Check other examples on how to communicate with Blynk. Remember
  // to avoid delay() function!
}

В цьому випадку, при відсутності WiFi ваш пристрій так і буде "мертвою залізякою", а у разі наявності WiFi і втрати зв'язку з сервером Blynk, ваш код блокуватиметься функцією Blynk.run() яка викликається з циклу loop() і користуватись пристроєм буде не можливо. 

Приклад рішення для ESP8266

Я пропоную такий варіант коду для підтримки зв'язку з точкою WiFi та сервером Blynk без блокування основного коду:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include "BlynkSimpleEsp8266.h"

const char* ssid        = "MY_SSID";
const char* password    = "MY_PASS";
const char* blynkToken  = "MY_TOKEN";

bool isWiFiConnected = false;
int  numTimerReconnect = 0;

WiFiEventHandler gotIpEventHandler;
WiFiEventHandler disconnectedEventHandler;
BlynkTimer timer;

void WiFiStationConnected(const WiFiEventStationModeGotIP& event);
void WiFiStationDisconnected(const WiFiEventStationModeDisconnected& event);
void ReconnectBlynk(void);
void BlynkRun(void);

void setup() 
{
  Serial.begin(115200);
  
  gotIpEventHandler = WiFi.onStationModeGotIP(&WiFiStationConnected);
  disconnectedEventHandler = WiFi.onStationModeDisconnected(&WiFiStationDisconnected);

  WiFi.begin(ssid, password);

  Blynk.config(blynkToken);

  if(Blynk.connect())
  {
    Serial.printf("[%8lu] setup: Blynk connected\r\n", millis());
  }
  else
  {
    Serial.printf("[%8lu] setup: Blynk no connected\r\n", millis());
  }
  
  Serial.printf("[%8lu] Setup: Start timer reconnected\r\n", millis());
  numTimerReconnect = timer.setInterval(60000, ReconnectBlynk);
}

void loop() 
{
  BlynkRun();  
  timer.run();
}

void WiFiStationConnected(const WiFiEventStationModeGotIP& event)
{
  isWiFiConnected = true;
  Serial.printf("[%8lu] Interrupt: Connected to AP, Ip: ", millis());
  Serial.println(WiFi.localIP());
}

void WiFiStationDisconnected(const WiFiEventStationModeDisconnected& event)
{
  isWiFiConnected = false;
  Serial.printf("[%8lu] Interrupt: Disconnected to AP!\r\n", millis());
}

void BlynkRun(void)
{
  if (isWiFiConnected)
  {
    if(Blynk.connected())
    {
      if (timer.isEnabled(numTimerReconnect))
      {
        timer.disable(numTimerReconnect);
        Serial.printf("[%8lu] BlynkRun: Stop timer reconnected\r\n", millis());
      }

      Blynk.run();
    }
    else
    {
      if (!timer.isEnabled(numTimerReconnect))
      {
        timer.enable(numTimerReconnect);
        Serial.printf("[%8lu] BlynkRun: Start timer reconnected\r\n", millis());
      }      
    }
  }
}

void ReconnectBlynk(void)
{
  if (!Blynk.connected())
  {
    if (Blynk.connect())
    {				        
      Serial.printf("[%8lu] ReconnectBlynk: Blynk reconnected\r\n", millis());
    }
    else
    {
      Serial.printf("[%8lu] ReconnectBlynk: Blynk not reconnected\r\n", millis());
    }
  }
  else
  {
    Serial.printf("[%8lu] ReconnectBlynk: Blynk connected\r\n", millis());
  }
}


Приклад рішення для ESP32

Приклад для ESP32 дещо відрізняється з попереднім для ESP8266, але логіка роботи залишається такою самою:

#include <Arduino.h>
#include <WiFi.h>
#include "BlynkSimpleEsp32.h"

const char* ssid        =  "MY_SSID";
const char* password    =  "MY_PASS";
const char* blynkToken  =  "MY_TOKEN";

bool isWiFiConnected = false;
int  numTimerReconnect = 0;

BlynkTimer timer;

void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info);
void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
void ReconnectBlynk(void);
void BlynkRun(void);

void setup() 
{
  Serial.begin(115200);

  WiFi.onEvent(WiFiStationConnected, SYSTEM_EVENT_STA_GOT_IP);
  WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED);
  WiFi.begin(ssid, password);

  Blynk.config(blynkToken);

  if(Blynk.connect())
  {
    Serial.printf("[%8lu] setup: Blynk connected\r\n", millis());
  }
  else
  {
    Serial.printf("[%8lu] setup: Blynk no connected\r\n", millis());
  }

  Serial.printf("[%8lu] Setup: Start timer reconnected\r\n", millis());
  numTimerReconnect = timer.setInterval(60000, ReconnectBlynk);
}

void loop() 
{
  BlynkRun();
  timer.run();
}

void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info)
{
  isWiFiConnected = true;
  Serial.printf("[%8lu] Interrupt: Connected to AP, IP: ", millis());
  Serial.println(WiFi.localIP());
}

void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info)
{
  isWiFiConnected = false;
  Serial.printf("[%8lu] Interrupt: Disconnected to AP!\r\n", millis());
}

void ReconnectBlynk(void)
{
  if (!Blynk.connected())
  {
    if (Blynk.connect())
    {				        
      Serial.printf("[%8lu] ReconnectBlynk: Blynk reconnected\r\n", millis());
    }
    else
    {
      Serial.printf("[%8lu] ReconnectBlynk: Blynk not reconnected\r\n", millis());
    }
  }
  else
  {
    Serial.printf("[%8lu] ReconnectBlynk: Blynk connected\r\n", millis());
  }
}

void BlynkRun(void)
{
  if (isWiFiConnected)
  {
    if(Blynk.connected())
    {
      if (timer.isEnabled(numTimerReconnect))
      {
        timer.disable(numTimerReconnect);
        Serial.printf("[%8lu] BlynkRun: Stop timer reconnected\r\n", millis());
      }

      Blynk.run();
    }
    else
    {
      if (!timer.isEnabled(numTimerReconnect))
      {
        timer.enable(numTimerReconnect);
        Serial.printf("[%8lu] BlynkRun: Start timer reconnected\r\n", millis());
      }      
    }
  }
}

Розумію, що для початківця це доволі складний код і потребує детальних пояснень. Тому  я підготував відео з поясненням і демонстрацією роботи з кнопкою, світлодіодом і таймером. А також демонстраційний код доступний і для завантаження і для ESP8266 , і для ESP32. Приємного перегляду.

Відео з поясненням і демонстрацією



середа, 25 листопада 2020 р.

Бібліотека драйвера MAX7219 в Arduino framework

 Передмова

Бібліотека для роботи з семисегментним-восьмирозрядним модулем з драйвером на мікросхемі max7219. Дозволяє друкувати окремі цифри-символи в окремих розрядах, друкувати цілі числа, цілі числа певної ширини, числа з плаваючою комою, засвічувати окремі сегменти. Бібліотека написана в межах Arduino framework, тому має працювати на будь якому чипові який підтримує Arduino framework (але це не точно).
Модуль 7 сегментного 8 розрядного дисплею на max7219

Приклад використання

Завантажуєте бібліотеку з GitHub репозиторію. Додаєте до свого проекту файли бібліотеки. Вставляєте до головного файлу вашого проекту цей приклад:

#include <Arduino.h>
#include <SPI.h>
#include "max7219.h"

// 15 - GPIO15 (CS), 8 - number of digits, 5 - intensivity (brightness)
Max7219 max7219 = Max7219(15, 8, 5);

/* ESP32 SPI
  SCK   - GPIO_18
  MISO  - GPIO_21
  MOSI  - GPIO_23
*/

void setup() 
{
  SPI.begin(18, 21, 23);
  max7219.Begin();
}

void loop() 
{
  max7219.DecodeOn();
  max7219.SetIntensivity(5);
  
  max7219.PrintNtos(max7219.DIGIT_7, 1234, 6);
  delay(1000);
  max7219.Clean();

  max7219.PrintItos(max7219.DIGIT_4, 5678);
  delay(1000);
  max7219.Clean();

  max7219.PrintFtos(max7219.DIGIT_8, -45.678f, 2);
  delay(1000);
  max7219.Clean();

  for (uint8_t i = 0; i < 8; i++)
  {
    max7219.PrintDigit(i + 1, i + 1, false);
    delay(100);
  }

  for (uint8_t i = 0x0F; i > 0x00; i--)
  {
    max7219.SetIntensivity(i);
    delay(100);
  }
  
  for (uint8_t i = 0; i < 0x0F; i++)
  {
    max7219.SetIntensivity(i);
    delay(100);
  }
  
  for (uint8_t i = 8; i > 0 ; i--)
  {
    max7219.PrintItos(i, 87654321);
    delay(200);
    max7219.Clean();  
  }
  
  max7219.Clean();
  delay(1000);
}

Відео демострація



понеділок, 5 жовтня 2020 р.

Прошивка ESP32 власною firmware за допомоги Flash Download Tools

Передмова

Коли ви створили власну прошивку для ESP32, наприклад в Platformio, чи Arduino IDE. І вам потрібно передати бінарний код для прошивки пристроїв ESP32 іншим людям, не розголошуючи сирцевий код, або прошивати серію власних пристроїв. То IDE не підходить для цих цілей. Краще і правильно користуватись фірмовою утилітою "Flash Download Tools". Як це зробити, читаємо далі в статті.

Завантаження і встановлення

Щоб завантажити утиліту, перейдіть за цією ланкою

Завантажити FLASH DOWNLOAD TOOL

Теку з утилітою потрібно витягнути з архіву і розмістити в зручне для вас місце на диску ПК. Але зауважу, шлях до утиліти не має містити кириличних літер. Лише латиницею.

Запуск утиліти:

Запуск утиліти

Обирайте "Developer Mode":

Оберіть "Developer Mode"

Оберіть тип чипу, в мене ESP32:

Вибір чипу

Тепер з'явиться головне вікно прошивальщика:

Додаємо файли і налаштування

Треба додати 4 файли і призначити адреси розташування в пам'яті модуля:

  • 0x1000 bootloader_dio_40m.bin
  • 0x8000 partitions.bin
  • 0xe000 boot_app0.bin
  • 0x10000 firmware.bin
Крім власного бінарного файла прошивки, що ми зкомпілювали firmware.bin нам протрібні ще три файли:
  • bootloader file
  • partition table file
  • firmware/app file
І де їх взяти?

Arduino IDE

Підготовка файлів для прошивання в Arduino IDE:
Підготовка файлів
Після компіляції створіть окрему папку для всіх чотирьох файлів де вам зручно. Перший файл "firmware.bin" буде розташований в теці скетча. Можна перейти з меню Arduino IDE "Скетч -> Показати теку скетчів". Або перейти за шляхом: "C:\Users\{user}\Documents\Arduino\{назва_вашого_скетчу}. Щоб добути інші файли, в файловому провідникові, потрібно дозволити перегляд прихованих тек. І так, розташування всіх файлів:
C:\Users\"Користувач"\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\partitions\boot_app0.bin
C:\Users\"Користувач"\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\sdk\bin\bootloader_dio_40m.bin
C:\Users\"Користувач"\AppData\Local\Temp\arduino_build_491506м'я_вашого_скетчу.ino.bin
C:\Users\"Користувач"\AppData\Local\Temp\arduino_build_491506м'я_вашого_скетчу.ino.partitions.bin
Кладемо всі ці чотири файли до теки, яку вже заздалегідь приготували і вказуємо їх для "FLASH DOWNLOAD TOOL". Або архівуємо теку і відправляємо третій стороні для прошивання.

Адреси для цих файлів для "FLASH DOWNLOAD TOOL", як вже було зазначено вище, мають бути такі:
  • 0x1000 bootloader_dio_40m.bin
  • 0x8000 Ім'я_вашого_скетчу.ino.partitions.bin
  • 0xe000 boot_app0.bin
  • 0x10000 Ім'я_вашого_скетчу.ino.bin

Platformio

Спершу підготуємо потрібні файли. В терміналі вашого проекту виконайте цей рядок:
pio run -v -t upload

Підготовка файлів


Після роботи компілятора і прошивача створяться всі потрібні файли за таким шляхом:
0x1000 C:\Users\"Ім'я_користувача"\.platformio\packages\framework-arduinoespressif32\tools\sdk\bin\bootloader_dio_40m.bin
0xe000 C:\Users\"Ім'я_користувача"\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin
0x8000 .pio\build\esp32dev\partitions.bin
0x10000 .pio\build\esp32dev\firmware.bin 
В файловому провідникові, потрібно дозволити перегляд прихованих тек. Файл partitions.bin та firmware.bin знаходяться в прихованій теці .pio вашого поточного проекту.

Кладемо всі ці чотири файли до теки, яку вже заздалегідь приготували і вказуємо їх для "FLASH DOWNLOAD TOOL". Або архівуємо теку і відправляємо третій стороні для прошивання.

Адреси для цих файлів для "FLASH DOWNLOAD TOOL", як вже було зазначено вище, мають бути такі:
  • 0x1000 bootloader_dio_40m.bin
  • 0x8000 partitions.bin
  • 0xe000 boot_app0.bin
  • 0x10000 firmware.bin

VisualMicro for MS Visual Studio

Хто використовує розширення ARDUINO IDE FOR VISUAL STUDIO, то добути потрібні файли можна за такими шляхами, як шлях до вашого проекту і тека "Debug" або "Release", де будуть два файли "назва_проекту.bin" та "назва_проекту.partitions.bin", а ще два файли беруться там де і для Arduino IDE файли bootloader_dio_40m.bin та boot_app0.bin. Наприклад для проекту "blink1":
C:\Users\"Ім'я_користувача"\source\repos\Blink1\Blink1\Release\Blink1.ino.bin
C:\Users\"Ім'я_користувача"\source\repos\Blink1\Blink1\Release\Blink1.partitions.bin
C:\Users\"Ім'я_користувача"\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\partitions\boot_app0.bin
C:\Users\"Ім'я_користувача"\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\sdk\bin\bootloader_dio_40m.bin
Кладемо всі ці чотири файли до теки, яку вже заздалегідь приготували і вказуємо їх для "FLASH DOWNLOAD TOOL". Або архівуємо теку і відправляємо третій стороні для прошивання.

Адреси для цих файлів для "FLASH DOWNLOAD TOOL", як вже було зазначено вище, мають бути такі:
  • 0x1000 bootloader_dio_40m.bin
  • 0x8000 Ім'я_вашого_проекту.ino.partitions.bin
  • 0xe000 boot_app0.bin
  • 0x10000 Ім'я_вашого_проекту.ino.bin

четвер, 19 вересня 2019 р.

Blynk: шаблон-конструктор для створення прошивок з WiFi Manager, WebOTA та автоматичним перепідключенням до вашого WiFi і серверу Blynk

Передмова

Для зручності створив початковий шаблон-конструктор прошивки для системи Blynk з початковими налаштуваннями по WiFi зі смартфону (SSID, PASS, TOKEN, Name Device). З можливістю оновлювати прошивку по повітрю WebOTA, та перепідключенням до мережі WiFi і серверу Blynk у разі пропадання зв'язку. Плюс, якщо зв'язок буде втрачено, функціонал самого пристрою не буде "гальмувати", а буде працювати в режимі OFFLINE як слід, до наступного підключення до серверу. Є обробка натискання системної кнопки "FLASH" яка під'єднана до GPIO_0 і зазвичай присутня на всіх платах на базі "ESP8266" і пристроїв "IoT", наприклад, дуже популярних "Sonoff". Кнопка розпізнає довжину натискання. Коротке натискання - для свого коду, який буде щось вмикати/вимикати. Довге натискання від 5 до 10 секунд - перезавантаження пристрою. Довге натискання понад 10 секунд - скидання всіх налаштувань.

Код шаблону

Код шаблону можна завантажити на GitHub, Dropbox,  або скопіювати прямо звідси:

/*
 Name:  Blynk_Table_Menu.ino
 Created: 9/19/2019 9:04:46 AM
 Author: Andriy Honcharenko
*/

/* CODE BEGIN Includes */
#include <BlynkSimpleEsp8266.h>
#include <Ticker.h>
#include <EEPROM.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
#include <WiFiManager.h>
/* CODE END Includes */

/* CODE BEGIN UD */
/* User defines ---------------------------------------------------------*/
#define BLYNK_PRINT Serial

#define NAME_DEVICE      "MyHomeIoT-ESP8266"

#define BUTTON_SYS0_PIN     0
#define LED_SYS_PIN      13

#define BUTTON_SYS_B0_VPIN    V20
#define WIFI_SIGNAL_VPIN    V80

#define INTERVAL_PRESSED_RESET_ESP  3000L
#define INTERVAL_PRESSED_RESET_SETTINGS 5000L
#define INTERVAL_PRESSED_SHORT   50
#define INTERVAL_SEND_DATA    30033L
#define INTERVAL_RECONNECT    60407L
#define INTERVAL_REFRESH_DATA   4065L
#define WIFI_MANAGER_TIMEOUT   180

#define EEPROM_SETTINGS_SIZE   512
#define EEPROM_START_SETTING_WM   0
#define EEPROM_SALT_WM     12661

#define LED_SYS_TOGGLE()    digitalWrite(LED_SYS_PIN, !digitalRead(LED_SYS_PIN))
#define LED_SYS_ON()     digitalWrite(LED_SYS_PIN, LOW)
#define LED_SYS_OFF()     digitalWrite(LED_SYS_PIN, HIGH)
/* CODE END UD */

/* CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
bool shouldSaveConfigWM  = false; //flag for saving data
bool btnSystemState0  = false;
bool triggerBlynkConnect = false;
bool isFirstConnect   = true; // Keep this flag not to re-sync on every reconnection

int startPressBtn = 0;

//structure for initial settings. It now takes 116 bytes
typedef struct {
 char  host[33] = NAME_DEVICE;    // 33 + '\0' = 34 bytes
 char  blynkToken[33] = "";     // 33 + '\0' = 34 bytes
 char  blynkServer[33] = "blynk-cloud.com"; // 33 + '\0' = 34 bytes
 char  blynkPort[6] = "8442";    // 04 + '\0' = 05 bytes
 int   salt = EEPROM_SALT_WM;    // 04   = 04 bytes
} WMSettings;         // 111 + 1  = 112 bytes (112 this is a score of 0)
//-----------------------------------------------------------------------------------------

WMSettings wmSettings;

BlynkTimer timer;

Ticker tickerESP8266;

//Declaration OTA WebUpdater
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;
/* CODE END PV */

/* CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
static void configModeCallback(WiFiManager* myWiFiManager);
static void saveConfigCallback(void);
static void tick(void);
static void untick(void);
static void readSystemKey(void);
static void timerRefreshData(void);
static void timerSendServer(void);
static void timerReconnect(void);
/* CODE END PFP */

// the setup function runs once when you press reset or power the board

void setup() 
{
 Serial.begin(115200);

 pinMode(BUTTON_SYS0_PIN, INPUT_PULLUP);
 pinMode(LED_SYS_PIN, OUTPUT);

 // Read the WM settings data from EEPROM to RAM
 EEPROM.begin(EEPROM_SETTINGS_SIZE);
 EEPROM.get(EEPROM_START_SETTING_WM, wmSettings);
 EEPROM.end();

 if (wmSettings.salt != EEPROM_SALT_WM) 
 {
  Serial.println(F("Invalid wmSettings in EEPROM, trying with defaults"));
  WMSettings defaults;
  wmSettings = defaults;
 }

 // Print old values to the terminal
 Serial.println(wmSettings.host);
 Serial.println(wmSettings.blynkToken);
 Serial.println(wmSettings.blynkServer);
 Serial.println(wmSettings.blynkPort);

 tickerESP8266.attach(0.5, tick);   // start ticker with 0.5 because we start in AP mode and try to connect

 //Local intialization. Once its business is done, there is no need to keep it around
 WiFiManager wifiManager;

 //reset saved wmSettings
 //wifiManager.resetSettings();

 //set minimu quality of signal so it ignores AP's under that quality
 //defaults to 8%
 //wifiManager.setMinimumSignalQuality();

 //sets timeout before webserver loop ends and exits even if there has been no setup.
 //useful for devices that failed to connect at some point and got stuck in a webserver loop
 //in seconds setConfigPortalTimeout is a new name for setTimeout
 wifiManager.setConfigPortalTimeout(WIFI_MANAGER_TIMEOUT);

 // The extra parameters to be configured (can be either global or just in the setup)
 // After connecting, parameter.getValue() will get you the configured value
 // id/name placeholder/prompt default length
 WiFiManagerParameter custom_device_name_text("<br/>Enter name of the device<br/>or leave it as it is<br/>");
 wifiManager.addParameter(&custom_device_name_text);

 WiFiManagerParameter custom_device_name("device-name", "device name", wmSettings.host, 33);
 wifiManager.addParameter(&custom_device_name);

 WiFiManagerParameter custom_blynk_text("<br/>Blynk config.<br/>");
 wifiManager.addParameter(&custom_blynk_text);

 WiFiManagerParameter custom_blynk_token("blynk-token", "blynk token", wmSettings.blynkToken, 33);
 wifiManager.addParameter(&custom_blynk_token);

 WiFiManagerParameter custom_blynk_server("blynk-server", "blynk server", wmSettings.blynkServer, 33);
 wifiManager.addParameter(&custom_blynk_server);

 WiFiManagerParameter custom_blynk_port("blynk-port", "port", wmSettings.blynkPort, 6);
 wifiManager.addParameter(&custom_blynk_port);

 //set config save notify callback
 wifiManager.setSaveConfigCallback(saveConfigCallback);

 //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
 wifiManager.setAPCallback(configModeCallback);

 //set custom ip for portal
 //wifiManager.setAPStaticIPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0));

 //fetches ssid and pass from eeprom and tries to connect
 //if it does not connect it starts an access point with the specified name
 //here  "AutoConnectAP"
 //and goes into a blocking loop awaiting configuration
 //wifiManager.autoConnect();
 //or use this for auto generated name ESP + ChipID

 if (wifiManager.autoConnect(wmSettings.host))
 {
  //if you get here you have connected to the WiFi
  Serial.println(F("Connected WiFi!"));
 }
 else
 {
  Serial.println(F("failed to connect and hit timeout"));
 }

 untick(); // cancel the flashing LED

 // Copy the entered values to the structure
 strcpy(wmSettings.host, custom_device_name.getValue());
 strcpy(wmSettings.blynkToken, custom_blynk_token.getValue());
 strcpy(wmSettings.blynkServer, custom_blynk_server.getValue());
 strcpy(wmSettings.blynkPort, custom_blynk_port.getValue());

 // Print new values to the terminal
 Serial.println(wmSettings.host);
 Serial.println(wmSettings.blynkToken);
 Serial.println(wmSettings.blynkServer);
 Serial.println(wmSettings.blynkPort);

 if (shouldSaveConfigWM)
 {
  LED_SYS_ON();
  // Write the input to the EEPROM
  EEPROM.begin(EEPROM_SETTINGS_SIZE);
  EEPROM.put(EEPROM_START_SETTING_WM, wmSettings);
  EEPROM.end();
  //---------------------------------
  LED_SYS_OFF();
 }

 // Run OTA WebUpdater
 MDNS.begin(wmSettings.host);
 httpUpdater.setup(&httpServer);
 httpServer.begin();
 MDNS.addService("http", "tcp", 80);
 Serial.printf("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", wmSettings.host);

 // Configure connection to blynk server
 Blynk.config(wmSettings.blynkToken, wmSettings.blynkServer, atoi(wmSettings.blynkPort));

 if (Blynk.connect())
 {
  //TODO: something to do if connected
 }
 else
 {
  //TODO: something to do if you failed to connect
 }

 timer.setInterval(INTERVAL_REFRESH_DATA, timerRefreshData);
 timer.setInterval(INTERVAL_SEND_DATA, timerSendServer);
 timer.setInterval(INTERVAL_RECONNECT, timerReconnect); 
}

// the loop function runs over and over again until power down or reset
void loop() 
{
 if (Blynk.connected())
 {
  Blynk.run(); // Initiates Blynk Server  
 }
 else
 {
  if (!tickerESP8266.active())
  {
   tickerESP8266.attach(2, tick);
  }
 }

 timer.run(); // Initiates BlynkTimer
 
 httpServer.handleClient(); // Initiates OTA WebUpdater  

 readSystemKey();
}

/* BLYNK CODE BEGIN */
BLYNK_CONNECTED()
{
 untick(); 

 Serial.println(F("Blynk Connected!"));
 Serial.println(F("local ip"));
 Serial.println(WiFi.localIP());  

 char str[32];
 sprintf_P(str, PSTR("%s Online!"), wmSettings.host); 
 Blynk.notify(str);

 if (isFirstConnect)
 {
  Blynk.syncAll();
  isFirstConnect = false;
 }
}

BLYNK_WRITE(BUTTON_SYS_B0_VPIN) // Example
{ 
 //TODO: something to do when a button is clicked in the Blynk app
 
 Serial.println(F("System_0 button pressed is App!")); 
}
/* BLYNK CODE END */

/* CODE BEGIN USER FUNCTION */
static void timerRefreshData(void)
{
 //TODO: here are functions for updating data from sensors, ADC, etc ...
}

static void timerSendServer(void)
{
 if (Blynk.connected())
 {
  //TODO: here are the functions that send data to the Blynk server
  Blynk.virtualWrite(WIFI_SIGNAL_VPIN, map(WiFi.RSSI(), -105, -40, 0, 100)); //Example send level WiFi signal
 }
 else
 {
  //TODO:
 }
}

static void timerReconnect(void)
{
 if (WiFi.status() != WL_CONNECTED)
 {
  Serial.println(F("WiFi not connected"));

  if (WiFi.begin() == WL_CONNECTED)
  {
   Serial.println(F("WiFi reconnected"));
  }
  else
  {
   Serial.println(F("WiFi not reconnected"));
  }
 }
 else// if (WiFi.status() == WL_CONNECTED)
 {
  Serial.println(F("WiFi in connected"));

  if (!Blynk.connected())
  {
   if (Blynk.connect())
   {
    Serial.println(F("Blynk reconnected"));
   }
   else
   {
    Serial.println(F("Blynk not reconnected"));
   }
  }
  else
  {
   Serial.println(F("Blynk in connected"));
  }
 }
}

static void configModeCallback(WiFiManager* myWiFiManager)
{
 Serial.println(F("Entered config mode"));
 Serial.println(WiFi.softAPIP());
 //if you used auto generated SSID, print it
 Serial.println(myWiFiManager->getConfigPortalSSID());
 //entered config mode, make led toggle faster
 tickerESP8266.attach(0.2, tick);
}

//callback notifying us of the need to save config
static void saveConfigCallback()
{
 Serial.println(F("Should save config"));
 shouldSaveConfigWM = true;
}

static void tick(void)
{
 //toggle state  
 LED_SYS_TOGGLE();     // set pin to the opposite state
}

static void untick(void)
{
 tickerESP8266.detach();
 LED_SYS_OFF(); //keep LED off 
 
}

static void readSystemKey(void)
{
 if (!digitalRead(BUTTON_SYS0_PIN) && !btnSystemState0)
 {
  btnSystemState0 = true;
  startPressBtn = millis();
 }
 else if (digitalRead(BUTTON_SYS0_PIN) && btnSystemState0)
 {
  btnSystemState0 = false;
  int pressTime = millis() - startPressBtn;

  if (pressTime > INTERVAL_PRESSED_RESET_ESP && pressTime < INTERVAL_PRESSED_RESET_SETTINGS)
  {
   if (Blynk.connected())
   {    
    Blynk.notify(String(wmSettings.host) + F(" reboot!"));
   }
   
   Blynk.disconnect();  
   tickerESP8266.attach(0.1, tick);   
   delay(2000);
   ESP.restart();
  }
  else if (pressTime > INTERVAL_PRESSED_RESET_SETTINGS)
  {
   if (Blynk.connected())
   {        
    Blynk.notify(String(wmSettings.host) + F(" setting reset! Connected WiFi AP this device!"));
   }   

   WMSettings defaults;
   wmSettings = defaults;

   LED_SYS_ON();
   // We write the default data to EEPROM
   EEPROM.begin(EEPROM_SETTINGS_SIZE);
   EEPROM.put(EEPROM_START_SETTING_WM, wmSettings);
   EEPROM.end();
   //------------------------------------------
   LED_SYS_OFF();

   delay(1000);
   WiFi.disconnect();
   delay(1000);
   ESP.restart();
  }
  else if (pressTime < INTERVAL_PRESSED_RESET_ESP && pressTime > INTERVAL_PRESSED_SHORT)
  {   
   Serial.println(F("System button_0 pressed is Device!"));
   // TODO: insert here what will happen when you press the ON / OFF button   
  }
  else if (pressTime < INTERVAL_PRESSED_SHORT)
  {   
   Serial.printf("Fixed false triggering %ims", pressTime);
   Serial.println();
  }  
 }
}
/* CODE END USER FUNCTION */

Можливості

  • При першому, після прошивання, увімкнені пристроєм утворюється точка WiFi з ім'ям "MyHomeIoT-ESP8266". Підключиться до цієї точки своїм смартфоном і відкрийте браузер та перейдіть за адресою 192.168.4.1 до налаштувань;
  • Для прикладу і перевірки на віртуальну шпильку V80 відправляються дані рівня сигналу WiFi. Додайте якийсь віджет "value" і назначте йому віртуальну шпильку V80;
  • Кнопка, яка підключена до GPIO_0 обробляється шаблоном та може реагувати на коротке натискання - вставляєте свій код для увімкнення/вимкнення чогось, або інших дій. Довге натискання кнопки від 5 до 10 секунд - перезавантаження пристрою. Довге натискання понад 10 секунд - скидання всіх налаштувань;
  • Для прикладу є обробка віртуальної кнопки V20. Якщо в BlynkApp додати до проекту кнопку на віртуальну шпильку V20, то пристрій в термінал буде надсилати повідомлення про те що кнопку натиснули з додатку Blynk. Інший код-функціонал на ваш розсуд;
  • Є можливість оновити прошивку по повітрю - WebOTA. Для цього потрібно знати IP пристрою і з будь якого браузера який знаходиться в тій же ж самій мережі набрати "IP_Your_Drvice/update". Наприклад ваш пристрій має IP - 192.168.100.111, тоді в браузер вводимо "192.168.100.111/update", обираємо файл прошивки і тиснемо кнопку "update". 
  • При втраті WiFi мережі, або зв'язку з сервером Blynk, функціонал пристрою не буде "гальмувати" і зв'язок автоматично відновиться при першій можливості;
  • Присутні в шаблоні три таймери для збору даних з датчиків (вставляєте свій код), для надсилання даних на сервер (вставляєте свій код. Є приклад з рівнем сигналу WiFi), та таймер для перепідключення до WiFi і Blynk;
  • Якщо до пристрою GPIO_13 підключити світлодіод, це додасть інформативності. Часте блимання - створена точка WiFi, повільне блимання - йде підключення до Wi-Fi і серверу Blynk, блимання раз на 2 секунди - немає підключення до серверу, або мережі.