본문 바로가기

카테고리 없음

ESP32 무선 OLED 디스플레이

반응형

 

디스플레이 기반 프로젝트를 진행할 때 대부분 버튼과 UART 명령, 또는 고정된 코드를 사용하여 OLED 디스플레이 장치에 데이터를 쓰는 방식을 사용했습니다 . 하지만 최근에는 웹페이지 인터페이스에서 즉시 업데이트할 수 있는 작고 유연한 무선 OLED 디스플레이를 만들고 싶었습니다. 오랜 검색 끝에 인터넷에서 완벽하고 훌륭한 프로젝트를 찾았는데, 이 프로젝트는 다음 프로젝트에서 영감을 받았습니다. 꼭 한번 확인해 보세요:

 

https://github.com/udit1567/Circuit_forge/tree/main/OLED%20Canvas

 

여기서는 그림 그리기 인터페이스, 이미지 업로드 및 삭제 인터페이스, 텍스트 표시 인터페이스를 구현하는 코드를 작성했습니다. 이 모든 옵션은 ESP32가 Wi-Fi 연결을 통해 제공하는 웹페이지에서 사용할 수 있으며, 이를 통해 ESP32 무선 OLED 디스플레이를 구현할 수 있습니다.

 

 

 

 

 

이 작은 아이디어는 IoT 대시보드, 간단한 UI 실험, 재미있는 메시지 게시판 등에 매우 유용한 도구로 발전했습니다. SSD1306 I2C OLED 디스플레이는 3.3V 전압으로 구동 가능하므로 디스플레이의 VDD를 ESP32 보드의 3.3V에 연결하고 접지는 접지에 연결하면 됩니다. SDA 핀은 GPIO 21에, SCL(SCK) 핀은 GPIO 22에 연결하면 배선이 완료됩니다. (OLED 디스플레이의 밝기나 명암에 문제가 있는 경우 SDA와 SCL 라인에 4.7K 풀업 저항을 추가하면 됩니다.)

 

ESP32 코딩을 시작하기 전에 몇 가지 사항을 이해해야 합니다.

 

ESP32는 STA 모드로 Wi-Fi에 연결됩니다. ESP32의 IP 주소를 열면 간단한 도구가 있는 웹 페이지가 나타납니다 . 이미지를 업로드하거나, 그리기 모드를 클릭하여 그림을 그리거나, 제공된 필드에 텍스트를 입력하고 전송할 수 있습니다. 이 웹 페이지는 캔버스 내용을 1비트 128x64 버퍼로 변환한 후, 바이너리 웹 소켓 프레임을 통해 ESP32로 전송합니다. 마지막으로 ESP32는 이를 SSD1306 OLED에 표시합니다.

 

왜 웹 소켓을 사용했는지 궁금하실 수도 있습니다. 테스트 과정에서 REST API를 사용해 봤지만, HTTP를 통해 매번 1024바이트를 전송하는 것이 느리고 매끄럽지 않았습니다. 그래서 이미지 프레임을 즉시 전송하고 지속적인 연결을 유지하기 위해 웹 소켓을 사용했습니다. SSD1306과 같은 소형 디스플레이에서는 웹 소켓이 픽셀을 실시간으로 업데이트하는 가장 쉬운 방법입니다.

 

ESP32 무선 OLED 디스플레이 코드 

 

/* Code by theoryCIRCUIT.com for Interfaceing ESP32 and 128x64 OLED to 
display images through web page interface and Wifi-do not remove this line*/

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define W 128
#define H 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C

const char* ssid = "Your_WiFi_SSID";
const char* pass = "Your_WiFi_Password";

Adafruit_SSD1306 oled(W, H, &Wire, OLED_RESET);
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

uint8_t buffer1bpp[W * H / 8];   // 1024 bytes

