본문 바로가기
  • 재미있는 펌웨어와 코딩
ESP32/펌웨어

ESP32 OTA를 이용한 펌웨어 업데이트

by 윤재윤호 2023. 10. 3.

ESP32는 와이파이를 이용한 웹 접속이 가능하기 때문에 펌웨어도 웹페이지로

접속하여 업데이트를 할 수 있습니다.

OTA (Over-the-air programming)를 이용하여 유선이 아닌 무선을 이용하여

펌웨어를 업데이트 해보겠습니다.

 

업데이트 하는 과정은 상당히 복잡합니다. 소스를 이해하려고 하지 말고 그냥

라이브러리 사용한다 생각하는것이 좋습니다.

그래서 SPIFFS에 ota.cssota.html파일을 넣어서 사용하면 편리 합니다.

 

예제 소스코드

// 와이파이를 이용한 펌웨어 업데이트.
#include <WiFi.h>
#include <DNSServer.h>
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include "SPIFFS.h"
#include <ArduinoJson.h>
#include <Update.h>
#include <ESPmDNS.h>

DNSServer dnsServer;
AsyncWebServer server( 80 );

const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

const char index_html[] PROGMEM = R"==(
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>ESP32 테스트 페이지</title>
      <style>
        html {font-family: Arial; display: inline-block; text-align: center;}
      </style>
    </head>
    <a href="/update">펌웨어 업데이트</a>
    <body>
    </body>
  </html>
)==";

int LED = 2; // LED GPIO2
size_t content_len;

// 페이지를 찾을 수 없을 때
void notFound(AsyncWebServerRequest *request) {
    request->send(404, "text/plain", "Not found");
}

void handleDoUpdate(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)
{
  if (!index)
  {
    content_len = request->contentLength();
    // if filename includes spiffs, update the spiffs partition
    int cmd = (filename.indexOf("spiffs") > -1) ? U_SPIFFS : U_FLASH;
    if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd))
    {
      Update.printError(Serial);
    }
  }

  if (Update.write(data, len) != len)
  {
    Update.printError(Serial);
  }

  if (final)
  {
    AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots");
    response->addHeader("Refresh", "20");  
    response->addHeader("Location", "/");
    request->send(response);
    if (!Update.end(true))
    {
      Update.printError(Serial);
    }
    else
    {
      Serial.println("Update complete");
      Serial.flush();
      ESP.restart();
    }
  }
}

void printProgress(size_t prg, size_t sz)
{
  //Serial.printf("Progress: %d%%\n", (prg*100)/content_len);
}

void setup()
{
  Serial.begin( 115200 );
  if( !SPIFFS.begin( true ) )
  {
    Serial.println( F("An Error has occurred while mounting SPIFFS") );
    return;
  }
  
  pinMode( LED, OUTPUT );
  digitalWrite( LED, LOW );

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  
  // 와이파이가 연결될 때까지 기다린다.
  if(WiFi.waitForConnectResult() != WL_CONNECTED) {
      Serial.printf("WiFi Failed!\n");
      return;
  }

  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // SPIFFS의 CSS파일 연결
  server.on( "/ota.css", HTTP_GET, [] (AsyncWebServerRequest *request )
  {
    request->send( SPIFFS, "/ota.css", "text/css" );
  });
  
  // 루트
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html);
  });

  // OTA
  server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/ota.html", "text/html");
  });

  server.on("/doUpdate", HTTP_POST,
    [](AsyncWebServerRequest *request) {},
    [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data,
                  size_t len, bool final) {handleDoUpdate(request, filename, index, data, len, final);}
  );
  Update.onProgress(printProgress);
  server.onNotFound(notFound);
  server.begin(); // 서버 시작
}

void loop()
{
  static uint32_t loopCount = 0;
  if( millis()-loopCount > 500 )
  {
    loopCount = millis();
    digitalWrite( LED, digitalRead( LED ) ? LOW : HIGH );
  }
}

 