// ----------- Webpage -------------
const char page_html[] PROGMEM = R"HTML(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32 OLED</title>
<style>
body { font-family: Arial; padding:20px; background:#f2f2f2; }
.box { background:white; padding:15px; border-radius:10px; }
canvas { border:1px solid #666; background:black; image-rendering:pixelated; cursor: crosshair; }
button { padding:10px 14px; margin-top:10px; margin-right:8px; }
</style>
</head>
<body>

<h2>ESP32 Wireless OLED Display - theoryCIRCUIT</h2>

<div class="box">
  <p><b>1. Upload / Draw Image</b></p>
  <input type="file" id="imgfile" accept="image/*"><br><br>

  <!-- 512×256 canvas displayed -->
  <canvas id="cv" width="512" height="256"></canvas><br>

  <button onclick="sendImage()">Send Image</button>
  <button onclick="clearCanvas()">Clear Image</button>
  <button onclick="toggleDraw()">Draw Mode</button>
</div>

<br>

<div class="box">
  <p><b>2. Send Text</b></p>
  <input type="text" id="txt" placeholder="Enter text" style="width:200px">
  <button onclick="sendText()">Send Text</button>
  <button onclick="clearText()">Clear Text</button>
</div>

<p id="status">WebSocket: connecting...</p>

<script>
let ws;
let cv = document.getElementById('cv');
let ctx = cv.getContext('2d');
ctx.fillStyle = "black"; ctx.fillRect(0,0,cv.width,cv.height);

let drawing = false;
let drawMode = false;

// ------------------ WebSocket ------------------
function connectWS(){
  ws = new WebSocket("ws://" + location.host + "/ws");
  ws.binaryType = "arraybuffer";

  ws.onopen = () => document.getElementById("status").innerText = "WebSocket: connected";
  ws.onclose = () => {
    document.getElementById("status").innerText = "WebSocket: reconnecting...";
    setTimeout(connectWS, 1500);
  };
}
connectWS();

// ------------------ Image Upload ------------------
document.getElementById("imgfile").onchange = async(e)=>{
  let f = e.target.files[0];
  let img = await createImageBitmap(f);
  ctx.drawImage(img, 0, 0, cv.width, cv.height);
};

// ------------------ Draw Mode ------------------
function toggleDraw(){
  drawMode = !drawMode;
  alert(drawMode ? "Draw mode ON" : "Draw mode OFF");
}

cv.onmousedown = e => { if(drawMode){ drawing = true; draw(e); }};
cv.onmouseup = () => drawing = false;
cv.onmouseleave = () => drawing = false;
cv.onmousemove = e => { if(drawing && drawMode) draw(e); };

function draw(e){
  let rect = cv.getBoundingClientRect();
  let x = e.clientX - rect.left;
  let y = e.clientY - rect.top;

  ctx.fillStyle = "white";
  ctx.fillRect(x, y, 4, 4);  // brush size 4×4
}

// ------------------ Clear Canvas ------------------
function clearCanvas(){
  ctx.fillStyle = "black";
  ctx.fillRect(0,0,cv.width,cv.height);
}

// ------------------ Convert 512×256 → 128×64 1-bit ------------------
function pack1bit(){
  let temp = document.createElement("canvas");
  temp.width = 128;
  temp.height = 64;
  let tctx = temp.getContext("2d");

  tctx.drawImage(cv, 0, 0, 128, 64);

  let data = tctx.getImageData(0,0,128,64).data;
  let out = new Uint8Array(1024);

  for(let y=0; y<64; y++){
    for(let x=0; x<128; x++){
      let i = (y*128 + x)*4;
      let bright = data[i];
      if(bright > 100){
        let byteIndex = y*16 + (x>>3);
        out[byteIndex] |= (1 << (7 - (x&7)));
      }
    }
  }
  return out;
}

// ------------------ Send Image ------------------
function sendImage(){
  if(ws.readyState===1){
    ws.send(pack1bit());
  }
}

// ------------------ Send Text ------------------
function sendText(){
  let t = document.getElementById("txt").value.trim();
  if(t.length && ws.readyState === 1){
    ws.send("T:" + t);
  }
}

function clearText(){
  document.getElementById("txt").value = "";
}
</script>

</body>
</html>
)HTML";

// ----------- WebSocket Handler ----------
void handleWS(AsyncWebSocket *server, AsyncWebSocketClient *client,
              AwsEventType type, void *arg, uint8_t *data, size_t len)
{
  if(type == WS_EVT_DATA){
    AwsFrameInfo *info = (AwsFrameInfo*)arg;

    // binary image frame
    if(info->opcode == WS_BINARY && len == sizeof(buffer1bpp)) {
      memcpy(buffer1bpp, data, 1024);
      oled.clearDisplay();
      oled.drawBitmap(0, 0, buffer1bpp, W, H, 1);
      oled.display();
    }

    // text frame
    else if(info->opcode == WS_TEXT){
      if(data[0]=='T' && data[1]==':'){
        String text = String((char*)data).substring(2);

        oled.clearDisplay();
        oled.setCursor(0,0);
        oled.setTextSize(1);
        oled.setTextColor(SSD1306_WHITE);
        oled.println(text);
        oled.display();
      }
    }
  }
}

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

  oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);
  oled.setCursor(0,0);
  oled.println("Waiting...");
  oled.display();

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  while(WiFi.status() != WL_CONNECTED){
    delay(300);
    Serial.print(".");
  }

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

  ws.onEvent(handleWS);
  server.addHandler(&ws);

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *req){
    req->send_P(200, "text/html", page_html);
  });

  server.begin();
}

void loop(){
  ws.cleanupClients();
}

 

 

튜토리얼 원문 링크 

 

ESP32 Wireless OLED Display

Most of the time when I’m working on display based project then i end up using buttons and UART commands or some fixed code to write data to the Display…

theorycircuit.com

 

관련 게시물:

  1. ESP32에서 아두이노 IDE를 사용하여 아날로그 입력 읽기
  2. ESP32를 이용한 간단한 오디오 레벨 표시기
  3. ESP32의 무선(OTA) 업데이트
  4. 웹 블루투스를 사용하여 ESP32 GPIO 핀을 제어하세요

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