소스 코드가 다소 길긴 하지만 OTA 부분만 따로 분리해서 라이브러리로 관리 하면 좋겠습니다.

 

ota.css, ota.html 파일을 다운 받아서 data 폴더에 넣고 SPIFFS에 넣습니다.

data.zip
0.00MB

 

SPIFFS에 파일 넣기

https://jooduino.tistory.com/6

 

ESP32 SPIFFS 사용하기

ESP32가 아닌 일반적으로 사용하던 ATMega328의 아두이노를 사용했을땐 데이터를 저장하기 위해서 내부의 EEPROM을 이용하였습니다. EEPROM에 간단한 데이터는 저장하기 편리하지만, 파일을 저장할 수

jooduino.tistory.com

 

펌웨어를 업데이트 하기 위해서 dns server를 생성합니다.

#include <DNSServer.h>
#include <Update.h>
#include <ESPmDNS.h>

DNSServer dnsServer;

 

SPIFFS에 저장된 ota.css 파일을 연결합니다.

// SPIFFS의 CSS파일 연결
server.on( "/ota.css", HTTP_GET, [] (AsyncWebServerRequest *request )
{
  request->send( SPIFFS, "/ota.css", "text/css" );
});

 

SPIFFS에 저장된 ota.html 파일을 연결합니다.

/update 경로가 펌웨어를 업데이트 하기위한 페이지 입니다.

// OTA
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(SPIFFS, "/ota.html", "text/html");
});

 

펌웨어를 다운로드하기 위한 경로입니다.(내부적으로 사용)

server.on("/doUpdate", HTTP_POST,
  [](AsyncWebServerRequest *request) {},
  [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data,
                size_t len, bool final) {handleDoUpdate(request, filename, index, data, len, final);}
);

 

펌웨어를 테스트 하기위한 부분입니다.

현재 파일을 처음에는 유선으로 업로드 한 후에 아래의 코드에서 0.5초마다 LED가 점멸하는 부분을 1초로 

바꾼다음 컴파일만 해서 bin 파일을 만들어 OTA로 업데이트 합니다. 그래서 LED의 점멸 속도를 확인 하여

펌웨어가 제대로 들어갔는지 확인 합니다.

void loop()
{
  static uint32_t loopCount = 0;
  if( millis()-loopCount > 500 )
  {
    loopCount = millis();
    digitalWrite( LED, digitalRead( LED ) ? LOW : HIGH );
  }
}

 

bin 파일 만들기

메뉴->스케치->컴파일된 바이너리 내보내기를 클릭하면 현재 프로젝트 폴더에 bin 파일이 생성 됩니다.

컴파일만 하여 bin 파일 얻기

 

펌웨어를 실행하여 얻은 ip 주소로 접속하면 펌웨어 업데이트 라는 링크 하나가 있습니다.

링크를 클릭하면 펌웨어를 업데이트 하기 위한 페이지로 연결 됩니다.

펌웨어 업데이트 페이지

 

Choose file... 를 눌러 bin 파일을 선택 합니다.

파일 선택

 

Update 버튼을 눌러 펌웨어를 업데이트 합니다.

업데이트중...
업데이트 완료

 

업데이트가 완료되면 LED의 점멸 속도가 달라져야 합니다.

공유기에서 포트포워딩을 설정하면 인터넷이 연결된 곳이면 어디에서든지 원격으로

업데이트를 할 수 있습니다.

 

고정 ip 만들기

https://jooduino.tistory.com/8

 

ESP32 고정 IP 만들기

ESP32 모듈을 공유기에 접속하면 사용하지 않는 ip주소를 받습니다. 하지만, 어떤 주소를 받을지 모르기 때문에 항상 할당받은 ip주소를 확인해야 합니다. 고정 ip를 만들려면 내가 사용하는 공유

jooduino.tistory.com

 

고정 ip로 만들어야 포트포워딩을 설정할 때 좋습니다.

이제 부터는 ESP32모듈을 USB로 연결하지 않아도 펌웨어를 업데이트 할 수 있습니다.