Skip to content

kdi6033/i2r

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

781 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation


Homepage   Shop   Youtube

✅ 1. i2r AI IoT PLC 개요 (Physical · On-Device AI · Edge Controller Lineup)

📌 i2r 플랫폼은

  • 코드 작성 없이 UI 입력만으로 동작하는 PLC
  • 센서 중심의 Physical AI 제어
  • On-Device AI 기반 현장 판단
  • Edge Controller 기반 즉시 실행

을 핵심 개념으로 설계된 클라우드 연동 IoT PLC 플랫폼입니다.

📌 MQTT · MongoDB · React UI · Python을 연동하여

  • 센서 데이터 수집
  • 제어 조건 및 규칙 UI 설정
  • 실시간 상태 모니터링
  • PLC 출력 및 액추에이터 제어

를 하나의 통합 환경에서 제공합니다.

📌 ESP32 기반 IoT PLC는

  • Wi-Fi / Bluetooth / RS-485 / MQTT 통신을 지원하며
  • 센서 입력을 Edge 단에서 즉시 해석하고
  • On-Device AI 판단 결과에 따라
  • PLC 출력과 외부 제어 시스템을 자동으로 동작시킵니다.

📌 이를 통해 입력(Sensor) → 판단(AI) → 출력(Control)

  • 프로그램 없이
  • UI 설정만으로
  • 산업 현장에서 안정적으로 실행되는
    Physical AI 기반 Edge PLC 제어 구조를 구현합니다.

📺 i2r 공식 채널 및 자료 링크


✅ 2. i2r IoT PLC & sensor

그림을 보고 모델을 확인 하세요

i2r-02 i2r-03 i2r-04 i2r-04-motor
IoT PLC (4ch) IoT PLC (4ch 센서) IoT PLC (8ch) 모터제어

i2r-02-hmi i2r-03-hmi i2r-04-hmi i2r-06
IoT PLC HMI IoT PLC HMI IoT PLC HMI 온습도 제어

🔷i2r IoT PLC 회로도

i2r-02 i2r-03 i2r-04 i2r-04-motor
i2r-06

🔷i2r IoT PLC 사양

i2r-02 i2r-03
- 정격전압 : 5V DC, 보드내에서는 5V로 설계했습니다.
- 입력전압 : 7V~26V DC Free Volt
- 작동온도 : -40 ℃ ~ 85 ℃
- 입력 : 4개, 접점만 연결되면 동작 (별도 전압 인가 금지)
- 출력 : 1개 30A 250VAC/30VDC, 3개 10A 125VAC/28VDC
- 통신 : WiFi 802.11 b/g/n (최대 150Mbps)
  • Bluetooth 4.2 BR/EDR + BLE
  • WiFi : 2.4GHz 지원 (5GHz 미지원)

- RS232 : 보드 내 TTL Level RX/TX 제공
i2r-02 보드와 동일 사양

- 온습도 센서 : AHT21
  • 습도 범위 : 0~100% RH
  • 온도 범위 : -40 ℃ ~ 120 ℃
  • 정확도 : 10~95% RH 구간
  • 습도 히스테리시스 : ±1% RH
  • 측정 시간 : 50ms
  • 응답 시간 : 5초
  • 통신 : I2C
i2r-04 i2r-04-motor
- 정격전압 : 5V DC, 보드내에서는 5V로 설계했습니다.
- 입력전압 : 7V~26V DC Free Volt
- 작동온도 : -40 ℃ ~ 85 ℃
- 입력 : 8개
- 출력 : 8개 10A 125VAC/28VDC
- 통신 : WiFi 802.11 b/g/n (최대 150Mbps)
  • Bluetooth 4.2 BR/EDR + BLE
  • WiFi : 2.4GHz 지원 (5GHz 미지원)

- RS232 : 보드 내 TTL Level RX/TX 제공
i2r-04 보드와 동일 사양

- 조도센서 : GY302 BH1750

✅ 3. 바이브 코딩 ( Claude Code, Physical AI PLC 자동 프로그램)

코딩 경험 없어도 됩니다!

  1. claude.ai 접속
  2. 채팅창에 아래 붙여넣기:
https://raw.githubusercontent.com/kdi6033/i2r/main/CLAUDE.md
읽고 [원하는 동작을 한국어로 설명]

예시:

→ 완성 코드 복사 → Arduino IDE 붙여넣기 → 업로드


✅ 4. i2r 크라우드 서버 연결과 No Code 프로그램

  • React 웹앱 기반 모니터링 및 제어: 다양한 환경에서 실시간으로 모니터링하고 제어할 수 있습니다.
  • AWS 클라우드: 클라우드 기반으로 원격지에서도 기기의 상태를 모니터링하고 제어가 가능합니다.
  • 스마트폰 애플리케이션: 모바일 앱을 통해 언제 어디서나 기기의 상태를 확인하고 제어할 수 있습니다.

스마트폰,탭,PC로 제어
""https://i2r.link** 접속하면 페이지마다 유튜브 링크를 따라 해보시면 쉽게 사용할 수 있습니다.

✅ 5. i2r 통신 프로토콜

2025년7월15일 chatgpt에 적합한 프로토콜을 새로 작성하고 있습니다. 이전에 구매한 보드는 7월30일 이후 새로운 펌웨어를 다운 받아 주세요

  • i2r 보드의 mqtt 통신에서는 아래와 같이 구성되어 email을 저장하면 다음 토픽으로 자신에 해당되는 데이터를 통신 할 수 있습니다.
  • 이 문서는 i2r IoT 보드와 클라이언트 간 MQTT 통신 시 사용하는 JSON 기반 명령어 프로토콜을 설명합니다.
  • 모든 메시지는 경량화를 위해 축약된 필드명과 **축약된 명령어 코드(c)**를 사용합니다.

📌 기본 MQTT 토픽 구조


  • intopic : i2r/{email}/in
  • outtopic : i2r/{email}/out

email은 각 사용자의 고유 ID 역할을 합니다.


📌 JSON 메시지 필드명 요약

전체 명칭 축약 코드 설명
adc adc analog to digital, 센서 ad converter (예: 3.4)
bat bat battaery output, 센서 보드에 들어오는 밭데리 전압 (예: 3.4)
command c 명령 종류 (예: df, so, gs, sch, bs 등 축약어 사용)
calibration cali 센서값 보정 (예: 28.1)
config cfg ssid password email 등 와이파이 정보를 터치 판넬에 보냄
delay d 입력신호가 들어오고 출력이 나가기 까지 지연시간 단위:초
duration du 센서에 의해서 동작할 때 동작시간 0이면 무한대로 지속함
dayOfWeek dw 반복 요일: 숫자(0=일 ~ 6=토), 또는 배열 [1,3,5] (월, 수, 금)
email e email
endMinutes (분) end 종료 시간 (예: 오전 10시 = 600)
fileName f file Name
from fr 메세지를 보내는 기기의 mac 주소
humidity humi 센서 현재 습도 값 (예: 55)
in in 입력 상태 배열 (예: [0,1,0,0])
light light 센서 조도 값 (예: 120)
mac m 대상 장치의 MAC 주소 (예: "A0:B7:65:CD:4D:34")
portNo n 출력 핀 번호 (0부터 시작)
value v 출력 값: true 또는 false, 혹은 수치 값
operation o 작업 종류: "save", "list", "delete", "deleteAll", "cali", "insert"
out out 출력 상태 배열 (예: [1,0,0,0])
pinIndex pi 출력 핀 인덱스 (0~3)
pageNo pn list 에서 데이터의 번호
portState ps 제어할 출력 포트 정보 배열 (예: [{"m": "...", "n": 0, "v": true}])
repeatMode rm 반복 주기: 0 = 매일, 1 = 매주
slotIndex sI 스케줄 작업시 슬롯 (번호)인덱스 지정
startMinutes (분) start 시작 시간 (예: 오전 9시 30분 = 570)
temperature temp 센서 현재 온도 값 (예: 27.8)
trigger tr bio: ON(1) OFF(0) 가 될 때 트리거 설정 후 delay를 설정하면 그 시간에 동작한다.
triggerValue tv 트리거가 동작하는 센서 값
type t 보드 종류 (예: 3 = i2r-03)
touch panel infor. ti 보드의 정보를 터치판넬에 보냄
typeSensor ts 센서 타입 (temp, humi, light)
version ver 프로그램 버젼

📌 명령어 (command : c) 축약 코드 목록

전체 명칭 축약 코드 (c) 설명
bindIO bio 입력 상태에 따라 출력 연동 설정
bindSensor bs 센서 조건에 따라 출력 제어 (온도, 습도, 조도 등)
downloadFirmware df 보드에 펌웨어를 다운로드
getStatus gs 보드 상태 요청 (온도, 습도, in/out version 등)
schedule sch 시간 기반 출력 동작 스케줄 설정
setInfo si Wi-Fi, MQTT 브로커 등 설정 정보 전송
setOutput so 핀출력을 표시 (1 = ON, 0 = OFF)
touchInput ti Touch Panel(RP2040 등) 전달

📌 예제 및 상세 설명

command (c) 예제 및 상세 설명
downloadFirmware
(df)
인터넷에서 통신으로 펌웨어를 보드로 내려 받는다
{"c":"df","m":"D8:13:2A:C3:E7:68","fileName":"i2r-03.ino.bin"}
setInfo
(si)
와이파이 및 통신에 필요한 정보를 보내 보드에 기록하여 기기는 여기 정보로 통신을 연결한다.
{"c":"si",'ssid':'','password'='', 'e':'***', 'mqttBroker':'ai.doowon.ac.kr'}}
bindIO
(bio)
bind_input_output
아래 상세설명 참조
bindSensor
(bs)
아래 상세설명 참조
downloadFirmware
(df)
{ "c": "df", "m": "D8:13:2A:C3:E7:68", "fileName": "i2r-03.ino.bin" }
지정한 MAC 주소를 가진 보드에 인터넷을 통해 펌웨어를 다운로드합니다. .ino.bin 또는 .bin 파일이 대상이며, OTA 업데이트를 통해 자동 적용됩니다.
getStatus
(gs)
{ "c": "gs", "m": "EC:64:C9:43:E8:B8" }
지정 보드의 상태를 요청합니다. 응답으로 온도, 습도, 입력포트(in), 출력포트(out) 정보가 포함됩니다.
응답 예시:
{"t":3,"email":"kdi6033@gmail.com","m":"EC:64:C9:43:E8:B8","temp":28.4,"humi":38,"in":[0,0,0,0],"out":[0,0,0,0]}
scheduleOutput
(sch)
아래 상세설명 참조
setInfo
(si)
{ "c": "si", "ssid": "i2r_wifi", "password": "00000000", "email": "user@example.com", "mqttBroker": "mqtt.i2r.link" }
보드가 Wi-Fi 및 MQTT 브로커에 연결할 수 있도록 설정 정보를 저장합니다. 이후 재시작 시 자동 연결됩니다.
setOutput
(so)
{ "c": "so", "m": "A0:B7:65:CD:4D:34", "n": 1, "v": 1 }
지정 보드의 n번 출력 핀을 제어합니다. v: 1은 ON, v: 0은 OFF입니다. 릴레이, 모터, LED 등에 사용됩니다.
touchInput
(ti)
{ "c": "ti", "light": 120 }
RP2040 등의 외부 터치패널에서 측정한 조도 값을 IoT 보드에 전달하여 조건 제어에 활용할 수 있습니다.

📌 입출력 설정 프로토콜 예제 (bindIO / c:"bio") 1️⃣ 트리거 등록
입력 0번 포트가 ON 되면, → 출력 1번 포트를 ON 시킵니다. 3("d":3)초 후에 4(du":4)초간 동작 d:0 → 지연시간 없음 (즉시 실행)

  • Full JSON (개발용 / 디버그용)
    {"command":"bindIO","operation":"insert","trigger":true,"mac":"D4:8C:49:50:46:F4","portNo":0,"delay":3,"duration":4,"portState":[{"mac":"D4:8C:49:50:46:F4","portNo":0,"value":true}]
  • Compressed JSON (MQTT 실제 전송)
{"c":"bio","d":3,"m":"D4:8C:49:50:46:F4","o":"insert","n":0,"tr":1,"du":4,"ps":[{"m":"D4:8C:49:50:46:F4","n":0,"v":1}] <br>

2️⃣ 트리거 목록 확인 (List)
MAC D4:8C:49:50:46:F4 인 i2r 보드에서 입력 0번 포트에 설정된 bindIO(입력→출력 연동) 규칙 목록을 조회하라는 명령입니다.

  • Full JSON (개발용 / 디버그용)
    {command: 'bindIO', operation: 'list', mac: 'D4:8C:49:50:46:F4', portNo: 0}
  • Compressed JSON (MQTT 실제 전송)
{"c":"bio","m":"D4:8C:49:50:46:F4","o":"list","n":0} <br>

3️⃣ 트리거 삭제 (Delete)
MAC D4:8C:49:50:46:F4 인 i2r 보드에서 입력 0번 포트에 설정된 bindIO 규칙 중 slotIndex(sI)가 1번인 항목을 삭제하라는 명령입니다.

  • Full JSON (개발용 / 디버그용)
    {"command":"bindIO","operation":"delete","mac":"D4:8C:49:50:46:F4","portNo":0,"slotIndex":1}
  • Compressed JSON (MQTT 실제 전송)
{"c":"bio","m":"D4:8C:49:50:46:F4","o":"delete","n":0,"sI":1} <br>

4️⃣ 전체 삭제 (DeleteAll)
MAC D4:8C:49:50:46:F4 인 i2r 보드에서 입력 0번 포트에 설정된 모든 bindIO(입력→출력 연동) 규칙을 전체 삭제하라는 명령입니다.

  • Full JSON (개발용 / 디버그용)
    {command: 'bindIO', operation: 'deleteAll', mac: 'D4:8C:49:50:46:F4', portNo: 0}
  • Compressed JSON (MQTT 실제 전송)
{"c":"bio","m":"D4:8C:49:50:46:F4","o":"deleteAll","n":0}

📌 스케줄 제어 프로토콜 (Schedule / c: "sch")

  • 스케줄 기능은 특정 시간대에 PLC 출력을 자동으로 ON/OFF 제어하는 기능입니다.
  • 즉, “매일 또는 특정 요일에 정해진 시간에 자동으로 스위치를 켜거나 끄는 기능”을 수행합니다.
  • 모든 설정은 MQTT 메시지(JSON 형식)으로 전송됩니다.

1️⃣ 매일 스케줄 등록 (Insert)

  • 매일 10:44부터 10:45까지 MAC 주소 "D4:8C:49:50:46:F4" 장치의 0번 포트 출력을 켜는 스케줄을 등록합니다.
  • "rm":"daily"는 매일 반복, "dw":0은 요일(일요일)을 의미하며 "dw"가 생략되면 daily 모드로 동작합니다.
  • start와 end는 자정(00:00) 기준 분 단위입니다. 예: 10:44 → 10×60 + 44 = 644, 10:45 → 645
  • Full JSON (개발용 / 디버그용)
    { "command":"schedule", "dayOfWeek":0, "endMinutes":645, "mac":"D4:8C:49:50:46:F4", "operation":"insert", "pinIndex":0, "repeatMode":"daily", "startMinutes":644 }
  • Compressed JSON (MQTT 실제 전송)
{"c":"sch","dw":0,"end":645,"m":"D4:8C:49:50:46:F4","o":"insert","pi":0,"rm":"daily","start":644}

2️⃣ 매주 스케줄 등록 (Insert)

  • 매주 월요일(1), 수요일(3) 오전 10:50부터 10:51까지

  • MAC 주소 "D4:8C:49:50:46:F4" 장치의 0번 포트 출력을 켜는 스케줄을 등록합니다.

  • "rm":"weekly"는 주간 반복을 의미하며, "dw":[1,3]은 월요일(1)과 수요일(3)에만 동작함을 뜻합니다.

  • "dw" 배열 값은 0=일, 1=월, 2=화, 3=수, 4=목, 5=금, 6=토입니다.

  • start와 end는 자정(00:00) 기준 분 단위입니다. 예: 10:50 → 10×60 + 50 = 650, 10:51 → 651

  • "pi":0은 제어할 포트 번호입니다.

  • Full JSON (개발용 / 디버그용)
    {"c": "sch","m": "D4:8C:49:50:46:F4","o": "insert","pi": 0,"start": 650,"end": 651,"rm": "weekly","dw": [1, 3]} { "command":"schedule", "dayOfWeek":[1, 3], "endMinutes":645, "mac":"D4:8C:49:50:46:F4", "operation":"insert","pinIndex":0, "repeatMode":"weekly", "startMinutes":644 }

  • Compressed JSON (MQTT 실제 전송)

{"c":"sch","dw":[1,3],"end":645,"m":"D4:8C:49:50:46:F4","o":"insert","pi":0,"rm":"weekly","start":644}

3️⃣ 매주 스케줄 목록 조회 (List)

  • 해 장치의 등록된 모든 스케줄을 조회합니다.

  • Full JSON (개발용 / 디버그용)
    {"command": "schedule","operation": "list","mac": "D4:8C:49:50:46:F4","pinIndex": 0}

  • Compressed JSON (MQTT 실제 전송)

{"c": "sch","m": "D4:8C:49:50:46:F4","o": "list","pi": 0}
  • 응답 예시
    {"c": "sch","m": "D4:8C:49:50:46:F4","o": "list","pi": 0,"rm": "weekly","dw": [1,3],"start": 650,"end": 651,"slotIndex": 2}

4️⃣ 매주 스케줄 삭제 (Delete)
특정 slotIndex를 지정하여 해당 스케줄 한 개만 삭제합니다.
"sI"는 slotIndex를 의미하며, 목록 조회(List) 결과에서 받은 값을 사용합니다.

  • Full JSON (개발용 / 디버그용)
    {"command": "schedule","operation": "delete","mac": "D4:8C:49:50:46:F4","pinIndex": 0,"slotIndex": 2}

  • Compressed JSON (MQTT 실제 전송)

{"c": "sch","m": "D4:8C:49:50:46:F4","o": "delete","pi": 0,"sI": 2}

5️⃣ 매주 스케줄 전체 삭제 (DeleteAll)

  • 해당 장치의 특정 포트(pi)에 등록된 모든 주간 스케줄을 삭제합니다.

  • 여러 요일 또는 여러 시간대 스케줄을 한 번에 제거할 때 사용합니다.

  • Full JSON (개발용 / 디버그용)
    {"command": "schedule","operation": "deleteAll","mac": "D4:8C:49:50:46:F4","pinIndex": 0}

  • Compressed JSON (MQTT 실제 전송)

{"c": "sch","m": "D4:8C:49:50:46:F4","o": "deleteAll","pi": 0}

📌 센서 트리거 프로토콜 예제 (bindSensor / c:"bs")

  • 센서의 측정값이 특정 상한(올라갈 때) 또는 하한(내려갈 때) 에 도달하면 지정한 기기의 출력을 자동으로 제어하는 기능입니다.
  • 즉, “센서 값이 특정 조건을 만족하면 → 다른 장치(또는 본인)의 릴레이/스위치를 제어”합니다.
  • 서로 다른 IoT PLC끼리도 연결이 가능하며 중복 설정도 가능 합니다.

1️⃣ 트리거 등록 (Insert)
MAC D4:8C:49:50:46:F4 장치에서 온도(temperature)가 35℃를 초과하면(tr:1), 동작기기(MAC D4:8C:49:50:46:F4)가 3초 지연(d:3) 후 4초 동안(du:4) 0번 출력(n:0)을 ON(v:1)하도록 조건을 추가(insert)하는 센서 바인딩 명령입니다.

  • Full JSON (개발용 / 디버그용)
    {"command":"bindSensor","typeSensor":"temperature","mac":"D4:8C:49:50:46:F4","operation":"insert","trigger":true,"triggerValue":35,"duration":4,"delay":3,"portState":[{"mac":"D4:8C:49:50:46:F4","portNo":0,"value":true}]}
  • Compressed JSON (MQTT 실제 전송)
{"c":"bs","ts":"temperature","d":3,"m":"D4:8C:49:50:46:F4","o":"insert","tr":1,"tv":35,"du":4,"ps":[{"m":"D4:8C:49:50:46:F4","n":0,"v":1}]}

2️⃣ 트리거 목록 확인 (List)
습도에 대한 리스트를 요청한다.

  • Full JSON (개발용 / 디버그용)
    {"command":"bindSensor","typeSensor":"temperature","mac":"D4:8C:49:50:46:F4","operation":"list"}
  • Compressed JSON (MQTT 실제 전송)
{"c":"bs","ts":"temperature","m":"D4:8C:49:50:46:F4","o":"list"}
  • 응답예시
    {"c":"bs","o":"list","ts":"temp","tr":1,"tv":35,"du":4,"d":3,"sI":6,"ps":[{"m":"D4:8C:49:50:46:F4","n":0,"v":1}],"e":"kdi6033@gmail.com","t":"i2r-03-hmi","fr":"D4:8C:49:50:46:F4","m":"D4:8C:49:50:46:F4"}

3️⃣ 트리거 삭제 (Delete)
습도센서에서 슬롯번호 1 의 설정을 제거한다.

  • Full JSON (개발용 / 디버그용)
    {"command":"bindSensor","typeSensor":"temperature","mac":"D4:8C:49:50:46:F4","operation":"delete","slotIndex":6}
  • Compressed JSON (MQTT 실제 전송)
{"c":"bs","ts":"temperature","m":"D4:8C:49:50:46:F4","o":"delete","sI":6}

4️⃣ 센서 타입 전체 삭제 (DeleteAll)
습도센서의 설정된 모든 값을 제거한다.

  • Full JSON (개발용 / 디버그용)
    {"command":"bindSensor","typeSensor":"temperature","mac":"D4:8C:49:50:46:F4","operation":"deleteAll"}

  • Compressed JSON (MQTT 실제 전송)

 {"c":"bs","ts":"temperature","m":"D4:8C:49:50:46:F4","o":"deleteAll"}

5️⃣ 센서 보정 (Calibration)
습도센서의 현재 값을 44로 설정한다.

  • Full JSON (개발용 / 디버그용)
    {"command":"bindSensor","typeSensor":"temperature","operation":"calibration","mac":"D4:8C:49:50:46:F4","value":44}
  • Compressed JSON (MQTT 실제 전송)
{"c":"bs","ts":"temperature","m":"D4:8C:49:50:46:F4","o":"cali","v":44}

✅ 6. 아두이노 프로그램 설정

Updating the screen

24-7 보드 아두이노 프로그램

File->Preferences->Additional Boards Manager URLs
https://dl.espressif.com/dl/package_esp32_index.json

Tools->Boars Manager

📌 partitions.csv 파일 작성 Flash 메모리 할당을 합니다. Skech->Export Compiled Binary 를 실행하면 build 디렉토리가 만들어 집니다. 다음은 생성된 파일 용량 입니다.

2024-09-29  오전 09:44         1,932,784 board-i2r-03.ino.bin
2024-09-29  오전 09:44            24,896 board-i2r-03.ino.bootloader.bin
2024-09-29  오전 09:44        20,233,488 board-i2r-03.ino.elf
2024-09-29  오전 09:44        24,280,721 board-i2r-03.ino.map
2024-09-29  오전 09:44         8,388,608 board-i2r-03.ino.merged.bin
2024-09-29  오전 09:44             3,072 board-i2r-03.ino.partitions.bin
               6개 파일          54,863,569 바이트

i2r-03.ino.bin (1,932,784 바이트): 이 파일은 컴파일된 애플리케이션 바이너리입니다. 애플리케이션 자체가 약 1.8MB 크기로, 이는 플래시 메모리 내 app0 또는 app1 파티션에 저장됩니다.

i2r-03.ino.bootloader.bin (24,896 바이트): 부트로더 파일로, 매우 작은 크기입니다. 일반적으로 부트로더는 플래시 메모리의 별도 파티션에 저장되며, 약 24KB 정도로 충분히 작은 크기입니다.

i2r-03.ino.elf (20,233,464 바이트): ELF 파일은 디버그 정보가 포함된 파일로, 플래시 메모리에는 업로드되지 않고 컴파일 과정 중에만 사용됩니다. 즉, 이 파일은 실제로 디바이스에 영향을 미치지 않습니다.

i2r-03.ino.map (24,273,503 바이트): 이 파일은 메모리 매핑 정보를 포함하는 파일로, 디버깅을 위해 사용됩니다. 이 역시 플래시 메모리에는 업로드되지 않으므로 실제 메모리 용량과는 무관합니다.

i2r-03.ino.merged.bin (8,388,608 바이트): 이 파일은 애플리케이션, 부트로더, 파티션 테이블이 합쳐진 통합 바이너리 파일입니다. 이 크기가 플래시 메모리의 전체 크기와 일치하므로, 이 파일을 플래시 메모리에 업로드할 경우 전체 용량을 꽉 채우게 됩니다.

i2r-03.ino.partitions.bin (3,072 바이트): 파티션 테이블 파일로, 작은 크기이므로 문제없이 플래시 메모리에 저장될 수 있습니다.

1,932,784 board-i2r-03.ino.bin 이 파일이 실행파일은 1.95M 정도의 용량 입니다. 실행 파일을 위해 여유있게 2.5M, OTA를 위해 같은 용량 2.5M, SPIFF 에는 1.8M를 할당 하겠습니다.
[ChatGPT]

2024-09-29  오전 09:44         1,932,784 board-i2r-03.ino.bin
2024-09-29  오전 09:44            24,896 board-i2r-03.ino.bootloader.bin
2024-09-29  오전 09:44        20,233,488 board-i2r-03.ino.elf
2024-09-29  오전 09:44        24,280,721 board-i2r-03.ino.map
2024-09-29  오전 09:44         8,388,608 board-i2r-03.ino.merged.bin
2024-09-29  오전 09:44             3,072 board-i2r-03.ino.partitions.bin
               6개 파일          54,863,569 바이트
실행 파일을 위해 여유있게  2.5M, OTA를 위해 같은 용량 2.5M, SPIFF 에는 1.8M를 할당해서 partitions.csv 파일 작성해줘

다음은 생성된 partitions.csv 파일 입니다.

# Name,   Type, SubType, Offset,  Size,   Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x280000,  
app1,     app,  ota_1,   0x290000, 0x280000, 
spiffs,   data, spiffs,  0x510000, 0x180000  
  • 첫 번째로 NVS 파티션은 비휘발성 저장소(Non-Volatile Storage)입니다. 이 영역은 설정값이나 작은 데이터를 영구적으로 저장하는데 사용됩니다. 예를 들어, 와이파이 연결 정보나 디바이스 설정 값과 같은 데이터를 저장할 수 있으며, 이 데이터는 전원을 껐다 켜도 유지됩니다. NVS는 EEPROM과 비슷한 역할을 하며, 이 테이블에서는 0x9000 오프셋에서 시작하여 20KB 크기로 할당되어 있습니다. Offset: 0x9000 에서 시작해서 Size: 0x5000 (20KB) 할당

  • 두 번째로 OTA 데이터 파티션은 OTA 업데이트를 관리하는데 사용됩니다. OTA(Over-The-Air) 업데이트는 디바이스의 펌웨어를 무선으로 업데이트할 수 있는 기능인데, 이 파티션은 현재 실행 중인 애플리케이션과 업데이트 상태를 저장하는 역할을 합니다. OTA 업데이트가 진행될 때, 시스템이 어떤 애플리케이션을 실행할지 결정하는 중요한 정보를 여기에 저장합니다. 이 파티션은 0xe000 오프셋에서 시작하며, 8KB의 크기를 가집니다. Offset: 0xe000 에서 시작해서 Size: 0x2000 (8KB) 할당

  • 세 번째와 네 번째로 app0과 app1 파티션은 각각 OTA 업데이트를 위한 애플리케이션 저장 공간입니다. 일반적으로 ESP32는 두 개의 애플리케이션 파티션을 가집니다. 하나는 현재 실행 중인 애플리케이션이 저장된 공간이고, 다른 하나는 새로운 업데이트를 다운로드할 공간입니다. 예를 들어, 현재 실행 중인 애플리케이션은 app0에 저장되어 있고, OTA 업데이트를 통해 새로운 펌웨어가 app1에 다운로드됩니다. 업데이트가 완료되면 다음에 디바이스가 부팅될 때 app1에서 애플리케이션이 실행됩니다. app0과 app1 파티션은 각각 2.5MB 크기로 할당되어 있어 대규모 애플리케이션을 저장할 수 있습니다. app0 는 Offset: 0x10000 에서 시작해서 Size: 0x280000 (2.5MB) 할당, app1는 Offset: 0x290000 에서 시작해서 Size: 0x280000 (2.5MB) 할당

  • 마지막으로 SPIFFS 파티션은 파일 시스템 저장 공간입니다. SPIFFS는 플래시 메모리를 기반으로 한 파일 시스템으로, 디바이스가 HTML 파일, 이미지, 스크립트 파일 등 다양한 파일 데이터를 저장하고 읽을 수 있도록 도와줍니다. 예를 들어, 웹서버를 구현하는 경우 웹 페이지의 정적 파일을 SPIFFS에 저장할 수 있습니다. 이 파티션은 1.5MB 크기로 할당되어 있으며, 시스템이 파일 기반 데이터를 관리할 수 있는 중요한 공간입니다. Offset: 0x510000 에서 시작해서 Size: 0x180000 (1.5MB) 할당.

✅ 7. I2C 통신 프로그램

💻 C code 예제 - 마스터 프로그램 : 터치스크린 RP2040
#include <Wire.h>

#define I2C_ADDR 0x08  // ESP32 슬레이브 주소
#define SDA_PIN 20     // I2C SDA 핀
#define SCL_PIN 21     // I2C SCL 핀

void setup() {
  Wire.setSDA(SDA_PIN);
  Wire.setSCL(SCL_PIN);
  Wire.begin();  // I2C 마스터 시작
  Serial.begin(115200);
  delay(1000);
  Serial.println("I2C Master 시작");
}

void sendCommand(const String& cmd) {
  Wire.beginTransmission(I2C_ADDR);
  Wire.write(cmd.c_str());
  Wire.endTransmission();
  Serial.println("전송: " + cmd);
}

void loop() {
  sendCommand("{\"cmd\":\"on\"}");
  delay(3000);

  sendCommand("{\"cmd\":\"off\"}");
  delay(3000);
}
💻 C code 예제 - 슬레이브 프로그램 : IoT PLC ESP32
#include <Wire.h>

#define SLAVE_ADDR 0x08
#define SDA_PIN 16
#define SCL_PIN 17

String received = "";

void receiveEvent(int howMany) {
  char c;
  received = "";
  while (Wire.available()) {
    c = Wire.read();
    received += c;
  }
  Serial.println("수신된 데이터: " + received);
}

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("I2C Slave 시작");

  // 슬레이브 모드로 SDA/SCL 핀 지정
  Wire.begin(SLAVE_ADDR, SDA_PIN, SCL_PIN, 100000);
  Wire.onReceive(receiveEvent);
}

void loop() {
  // 슬레이브는 이벤트 기반이므로 loop는 비워둠
}

✅ 8. Arduino 1.3인치 OLED LCD 쉴드 (shield-03)

i2r-05 보드 ESP32-S3 보드를 사용하여 1.3인치 I2C OLED 디스플레이 (SH1106 드라이버) 를 제어하는 방법을 설명합니다.

🎯 하드웨어 사양 (Hardware Specifications)

항목 설명
디스플레이 타입 OLED (Organic Light-Emitting Diode)
화면 크기 1.3 인치 (대각선)
해상도 128 x 64 픽셀
드라이버 IC SH1106
통신 방식 I2C (IIC)
I2C 주소 0x3C (일반적) 또는 0x3D
동작 전압 3.3V ~ 5V
시야각 > 160°

🎯 핀 연결 (ESP32-S3 기준)

OLED 핀 ESP32-S3 핀 비고
GND GND 접지
VCC 3.3V / 5V 전원
SCL GPIO 17 Serial Clock
SDA GPIO 18 Serial Data

참고: ESP32-S3의 I2C 핀(SDA/SCL)은 맵핑이 가능하지만, 이 예제에서는 SDA=18, SCL=17번 핀을 사용하도록 코드가 작성되어 있습니다.


🎯 필요한 라이브러리 (Software Dependencies)

이 프로젝트는 단색 디스플레이를 위한 강력한 그래픽 라이브러리인 U8g2를 사용합니다.

  1. 라이브러리 설치 방법:
    1. 아두이노 IDE를 실행합니다.
    2. 메뉴에서 스케치 (Sketch) -> 라이브러리 포함하기 (Include Library) -> 라이브러리 관리 (Manage Libraries...) 로 이동합니다.
    3. 검색창에 U8g2 (작성자: olikraus)를 입력하여 검색합니다.
    4. 설치 (Install) 버튼을 클릭합니다.


💻 아두이노 프로그램
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>

// 1.3인치 SH1106 I2C OLED 예제 (SDA=18, SCL=17)
// 아두이노 라이브러리 매니저에서 "U8g2" 라이브러리를 설치해야 합니다.

// SDA = 18, SCL = 17 핀 설정
// 생성자 파라미터: (Rotation, Reset, Clock/SCL, Data/SDA)
// ESP32-S3의 경우 HW I2C 생성자에 핀 번호를 직접 전달하면 내부적으로 Wire.begin(sda, scl)을 호출합니다.
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 17, 18);

void setup(void) {
  u8g2.begin();  // 디스플레이 초기화
}

void loop(void) {
  u8g2.clearBuffer();          // 내부 메모리 지우기
  
  u8g2.setFont(u8g2_font_ncenB08_tr); // 폰트 설정
  u8g2.drawStr(0, 10, "Hello World!"); // (x, y)
  
  u8g2.drawStr(0, 30, "ESP32-S3");
  u8g2.drawStr(0, 45, "SDA: 18, SCL: 17");
  
  u8g2.sendBuffer();          // 디스플레이 전송
  delay(1000);  
}

🚀 실행 방법

  1. 위의 핀 연결 표를 참고하여 1.3" OLED를 ESP32-S3 보드에 연결합니다 (SDA: 18, SCL: 17).
  2. 아두이노 IDE에서 lcd1-3.ino 파일을 엽니다.
  3. 툴 (Tools) -> 보드 (Board) 메뉴에서 사용 중인 ESP32-S3 보드를 선택합니다. (예: ESP32S3 Dev Module)
  4. 포트를 선택하고 업로드 (Upload) 버튼을 누릅니다.
  5. 업로드가 완료되면 OLED 화면에 "Hello World!" 문구와 핀 정보가 출력되는지 확인합니다.

✅ 9. HMI 한글 터치스크린 ( rp2040 + 3.5"LCD)

RP2040-Zero와 3.5인치 IPS LCD를 이용해 i2r-02 IoT PLC용 HMI(Human Machine Interface)를 만드는 방법을 설명합니다.

📌 1. 부품 목록

부품 사양
MCU RP2040-Zero (Waveshare)
LCD 3.5인치 IPS SPI LCD (320×480)
디스플레이 IC ILI9488
터치 IC XPT2046 (저항막 방식)
PLC i2r-02 (ESP32 기반, 4채널 입출력)
통신 UART Serial1, 9600 bps

📌 2. LCD 핀 연결

RP2040-Zero LCD 핀 번호 LCD 핀 이름 비고
5V 1 VDD 반드시 5V — 3.3V 시 터치 오작동
GND 2 GND 접지
GPIO 2 3 CS LCD Chip Select
GPIO 3 4 RST LCD 리셋
GPIO 4 5 DC 데이터/커맨드 선택
GPIO 11 6 & 12 SDI (MOSI) & TDI LCD 데이터 입력, PCB에서 LCD 6번과 12번을 묶어서 GPIO 11번에 연결
GPIO 10 7 & 10 SCK & TCK SPI 클럭, PCB에서 LCD 7번과 10번을 묶어서 GPIO 10번에 연결
GPIO 5 8 BL 백라이트 (PWM 제어)
연결 금지 9 SDO ILI9488 버스 충돌 방지 — 반드시 비워둠
GPIO 6 11 TCS 터치 Chip Select
GPIO 8 13 TDO 터치 데이터 출력 — 반드시 연결
GPIO 9 14 PEN (IRQ) 터치 인터럽트 (옵션)

주의: SDO(9번) 핀은 ILI9488의 하드웨어 결함으로 SPI 버스 충돌을 일으킵니다. 절대 연결하지 않습니다.


📌 3. RP2040 ↔ i2r-02 PLC 연결

RP2040-Zero i2r-02 (ESP32) 설명
GPIO 12 (TX) RX HMI → PLC 데이터 전송
GPIO 13 (RX) TX PLC → HMI 데이터 수신
GND GND 공통 접지 (필수)

📌 4. I2C 통신

RP2040-Zero 비고
3V3 3.3V
GND 접지
GPIO 26 SDA
GPIO 27 SCL

** 📌 5. TFT_eSPI 라이브러리 & 예제 프로그램 **

TFT_eSPI 라이브러리를 설치하고 \Arduino\libraries\TFT_eSPI
이 폴더 안에 있는 기존 User_Setup.h를 프로젝트의 User_Setup.h로 덮어쓰기: TFT_eSPI 라이브러리에 "내 LCD 드라이버와 연결 핀이 이것"이라고 알려주는 필수 설정 파일입니다.


💻 User_Setup.h
// TFT_eSPI User_Setup - CrowPanel 3.5" IPS ILI9488 (RP2040)
// 핀 연결 가이드: lcd_github.md 참조

#define USER_SETUP_INFO "CrowPanel 3.5 ILI9488 RP2040"

// ----- Section 1: Driver -----
#define ILI9488_DRIVER

// 3.5" 320x480 (portrait)
#define TFT_WIDTH 320
#define TFT_HEIGHT 480

// IPS 패널 색 반전
#define TFT_INVERSION_OFF
// #define TFT_INVERSION_ON

// ----- Section 2: Pins -----
#define TFT_CS   2   // LCD CS (LCD 3번)
#define TFT_RST  3   // LCD Reset (LCD 4번)
#define TFT_DC   4   // LCD Data/Command (LCD 5번)
#define TFT_MOSI 11  // LCD SDI + 터치 TDI 공유 (LCD 6, 12번)
#define TFT_SCLK 10  // LCD SCK + 터치 TCK 공유 (LCD 7, 10번)
#define TFT_BL   5   // 백라이트 (LCD 8번)
#define TFT_MISO 8   // 터치 TDO (LCD 13번) — SPI1 RX
#define TFT_BACKLIGHT_ON HIGH

// 터치 (XPT2046)
#define TOUCH_CS  6  // 터치 CS (LCD 11번)
#define TOUCH_IRQ 9  // 터치 IRQ (LCD 14번)

// ----- Section 3: RP2040 SPI -----
#define TFT_SPI_PORT 1

// ----- Section 4: SPI speed -----
#define SPI_FREQUENCY 40000000
#define SPI_READ_FREQUENCY 20000000
#define SPI_TOUCH_FREQUENCY 2500000

// ----- Section 5: Fonts -----
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF
#define SMOOTH_FONT

💻 예제1: 2초마다 배경색을 빨강→초록→파랑으로 순환하며 화면 중앙에 "Hello world"를 흰색으로 표시합니다.
#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();

void printCenter(const char* text) {
  int16_t tw = tft.textWidth(text, 4);
  int16_t th = tft.fontHeight(4);
  tft.drawString(text, (480 - tw) / 2, (320 - th) / 2, 4);
}

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

  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);

  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);

  tft.setTextColor(TFT_WHITE);
  printCenter("Hello world");

  Serial.println("Hello world Displayed!");
}

int color_state = 0;

void loop() {
  delay(2000);

  if (color_state == 0) {
    tft.fillScreen(TFT_RED);
    color_state = 1;
  } else if (color_state == 1) {
    tft.fillScreen(TFT_GREEN);
    color_state = 2;
  } else {
    tft.fillScreen(TFT_BLUE);
    color_state = 0;
  }

  tft.setTextColor(TFT_WHITE);
  printCenter("Hello world");
}

💻 에제2: 터치 버튼을 누를 때마다 버튼 색이 빨강(OFF)↔초록(ON)으로 토글되는 터치 UI 예제입니다.
#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();

bool buttonState = false;
bool lastTouchState = false;

const int btnX = 140;
const int btnY = 110;
const int btnW = 200;
const int btnH = 100;

void drawButton() {
  uint16_t btnColor = buttonState ? TFT_GREEN : TFT_RED;
  const char* btnText = buttonState ? "ON" : "OFF";

  tft.fillRoundRect(btnX, btnY, btnW, btnH, 15, btnColor);

  tft.setTextColor(TFT_WHITE);
  int16_t tw = tft.textWidth(btnText, 4);
  int16_t th = tft.fontHeight(4);
  tft.drawString(btnText, btnX + (btnW - tw) / 2, btnY + (btnH - th) / 2, 4);
}

bool getTouchCoords(int16_t &x, int16_t &y) {
  uint16_t rx = 0, ry = 0;

  if (digitalRead(TOUCH_IRQ) == LOW) {
    if (tft.getTouchRaw(&rx, &ry)) {
      x = map(ry, 204, 3781, 0, 480);
      y = map(rx, 3808, 311, 0, 320);
      x = constrain(x, 0, 479);
      y = constrain(y, 0, 319);
      return true;
    }
  }
  return false;
}

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

  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
  pinMode(TOUCH_IRQ, INPUT_PULLUP);

  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);

  tft.setTextColor(TFT_WHITE);
  int16_t tw = tft.textWidth("Touch Button Test", 4);
  tft.drawString("Touch Button Test", (480 - tw) / 2, 40, 4);

  drawButton();
}

void loop() {
  int16_t x, y;
  bool isTouched = getTouchCoords(x, y);

  if (isTouched && !lastTouchState) {
    Serial.print("Touch: X="); Serial.print(x);
    Serial.print(" Y="); Serial.println(y);

    if (x >= btnX && x <= btnX + btnW && y >= btnY && y <= btnY + btnH) {
      buttonState = !buttonState;
      drawButton();
      Serial.println(buttonState ? "ON" : "OFF");
    }
  }

  lastTouchState = isTouched;
  delay(20);
}

💻 에제3: 예제2에서 터치 버튼이 오동작을 하면 calibration 으로 화면 터치위치 값을 알려주세요
/*
 * Touch Calibration for ILI9488 + XPT2046 (RP2040)
 *
 * 화면 4 모서리 십자를 순서대로 터치하면
 * map() 파라미터를 Serial과 화면에 출력합니다.
 *
 * 순서: 좌상 → 우상 → 우하 → 좌하
 */

#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();

#define SCREEN_W 480
#define SCREEN_H 320
#define MARGIN   20

const int16_t TARGET_X[4] = { MARGIN, SCREEN_W - MARGIN, SCREEN_W - MARGIN, MARGIN };
const int16_t TARGET_Y[4] = { MARGIN, MARGIN,             SCREEN_H - MARGIN, SCREEN_H - MARGIN };
const char*   LABEL[4]    = { "1:Left-Top", "2:Right-Top", "3:Right-Bottom", "4:Left-Bottom" };

uint16_t rawRX[4], rawRY[4];
int      step        = 0;
bool     waitRelease = false;

void drawCross(int16_t x, int16_t y, uint16_t color) {
  tft.drawLine(x - 18, y,      x + 18, y,      color);
  tft.drawLine(x,      y - 18, x,      y + 18, color);
  tft.drawCircle(x, y, 6, color);
}

void showStep(int s) {
  tft.fillScreen(TFT_BLACK);

  for (int i = 0; i < s; i++)
    drawCross(TARGET_X[i], TARGET_Y[i], TFT_DARKGREY);

  drawCross(TARGET_X[s], TARGET_Y[s], TFT_YELLOW);

  tft.setTextColor(TFT_WHITE);
  tft.setTextSize(2);
  tft.setCursor(10, SCREEN_H / 2 - 20);
  tft.printf("Touch %s", LABEL[s]);

  tft.setTextColor(TFT_CYAN);
  tft.setTextSize(1);
  tft.setCursor(10, SCREEN_H / 2 + 15);
  tft.printf("Step %d / 4", s + 1);
}

void showResult() {
  // 좌우 쌍 평균 → ry 가 화면 X 방향
  float ry_left  = (rawRY[0] + rawRY[3]) / 2.0f;
  float ry_right = (rawRY[1] + rawRY[2]) / 2.0f;

  // 상하 쌍 평균 → rx 가 화면 Y 방향
  float rx_top    = (rawRX[0] + rawRX[1]) / 2.0f;
  float rx_bottom = (rawRX[2] + rawRX[3]) / 2.0f;

  // MARGIN 위치 → 0 / 480(320) 외삽
  float sx  = ry_right - ry_left;
  int ry0   = (int)(ry_left  - sx * MARGIN / (SCREEN_W - 2 * MARGIN));
  int ry480 = (int)(ry_right + sx * MARGIN / (SCREEN_W - 2 * MARGIN));

  float sy   = rx_bottom - rx_top;
  int rx0    = (int)(rx_top    - sy * MARGIN / (SCREEN_H - 2 * MARGIN));
  int rx320  = (int)(rx_bottom + sy * MARGIN / (SCREEN_H - 2 * MARGIN));

  Serial.println("\n===== Calibration Result =====");
  Serial.printf("x = map(ry, %d, %d, 0, 480);\n", ry0, ry480);
  Serial.printf("y = map(rx, %d, %d, 0, 320);\n", rx0, rx320);
  Serial.println("================================");

  tft.fillScreen(TFT_BLACK);

  tft.setTextColor(TFT_GREEN);
  tft.setTextSize(2);
  tft.setCursor(10, 15);
  tft.println("Calibration Done!");

  tft.setTextColor(TFT_WHITE);
  tft.setTextSize(2);
  tft.setCursor(10, 60);
  tft.println("Copy to your sketch:");

  tft.setTextColor(TFT_YELLOW);
  tft.setTextSize(1);
  tft.setCursor(10, 100);
  tft.printf("x = map(ry, %d, %d, 0, 480);", ry0, ry480);
  tft.setCursor(10, 118);
  tft.printf("y = map(rx, %d, %d, 0, 320);", rx0, rx320);

  tft.setTextColor(TFT_DARKGREY);
  tft.setCursor(10, 160);
  tft.println("Raw values (rx / ry):");
  for (int i = 0; i < 4; i++) {
    tft.setCursor(10, 175 + i * 14);
    tft.printf("%s  rx=%4d  ry=%4d", LABEL[i], rawRX[i], rawRY[i]);
  }

  tft.setTextColor(TFT_CYAN);
  tft.setCursor(10, 270);
  tft.println("Press RESET to redo.");
}

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

  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
  pinMode(TOUCH_IRQ, INPUT_PULLUP);

  tft.init();
  tft.setRotation(1);

  showStep(0);
}

void loop() {
  if (step >= 4) return;

  uint16_t rx = 0, ry = 0;
  bool touched = (digitalRead(TOUCH_IRQ) == LOW) && tft.getTouchRaw(&rx, &ry);

  if (waitRelease) {
    if (!touched) waitRelease = false;
    return;
  }

  if (touched) {
    rawRX[step] = rx;
    rawRY[step] = ry;
    Serial.printf("[%d] %s  rx=%d  ry=%d\n", step + 1, LABEL[step], rx, ry);
    step++;
    waitRelease = true;
    if (step >= 4) showResult();
    else           showStep(step);
  }

  delay(20);
}

📌 6. i2r-02 통신 프로토콜

HMI(RP2040)와 PLC(ESP32)는 Serial1 UART 9600 bps로 JSON을 교환합니다.
메시지는 줄바꿈(\n)으로 구분합니다.

📌 PLC → HMI: 상태 브로드캐스트

{
  "c": "ti",
  "in":  [false, true, false, false],
  "out": [true, false, false, false],
  "wifi": true,
  "mqtt": true,
  "time": "14:30",
  "mac": "AA:BB:CC:DD:EE:FF"
}
타입 설명
c string "ti" = Telemetry Info
in bool[4] 디지털 입력 채널 0~3 상태
out bool[4] 디지털 출력 채널 0~3 상태
wifi bool WiFi 연결 여부
mqtt bool MQTT 브로커 연결 여부
time string 현재 시간 ("HH:MM")
mac string ESP32 MAC 주소

📌 PLC → HMI: 설정값 전달

{
  "c": "cfg",
  "ssid": "MyWifi",
  "password": "MyPassword",
  "e": "user@example.com",
  "mqttBroker": "mqtt.i2r.link"
}

📌 PLC → HMI: 펌웨어 다운로드 상태

{ "c": "df_start" }
{ "c": "df_result", "ok": 1 }

📌 HMI → PLC: 출력 채널 제어

{ "c": "so", "n": 0, "v": 1 }
설명
c "so" = Set Output
n 채널 번호 (0~3)
v 1 = ON, 0 = OFF

📌 HMI → PLC: WiFi/MQTT 설정 저장

{
  "c": "si",
  "e": "user@example.com",
  "ssid": "MyWifi",
  "password": "MyPassword",
  "mqttBroker": "mqtt.i2r.link"
}

📌 HMI → PLC: 기타 커맨드

JSON 설명
{"c":"ti","req":"config"} PLC에 저장된 설정값 요청 (부팅 시 자동 전송)
{"c":"ti","bleboot":1} 블루투스 설정 모드 요청
{"c":"df","f":"board-i2r-02-hmi.ino.bin"} OTA 펌웨어 업데이트 요청

📌 7. UI 구조

3개 탭으로 구성됩니다 (화면 해상도 480×320, 탭바 48px).

📌 탭 1: 제어판

  • INPUT LED (원형 60×60): PLC 입력 상태 반영, 틸 컬러 ON(#00B080)
  • OUTPUT 버튼 (88×68): 터치 시 so 커맨드 전송, 앰버 컬러 ON(#FF8C00)
  • 상태바: WiFi/MQTT 연결 아이콘, 현재 시간

📌 탭 2: 설정

  • WiFi SSID / 비밀번호 / 이메일 / MQTT 브로커 주소 입력
  • 가상 키보드 — 입력란 포커스 시 자동 표시
  • 연결 버튼: PLC에 설정 전송 + LittleFS 저장
  • 블루투스 버튼: BLE 설정 모드 요청
  • 펌웨어 버튼: OTA 업데이트 요청
  • 사용법 버튼: YouTube QR 코드 팝업

📌 탭 3: 메뉴얼

  • 주요 YouTube/GitHub 링크를 버튼으로 나열
  • 터치 시 QR 코드 팝업 표시 (전체화면 오버레이)

📌 8. 터치 캘리브레이션

이 패널은 XY 축이 스왑되어 있어 실측 기반 보정이 필요합니다.

코너 raw rx (0xD0) raw ry (0x90) 화면 좌표
좌상단 3814 214 (0, 0)
우상단 3801 3797 (480, 0)
우하단 351 3764 (480, 320)
좌하단 271 194 (0, 320)

재캘리브레이션이 필요하면 hmi-i2r02.inoTOUCH_DEBUG 매크로 주석을 해제한 뒤 4 코너를 눌러 raw 값을 확인하고, map() 인자를 교체하세요.


📌 9. 설정 파일 저장 (LittleFS)

HMI 보드는 /config.json에 설정을 저장합니다.

{
  "ssid":  "MyWifi",
  "pw":    "MyPassword",
  "email": "user@example.com",
  "mqtt":  "mqtt.i2r.link"
}
  • 부팅 시 자동 로드 → 설정 탭 입력란에 표시
  • PLC에서 cfg 커맨드 수신 시에도 자동 저장

📌 10. 필요 라이브러리

라이브러리 용도
LVGL v9.x UI 프레임워크
TFT_eSPI ILI9488 디스플레이 드라이버

📌 11. lv_conf.h 설정

#define LV_COLOR_DEPTH  16
#define LV_USE_TFT_ESPI  0   // 수동 드라이버 사용 (중요)

📌 12. claude 를 이용한 바이브 코딩

바이브 코딩이란?

바이브 코딩(Vibe Coding) 은 코드를 직접 타이핑하는 대신,
"이런 느낌으로 동작했으면 좋겠다" 는 의도를 AI에게 자연어로 설명하면
AI가 전체 코드를 작성·수정해 주는 개발 방식입니다.

코드를 몰라도 됩니다. 원하는 동작을 한국어로 설명하면 Claude가 소스를 완성합니다.


사용 방법

1단계 — 템플릿 파일 준비

이 프로젝트는 LCD35.md 에 전체 소스 코드와 회로 정보를 담아 두었습니다.
Claude가 이 파일을 읽으면 하드웨어 구성과 기존 코드를 완전히 파악합니다.

2단계 — Claude에게 프롬프트 입력

아래 형식을 복사해서 claude.ai 또는 Claude Code에 붙여넣으세요.

https://raw.githubusercontent.com/kdi6033/i2r/main/LCD35.md 읽고 [원하는 동작을 한국어로 설명]

3단계 — 결과 코드를 Arduino IDE에 붙여넣기

Claude가 수정된 .ino 파일을 출력합니다. 그대로 복사해서 업로드하면 됩니다.


바이브 코딩 예시

예시 1 — 화면에 새 데이터 표시

https://raw.githubusercontent.com/kdi6033/i2r/main/LCD35.md 읽고
ESP32에서 {"c":"ti","rpm":1500,"temp":72.3} JSON을 보내면
제어판 탭 중앙에 RPM과 온도를 크게 표시해줘

예시 2 — 채널 수 변경

https://raw.githubusercontent.com/kdi6033/i2r/main/LCD35.md 읽고
입력 LED를 4개에서 8개로 늘려줘
두 줄로 배치하고 LED 크기는 46px로 줄여줘

예시 3 — 다크 테마 적용

https://raw.githubusercontent.com/kdi6033/i2r/main/LCD35.md 읽고
전체 색상을 다크 테마로 바꿔줘
배경은 #1A1A2E, 패널은 #16213E, 텍스트는 흰색 계열로 해줘

예시 4 — 새 탭 추가

https://raw.githubusercontent.com/kdi6033/i2r/main/LCD35.md 읽고
"그래프" 탭을 추가해줘
{"c":"ti","val":숫자} 수신 시 LVGL lv_chart로 실시간 선 그래프를 그려줘
최근 30개 값을 표시하고 자동 스크롤 해줘

5 — 버튼 이름 변경

https://raw.githubusercontent.com/kdi6033/i2r/main/LCD35.md 읽고
출력 버튼 4개의 이름을 숫자 대신
"펌프", "히터", "팬", "밸브" 로 바꿔줘

바이브 코딩 워크플로우

┌─────────────────────────────────────────────┐
│  1. LCD35.md URL + 원하는 동작 설명          │
│     → Claude에 입력                          │
└────────────────┬────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────┐
│  2. Claude가 LCD35.md 전체를 읽음            │
│     - 하드웨어 핀, 라이브러리 파악           │
│     - 기존 소스 구조 이해                    │
│     - 요청 사항 반영하여 코드 수정           │
└────────────────┬────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────┐
│  3. 수정된 .ino 파일 출력                    │
│     → Arduino IDE에 붙여넣기                 │
│     → RP2040에 업로드                        │
└─────────────────────────────────────────────┘

주의사항

항목 설명
터치 캘리브레이션 패널마다 값이 다릅니다. CAL_RY_MIN 등 4개 상수를 본인 패널에 맞게 교체하세요.
폰트 파일 NotoSansKR_20.h, NotoSansKR_20.c 는 그대로 사용합니다.
User_Setup.h TFT_eSPI 라이브러리 폴더에 복사해야 합니다.
라이브러리 버전 LVGL v9.x, TFT_eSPI, ArduinoJson v7.x 를 사용합니다.

더 잘 활용하는 팁

  • 구체적일수록 좋습니다. "버튼 추가해줘" 보다 "제어판 탭 오른쪽 하단에 비상정지 버튼을 빨간색으로 추가해줘" 가 낫습니다.
  • JSON 포맷을 명시하면 통신 파싱 코드까지 자동 생성됩니다.
  • 결과가 마음에 안 들면 이어서 수정 요청할 수 있습니다. "방금 코드에서 버튼 크기만 키워줘"
  • Claude Code(VS Code 확장)를 사용하면 파일을 직접 수정해줍니다.

HMI-i2r 다운로드

아래 링크에서 zip 파일을 다운로드해서 프로그램 하세요.

hmi-i2r.zip 다운로드

압축을 해제하면 hmi-i2r 폴더가 생성됩니다.
hmi-i2r.ino 파일을 Arduino IDE로 열어 RP2040-Zero에 업로드하세요.


✅ 10. HMI 한글 터치스크린 (CrowPanel Pico Display 3.5)

  • ESP32 IoT PLC와 CrowPanel(RP2040)을 RS232 직렬 통신으로 연결하여,
  • CrowPanel은 사용자 인터페이스(UI) 및 I2C 센서 계측을 담당하고,
  • ESP32는 릴레이 제어 및 클라우드(MQTT) 연동을 담당하는 IoT 제어 시스템입니다.
  • IoT PLC는 CrowPanel(RP2040) 터치 스크린을 사용합니다. 와이파이가 있는 제품을 전파인증을 받아 판매해야 함으로 인증이 필요없는 제품을 선택했습니다. 아래는 당사의 기술자료 입니다.

시스템 구성

항목 설명
RS232 마스터 CrowPanel (RP2040)
RS232 슬레이브 ESP32
센서 계측 CrowPanel에서 I2C로 직접 계측 (조도, 온습도 등)
UI 이벤트 터치 버튼 입력 → RS232 메시지 전송 ({"cmd":"on"} 등)
액츄에이터 제어 ESP32에서 명령 수신 후 릴레이/모터 제어
클라우드 연동 ESP32 → MQTT 전송 (선택적 기능)

연결 방식

  • CrowPanel (RP2040) ↔ ESP32 (IoT PLC)
    • RS232 직렬 통신 사용
    • 안정적인 양방향 데이터 전송 지원
  • 센서 계측 (CrowPanel 내부)
    • RP2040이 I2C 버스로 조도, 온도, 습도 등 센서를 직접 읽음
    • I2C는 최대 32바이트 프레임 제한과 양방향 통신의 한계가 있어, PLC와 UI 간 통신에는 적합하지 않음
    • 따라서 UI ↔ PLC 통신은 RS232, 센서 ↔ RP2040 통신만 I2C로 유지

📌 CrowPanel Pico Display 3.5" HMI 모듈

이 보드는 RP2040 MCU + 3.5" 480×320 TFT LCD + 정전식 터치스크린이 결합된 HMI(Human Machine Interface) 모듈입니다. LVGL, C/C++, MicroPython을 지원하여 다양한 UI 및 IoT 응용에 활용할 수 있습니다.

회로도

📌 주요 기능

  • RP2040 듀얼코어 MCU 내장 → 외부 보드 없이 단독 동작 가능
  • 3.5인치 480×320 해상도 TFT 디스플레이 → 선명한 그래픽 표현
  • 정전식 터치스크린 → 직관적인 UI 제어 가능
  • LVGL 지원 → 버튼, 슬라이더, 차트, 키보드 등 고급 GUI 구성
  • USB-C 포트 → 전원 공급 및 펌웨어 업로드 지원
  • C/C++ & MicroPython 개발 환경 → 초보자부터 전문가까지 사용 가능
  • GPIO 확장 핀 제공 → 센서, 액추에이터 등 외부 장치 연결 가능
  • HMI 전용 설계 → IoT, 스마트 제어, 교육용 UI 개발에 최적화

⚙️ GPIO Pin Definition

Pin Function Pin Function
P1 GP0 / UART0 TX P9 GP19
P2 GP1 / UART0 RX P10 GP20 / I2C0 SDA
P3 GP2 / I2C1 SDA P11 GP21 / I2C0 SCL
P4 GP3 / I2C1 SCL P12 GP26 / ADC1 / I2C1 SDA
P5 GP4 / UART1 TX P13 GP27 / ADC0 / I2C1 SCL
P6 GP5 / UART1 TX P14 GP28 / ADC2
P7 GP6 / I2C1 SDA P15 GND
P8 GP7 / I2C1 SCL P16 VCC 3V3

⚙️ 주요 사양 (Specifications)

항목 내용
MCU Raspberry Pi RP2040
디스플레이 3.5인치 TFT LCD
해상도 480 × 320 pixels
터치 방식 정전식 터치스크린 (CTP)
터치 컨트롤러 FT6236 (I2C 인터페이스)
플래시 메모리 16MB (외부 Flash)
USB USB Type-C (프로그램 업로드 및 전원 공급)
I/O 인터페이스 20핀 확장헤더 (I2C, SPI, UART, GPIO 등)
전원 입력 5V (USB-C 또는 확장 커넥터)
크기 85mm × 55mm
사용가능 언어 C/C++, MicroPython
LVGL 지원 LVGL 8.x / 9.x 지원 (UI 개발용 라이브러리)

터치스크린 프로토콜 조도센서 값을 참조하는 예제

{ "c": "ti", "light": 120 }

✅ 11. LVGL, TFT_eSPI 설치

그래픽과 터치스크린을 구현하기 위한 구조를 설명하겠습니다.

  • LVGL 에서는 그래픽에 필요한 설정을 해야 합니다.
  • ILI9341 는 터치 스크린마다 사용하는 종류가 달라지므로 TFT_eSPI 에 정의를 해야 합니다.

📺 CrowPanel(RP2040) 기술 자료
참조기술문서 및 프로그램 다운로드
회로도
유튜브 시청
Wiki

아두이노에서 보드 설정할 때 사용하세요

Additional Boards Manager URLs
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

📌 1. LVGL + TFT_eSPI + ILI9341 관계

1) LVGL (Light and Versatile Graphics Library)

  • 오픈소스 GUI 라이브러리
  • 버튼, 슬라이더, 차트, 탭, 텍스트 등 고급 UI 위젯 제공
  • 하드웨어 독립적 → 직접 LCD 제어하지 않음
  • 대신 “화면에 이 사각형을 그려라” 같은 **그리기 요청(draw call)**만 처리
  • 실제 픽셀 출력은 **디스플레이 드라이버(TFT_eSPI)**에 맡김
  • 👉 즉, UI 엔진 (사용자 인터페이스 제공)

2) TFT_eSPI (디스플레이 드라이버)

  • ESP32 전용 고속 그래픽 라이브러리
  • ILI9341, ST7735, ST7796 등 여러 디스플레이 칩 지원
  • tft.drawPixel(x, y, color) 같은 저수준 API 제공
  • SPI 통신을 최적화하여 빠른 화면 갱신 가능
  • LVGL 같은 상위 라이브러리가 직접 ILI9341을 제어하지 않고 TFT_eSPI를 통해 제어
  • 👉 즉, 소프트웨어 드라이버 (ESP32 ↔ ILI9341 연결)

3) ILI9341 (디스플레이 컨트롤러)

  • 2.4~3.2인치 TFT LCD에서 많이 사용하는 디스플레이 드라이버 칩
  • SPI 또는 병렬 인터페이스를 통해 픽셀 데이터를 수신
  • 하드웨어적으로 LCD 패널의 픽셀 제어, 컬러 표현, 메모리 관리를 담당
  • 예: 0,0 좌표에 빨간색 픽셀 그려라 → ILI9341이 LCD에 실제 반영 👉 즉, 하드웨어 칩 (디스플레이 컨트롤러)

4) 관계 정리

  • LVGL → UI를 논리적으로 관리 (버튼, 차트, 텍스트 등)
  • TFT_eSPI → LVGL이 요청한 그래픽을 픽셀 단위 명령으로 변환
  • ILI9341 → 실제 TFT LCD 패널의 픽셀을 제어하여 화면에 출력

5) 데이터 흐름

LVGL (UI/위젯 관리)
   ↓
TFT_eSPI (ESP32용 디스플레이 드라이버)
   ↓
ILI9341 (하드웨어 컨트롤러)
   ↓
LCD 화면 출력

📌 2. LVGL (Light and Versatile Graphics Library) 디스플레이 드라이버 적은 메모리에서도 부드럽고 직관적인 터치 UI를 구현할 수 있는 오픈소스 그래픽 라이브러리입니다
다음 기술 사이트를 참조하세요
lvgl 기술사이트

📌 📺 시연 영상 (IoT PLC HMI, LVGL 설치와 Hello 띄우기)

** 디스플레이 드라이버의 역할 ** LVGL 디스플레이 드라이버는 아래 두 가지를 담당합니다.

  1. Flush callback (disp_drv.flush_cb)
  • LVGL이 "이 영역을 빨간색으로 칠해라"라고 명령을 내리면,
  • flush_cb 함수가 해당 픽셀 데이터를 실제 LCD 드라이버(ST7796, ILI9341 등) 로 전송합니다.
  1. Buffer 관리 (lv_disp_draw_buf_t)
  • LVGL은 화면 전체 크기만큼의 버퍼를 만들 필요가 없습니다.
  • 보통 화면의 1/10~1/20 크기 버퍼만 두고, 해당 영역만 갱신합니다.
  • 이 버퍼와 실제 하드웨어 전송을 연결하는 것이 디스플레이 드라이버의 역할입니다.

📌 3. lvgl 설치 후 lv_conf.h 파일 만들기 lv_conf_template.h 를 필요한 항목을 수정해서 lv_conf.h 로 저장합니다.

📌 요약 (수정해야 하는 항목만) 1.#if 0 → #if 1 2. LV_COLOR_DEPTH = 16 (ILI9341) 3. LV_MEM_CUSTOM = 1 (malloc/free 사용) 4. LV_TICK_CUSTOM = 1, millis() 기반 설정 5. LV_DPI_DEF = 130 (3.5" 기준) 6. 폰트 설정: 필요 시 한글 폰트 추가 7. 위젯: 필요한 것만 1로 유지 8. 데모: 학습 시 1, 실제 코드에선 0

1) 파일 활성화

#if 0 /*Set it to "1" to enable content*/

👉 0 → 1 로 변경해야 LVGL이 이 설정을 읽습니다.

#if 1 /*Set it to "1" to enable content*/

2) 색상 설정

#define LV_COLOR_DEPTH 16
#define LV_COLOR_16_SWAP 0

ILI9341은 RGB565(16bit) 사용 → LV_COLOR_DEPTH 16 유지 색상이 뒤집혀 나오면 LV_COLOR_16_SWAP을 1로 변경

3) 메모리 설정 현재:

#define LV_MEM_CUSTOM 0

👉 ESP32는 malloc/free를 쓰는 게 일반적이므로 1로 변경

#define LV_MEM_CUSTOM 1
#define LV_MEM_CUSTOM_INCLUDE <stdlib.h>
#define LV_MEM_CUSTOM_ALLOC malloc
#define LV_MEM_CUSTOM_FREE free

4) Tick 설정 현재:

#define LV_TICK_CUSTOM 0

👉 Arduino 환경에서는 millis() 사용하도록 1로 변경

#define LV_TICK_CUSTOM 1
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h"
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())

5) DPI 설정

#define LV_DPI_DEF 130

3.5" 320x480 해상도면 130 정도가 적당 (필요하면 100~140 사이 조정 가능)

6) 폰트 설정 (한글 UI 필요 시) 기본값:

#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_DEFAULT &lv_font_montserrat_14

👉 한글 표시하려면 커스텀 폰트 선언 추가

#define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(NotoSansKR_20)

(NotoSansKR_20.c 파일을 프로젝트에 포함해야 함)

7) 위젯 사용 여부 기본값은 대부분 1 → 그대로 둬도 무방 👉 메모리 절약하려면 안 쓰는 위젯을 0으로 꺼두기 예: 버튼, 레이블, 슬라이더만 쓸 경우

#define LV_USE_BTN     1
#define LV_USE_LABEL   1
#define LV_USE_SLIDER  1

8) 데모/예제

#define LV_BUILD_EXAMPLES 1
#define LV_USE_DEMO_WIDGETS 1

처음 테스트할 때는 1로 켜두면 좋음 실제 제품 코드에서는 불필요하면 0으로 꺼서 용량/속도 최적화


💻 lv_conf.h v9 붙여넣기 해서 사용하세요
/**
 * @file lv_conf.h
 * Configuration file for v8.3.11
 */

/*
 * Copy this file as `lv_conf.h`
 * 1. simply next to the `lvgl` folder
 * 2. or any other places and
 *    - define `LV_CONF_INCLUDE_SIMPLE`
 *    - add the path as include path
 */

/* clang-format off */
#if 1 /*Set it to "1" to enable content*/

#ifndef LV_CONF_H
#define LV_CONF_H

#include <stdint.h>

/*====================
   COLOR SETTINGS
 *====================*/

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16

/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 0

/*Enable features to draw on transparent background.
 *It's required if opa, and transform_* style properties are used.
 *Can be also used if the UI is above another layer, e.g. an OSD menu or video player.*/
#define LV_COLOR_SCREEN_TRANSP 0

/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
 * 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
#define LV_COLOR_MIX_ROUND_OFS 0

/*Images pixels with this color will not be drawn if they are chroma keyed)*/
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00)         /*pure green*/

/*=========================
   MEMORY SETTINGS
 *=========================*/

/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 1
#if LV_MEM_CUSTOM == 0
    /*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
    #define LV_MEM_SIZE (48U * 1024U)          /*[bytes]*/

    /*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
    #define LV_MEM_ADR 0     /*0: unused*/
    /*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
    #if LV_MEM_ADR == 0
        #undef LV_MEM_POOL_INCLUDE
        #undef LV_MEM_POOL_ALLOC
    #endif

#else       /*LV_MEM_CUSTOM*/
    #define LV_MEM_CUSTOM_INCLUDE <stdlib.h>   /*Header for the dynamic memory function*/
    #define LV_MEM_CUSTOM_ALLOC   malloc
    #define LV_MEM_CUSTOM_FREE    free
    #define LV_MEM_CUSTOM_REALLOC realloc
#endif     /*LV_MEM_CUSTOM*/

/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
 *You will see an error log message if there wasn't enough buffers. */
#define LV_MEM_BUF_MAX_NUM 16

/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
#define LV_MEMCPY_MEMSET_STD 0

/*====================
   HAL SETTINGS
 *====================*/

/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 30      /*[ms]*/

/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 30     /*[ms]*/

/*Use a custom tick source that tells the elapsed time in milliseconds.
 *It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
    #define LV_TICK_CUSTOM_INCLUDE "Arduino.h"         /*Header for the system time function*/
    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())    /*Expression evaluating to current system time in ms*/
    /*If using lvgl as ESP32 component*/
    // #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
    // #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
#endif   /*LV_TICK_CUSTOM*/

/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
 *(Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI_DEF 130     /*[px/inch]*/

/*=======================
 * FEATURE CONFIGURATION
 *=======================*/

/*-------------
 * Drawing
 *-----------*/

/*Enable complex draw engine.
 *Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
#define LV_DRAW_COMPLEX 1
#if LV_DRAW_COMPLEX != 0

    /*Allow buffering some shadow calculation.
    *LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
    *Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
    #define LV_SHADOW_CACHE_SIZE 0

    /* Set number of maximally cached circle data.
    * The circumference of 1/4 circle are saved for anti-aliasing
    * radius * 4 bytes are used per circle (the most often used radiuses are saved)
    * 0: to disable caching */
    #define LV_CIRCLE_CACHE_SIZE 4
#endif /*LV_DRAW_COMPLEX*/

/**
 * "Simple layers" are used when a widget has `style_opa < 255` to buffer the widget into a layer
 * and blend it as an image with the given opacity.
 * Note that `bg_opa`, `text_opa` etc don't require buffering into layer)
 * The widget can be buffered in smaller chunks to avoid using large buffers.
 *
 * - LV_LAYER_SIMPLE_BUF_SIZE: [bytes] the optimal target buffer size. LVGL will try to allocate it
 * - LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE: [bytes]  used if `LV_LAYER_SIMPLE_BUF_SIZE` couldn't be allocated.
 *
 * Both buffer sizes are in bytes.
 * "Transformed layers" (where transform_angle/zoom properties are used) use larger buffers
 * and can't be drawn in chunks. So these settings affects only widgets with opacity.
 */
#define LV_LAYER_SIMPLE_BUF_SIZE          (24 * 1024)
#define LV_LAYER_SIMPLE_FALLBACK_BUF_SIZE (3 * 1024)

/*Default image cache size. Image caching keeps the images opened.
 *If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
 *With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
 *However the opened images might consume additional RAM.
 *0: to disable caching*/
#define LV_IMG_CACHE_DEF_SIZE 0

/*Number of stops allowed per gradient. Increase this to allow more stops.
 *This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#define LV_GRADIENT_MAX_STOPS 2

/*Default gradient buffer size.
 *When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again.
 *LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes.
 *If the cache is too small the map will be allocated only while it's required for the drawing.
 *0 mean no caching.*/
#define LV_GRAD_CACHE_DEF_SIZE 0

/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
 *LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
 *The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#define LV_DITHER_GRADIENT 0
#if LV_DITHER_GRADIENT
    /*Add support for error diffusion dithering.
     *Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
     *The increase in memory consumption is (24 bits * object's width)*/
    #define LV_DITHER_ERROR_DIFFUSION 0
#endif

/*Maximum buffer size to allocate for rotation.
 *Only used if software rotation is enabled in the display driver.*/
#define LV_DISP_ROT_MAX_BUF (10*1024)

/*-------------
 * GPU
 *-----------*/

/*Use Arm's 2D acceleration library Arm-2D */
#define LV_USE_GPU_ARM2D 0

/*Use STM32's DMA2D (aka Chrom Art) GPU*/
#define LV_USE_GPU_STM32_DMA2D 0
#if LV_USE_GPU_STM32_DMA2D
    /*Must be defined to include path of CMSIS header of target processor
    e.g. "stm32f7xx.h" or "stm32f4xx.h"*/
    #define LV_GPU_DMA2D_CMSIS_INCLUDE
#endif

/*Enable RA6M3 G2D GPU*/
#define LV_USE_GPU_RA6M3_G2D 0
#if LV_USE_GPU_RA6M3_G2D
    /*include path of target processor
    e.g. "hal_data.h"*/
    #define LV_GPU_RA6M3_G2D_INCLUDE "hal_data.h"
#endif

/*Use SWM341's DMA2D GPU*/
#define LV_USE_GPU_SWM341_DMA2D 0
#if LV_USE_GPU_SWM341_DMA2D
    #define LV_GPU_SWM341_DMA2D_INCLUDE "SWM341.h"
#endif

/*Use NXP's PXP GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_PXP 0
#if LV_USE_GPU_NXP_PXP
    /*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
    *   and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
    *   has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
    *0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
    */
    #define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
#endif

/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_VG_LITE 0

/*Use SDL renderer API*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
    #define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
    /*Texture cache size, 8MB by default*/
    #define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
    /*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
    #define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
#endif

/*-------------
 * Logging
 *-----------*/

/*Enable the log module*/
#define LV_USE_LOG 0
#if LV_USE_LOG

    /*How important log should be added:
    *LV_LOG_LEVEL_TRACE       A lot of logs to give detailed information
    *LV_LOG_LEVEL_INFO        Log important events
    *LV_LOG_LEVEL_WARN        Log if something unwanted happened but didn't cause a problem
    *LV_LOG_LEVEL_ERROR       Only critical issue, when the system may fail
    *LV_LOG_LEVEL_USER        Only logs added by the user
    *LV_LOG_LEVEL_NONE        Do not log anything*/
    #define LV_LOG_LEVEL LV_LOG_LEVEL_WARN

    /*1: Print the log with 'printf';
    *0: User need to register a callback with `lv_log_register_print_cb()`*/
    #define LV_LOG_PRINTF 0

    /*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
    #define LV_LOG_TRACE_MEM        1
    #define LV_LOG_TRACE_TIMER      1
    #define LV_LOG_TRACE_INDEV      1
    #define LV_LOG_TRACE_DISP_REFR  1
    #define LV_LOG_TRACE_EVENT      1
    #define LV_LOG_TRACE_OBJ_CREATE 1
    #define LV_LOG_TRACE_LAYOUT     1
    #define LV_LOG_TRACE_ANIM       1

#endif  /*LV_USE_LOG*/

/*-------------
 * Asserts
 *-----------*/

/*Enable asserts if an operation is failed or an invalid data is found.
 *If LV_USE_LOG is enabled an error message will be printed on failure*/
#define LV_USE_ASSERT_NULL          1   /*Check if the parameter is NULL. (Very fast, recommended)*/
#define LV_USE_ASSERT_MALLOC        1   /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
#define LV_USE_ASSERT_STYLE         0   /*Check if the styles are properly initialized. (Very fast, recommended)*/
#define LV_USE_ASSERT_MEM_INTEGRITY 0   /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
#define LV_USE_ASSERT_OBJ           0   /*Check the object's type and existence (e.g. not deleted). (Slow)*/

/*Add a custom handler when assert happens e.g. to restart the MCU*/
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1);   /*Halt by default*/

/*-------------
 * Others
 *-----------*/

/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 0
#if LV_USE_PERF_MONITOR
    #define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif

/*1: Show the used memory and the memory fragmentation
 * Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0
#if LV_USE_MEM_MONITOR
    #define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif

/*1: Draw random colored rectangles over the redrawn areas*/
#define LV_USE_REFR_DEBUG 0

/*Change the built in (v)snprintf functions*/
#define LV_SPRINTF_CUSTOM 0
#if LV_SPRINTF_CUSTOM
    #define LV_SPRINTF_INCLUDE <stdio.h>
    #define lv_snprintf  snprintf
    #define lv_vsnprintf vsnprintf
#else   /*LV_SPRINTF_CUSTOM*/
    #define LV_SPRINTF_USE_FLOAT 0
#endif  /*LV_SPRINTF_CUSTOM*/

#define LV_USE_USER_DATA 1

/*Garbage Collector settings
 *Used if lvgl is bound to higher level language and the memory is managed by that language*/
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
    #define LV_GC_INCLUDE "gc.h"                           /*Include Garbage Collector related things*/
#endif /*LV_ENABLE_GC*/

/*=====================
 *  COMPILER SETTINGS
 *====================*/

/*For big endian systems set to 1*/
#define LV_BIG_ENDIAN_SYSTEM 0

/*Define a custom attribute to `lv_tick_inc` function*/
#define LV_ATTRIBUTE_TICK_INC

/*Define a custom attribute to `lv_timer_handler` function*/
#define LV_ATTRIBUTE_TIMER_HANDLER

/*Define a custom attribute to `lv_disp_flush_ready` function*/
#define LV_ATTRIBUTE_FLUSH_READY

/*Required alignment size for buffers*/
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1

/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
 * E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN

/*Attribute to mark large constant arrays for example font's bitmaps*/
#define LV_ATTRIBUTE_LARGE_CONST

/*Compiler prefix for a big array declaration in RAM*/
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY

/*Place performance critical functions into a faster memory (e.g RAM)*/
#define LV_ATTRIBUTE_FAST_MEM

/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
#define LV_ATTRIBUTE_DMA

/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
 *should also appear on LVGL binding API such as Micropython.*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/

/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
#define LV_USE_LARGE_COORD 0

/*==================
 *   FONT USAGE
 *===================*/

/*Montserrat fonts with ASCII range and some symbols using bpp = 4
 *https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8  0
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 0
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 0
#define LV_FONT_MONTSERRAT_18 0
#define LV_FONT_MONTSERRAT_20 1
#define LV_FONT_MONTSERRAT_22 0
#define LV_FONT_MONTSERRAT_24 0
#define LV_FONT_MONTSERRAT_26 0
#define LV_FONT_MONTSERRAT_28 1
#define LV_FONT_MONTSERRAT_30 0
#define LV_FONT_MONTSERRAT_32 0
#define LV_FONT_MONTSERRAT_34 1
#define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 0
#define LV_FONT_MONTSERRAT_40 0
#define LV_FONT_MONTSERRAT_42 0
#define LV_FONT_MONTSERRAT_44 0
#define LV_FONT_MONTSERRAT_46 0
#define LV_FONT_MONTSERRAT_48 0

/*Demonstrate special features*/
#define LV_FONT_MONTSERRAT_12_SUBPX      0
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0  /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0  /*Hebrew, Arabic, Persian letters and all their forms*/
#define LV_FONT_SIMSUN_16_CJK            0  /*1000 most common CJK radicals*/

/*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8  0
#define LV_FONT_UNSCII_16 0

/*Optionally declare custom fonts here.
 *You can use these fonts as default font too and they will be available globally.
 *E.g. #define LV_FONT_CUSTOM_DECLARE   LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
/*#define LV_FONT_CUSTOM_DECLARE */
#define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(NotoSansKR_20)

/*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_14

/*Enable handling large font and/or fonts with a lot of characters.
 *The limit depends on the font size, font face and bpp.
 *Compiler error will be triggered if a font needs it.*/
#define LV_FONT_FMT_TXT_LARGE 0

/*Enables/disables support for compressed fonts.*/
#define LV_USE_FONT_COMPRESSED 1

/*Enable subpixel rendering*/
#define LV_USE_FONT_SUBPX 0
#if LV_USE_FONT_SUBPX
    /*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
    #define LV_FONT_SUBPX_BGR 0  /*0: RGB; 1:BGR order*/
#endif

/*Enable drawing placeholders when glyph dsc is not found*/
#define LV_USE_FONT_PLACEHOLDER 1

/*=================
 *  TEXT SETTINGS
 *=================*/

/**
 * Select a character encoding for strings.
 * Your IDE or editor should have the same character encoding
 * - LV_TXT_ENC_UTF8
 * - LV_TXT_ENC_ASCII
 */
#define LV_TXT_ENC LV_TXT_ENC_UTF8

/*Can break (wrap) texts on these chars*/
#define LV_TXT_BREAK_CHARS " ,.;:-_"

/*If a word is at least this long, will break wherever "prettiest"
 *To disable, set to a value <= 0*/
#define LV_TXT_LINE_BREAK_LONG_LEN 0

/*Minimum number of characters in a long word to put on a line before a break.
 *Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3

/*Minimum number of characters in a long word to put on a line after a break.
 *Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3

/*The control character to use for signalling text recoloring.*/
#define LV_TXT_COLOR_CMD "#"

/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
 *The direction will be processed according to the Unicode Bidirectional Algorithm:
 *https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
#define LV_USE_BIDI 0
#if LV_USE_BIDI
    /*Set the default direction. Supported values:
    *`LV_BASE_DIR_LTR` Left-to-Right
    *`LV_BASE_DIR_RTL` Right-to-Left
    *`LV_BASE_DIR_AUTO` detect texts base direction*/
    #define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
#endif

/*Enable Arabic/Persian processing
 *In these languages characters should be replaced with an other form based on their position in the text*/
#define LV_USE_ARABIC_PERSIAN_CHARS 0

/*==================
 *  WIDGET USAGE
 *================*/

/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/

#define LV_USE_ARC        1

#define LV_USE_BAR        1

#define LV_USE_BTN        1

#define LV_USE_BTNMATRIX  1

#define LV_USE_CANVAS     1

#define LV_USE_CHECKBOX   1

#define LV_USE_DROPDOWN   1   /*Requires: lv_label*/

#define LV_USE_IMG        1   /*Requires: lv_label*/

#define LV_USE_LABEL      1
#if LV_USE_LABEL
    #define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
    #define LV_LABEL_LONG_TXT_HINT 1  /*Store some extra info in labels to speed up drawing of very long texts*/
#endif

#define LV_USE_LINE       1

#define LV_USE_ROLLER     1   /*Requires: lv_label*/
#if LV_USE_ROLLER
    #define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
#endif

#define LV_USE_SLIDER     1   /*Requires: lv_bar*/

#define LV_USE_SWITCH     1

#define LV_USE_TEXTAREA   1   /*Requires: lv_label*/
#if LV_USE_TEXTAREA != 0
    #define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500    /*ms*/
#endif

#define LV_USE_TABLE      1

/*==================
 * EXTRA COMPONENTS
 *==================*/

/*-----------
 * Widgets
 *----------*/
#define LV_USE_ANIMIMG    1

#define LV_USE_CALENDAR   1
#if LV_USE_CALENDAR
    #define LV_CALENDAR_WEEK_STARTS_MONDAY 0
    #if LV_CALENDAR_WEEK_STARTS_MONDAY
        #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
    #else
        #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
    #endif

    #define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March",  "April", "May",  "June", "July", "August", "September", "October", "November", "December"}
    #define LV_USE_CALENDAR_HEADER_ARROW 1
    #define LV_USE_CALENDAR_HEADER_DROPDOWN 1
#endif  /*LV_USE_CALENDAR*/

#define LV_USE_CHART      1

#define LV_USE_COLORWHEEL 1

#define LV_USE_IMGBTN     1

#define LV_USE_KEYBOARD   1

#define LV_USE_LED        1

#define LV_USE_LIST       1

#define LV_USE_MENU       1

#define LV_USE_METER      1

#define LV_USE_MSGBOX     1

#define LV_USE_SPAN       1
#if LV_USE_SPAN
    /*A line text can contain maximum num of span descriptor */
    #define LV_SPAN_SNIPPET_STACK_SIZE 64
#endif

#define LV_USE_SPINBOX    1

#define LV_USE_SPINNER    1

#define LV_USE_TABVIEW    1

#define LV_USE_TILEVIEW   1

#define LV_USE_WIN        1

/*-----------
 * Themes
 *----------*/

/*A simple, impressive and very complete theme*/
#define LV_USE_THEME_DEFAULT 1
#if LV_USE_THEME_DEFAULT

    /*0: Light mode; 1: Dark mode*/
    #define LV_THEME_DEFAULT_DARK 0

    /*1: Enable grow on press*/
    #define LV_THEME_DEFAULT_GROW 1

    /*Default transition time in [ms]*/
    #define LV_THEME_DEFAULT_TRANSITION_TIME 80
#endif /*LV_USE_THEME_DEFAULT*/

/*A very simple theme that is a good starting point for a custom theme*/
#define LV_USE_THEME_BASIC 1

/*A theme designed for monochrome displays*/
#define LV_USE_THEME_MONO 1

/*-----------
 * Layouts
 *----------*/

/*A layout similar to Flexbox in CSS.*/
#define LV_USE_FLEX 1

/*A layout similar to Grid in CSS.*/
#define LV_USE_GRID 1

/*---------------------
 * 3rd party libraries
 *--------------------*/

/*File system interfaces for common APIs */

/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 0
#if LV_USE_FS_STDIO
    #define LV_FS_STDIO_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
    #define LV_FS_STDIO_PATH ""         /*Set the working directory. File/directory paths will be appended to it.*/
    #define LV_FS_STDIO_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

/*API for open, read, etc*/
#define LV_USE_FS_POSIX 0
#if LV_USE_FS_POSIX
    #define LV_FS_POSIX_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
    #define LV_FS_POSIX_PATH ""         /*Set the working directory. File/directory paths will be appended to it.*/
    #define LV_FS_POSIX_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

/*API for CreateFile, ReadFile, etc*/
#define LV_USE_FS_WIN32 0
#if LV_USE_FS_WIN32
    #define LV_FS_WIN32_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
    #define LV_FS_WIN32_PATH ""         /*Set the working directory. File/directory paths will be appended to it.*/
    #define LV_FS_WIN32_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/
#define LV_USE_FS_FATFS 0
#if LV_USE_FS_FATFS
    #define LV_FS_FATFS_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
    #define LV_FS_FATFS_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/
#define LV_USE_FS_LITTLEFS 0
#if LV_USE_FS_LITTLEFS
    #define LV_FS_LITTLEFS_LETTER '\0'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
    #define LV_FS_LITTLEFS_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

/*PNG decoder library*/
#define LV_USE_PNG 0

/*BMP decoder library*/
#define LV_USE_BMP 0

/* JPG + split JPG decoder library.
 * Split JPG is a custom format optimized for embedded systems. */
#define LV_USE_SJPG 0

/*GIF decoder library*/
#define LV_USE_GIF 0

/*QR code library*/
#define LV_USE_QRCODE 1

/*FreeType library*/
#define LV_USE_FREETYPE 0
#if LV_USE_FREETYPE
    /*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
    #define LV_FREETYPE_CACHE_SIZE (16 * 1024)
    #if LV_FREETYPE_CACHE_SIZE >= 0
        /* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
        /* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
        /* if font size >= 256, must be configured as image cache */
        #define LV_FREETYPE_SBIT_CACHE 0
        /* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
        /* (0:use system defaults) */
        #define LV_FREETYPE_CACHE_FT_FACES 0
        #define LV_FREETYPE_CACHE_FT_SIZES 0
    #endif
#endif

/*Tiny TTF library*/
#define LV_USE_TINY_TTF 0
#if LV_USE_TINY_TTF
    /*Load TTF data from files*/
    #define LV_TINY_TTF_FILE_SUPPORT 0
#endif

/*Rlottie library*/
#define LV_USE_RLOTTIE 0

/*FFmpeg library for image decoding and playing videos
 *Supports all major image formats so do not enable other image decoder with it*/
#define LV_USE_FFMPEG 0
#if LV_USE_FFMPEG
    /*Dump input information to stderr*/
    #define LV_FFMPEG_DUMP_FORMAT 0
#endif

/*-----------
 * Others
 *----------*/

/*1: Enable API to take snapshot for object*/
#define LV_USE_SNAPSHOT 0

/*1: Enable Monkey test*/
#define LV_USE_MONKEY 0

/*1: Enable grid navigation*/
#define LV_USE_GRIDNAV 0

/*1: Enable lv_obj fragment*/
#define LV_USE_FRAGMENT 0

/*1: Support using images as font in label or span widgets */
#define LV_USE_IMGFONT 0

/*1: Enable a published subscriber based messaging system */
#define LV_USE_MSG 0

/*1: Enable Pinyin input method*/
/*Requires: lv_keyboard*/
#define LV_USE_IME_PINYIN 0
#if LV_USE_IME_PINYIN
    /*1: Use default thesaurus*/
    /*If you do not use the default thesaurus, be sure to use `lv_ime_pinyin` after setting the thesauruss*/
    #define LV_IME_PINYIN_USE_DEFAULT_DICT 1
    /*Set the maximum number of candidate panels that can be displayed*/
    /*This needs to be adjusted according to the size of the screen*/
    #define LV_IME_PINYIN_CAND_TEXT_NUM 6

    /*Use 9 key input(k9)*/
    #define LV_IME_PINYIN_USE_K9_MODE      1
    #if LV_IME_PINYIN_USE_K9_MODE == 1
        #define LV_IME_PINYIN_K9_CAND_TEXT_NUM 3
    #endif // LV_IME_PINYIN_USE_K9_MODE
#endif

/*==================
* EXAMPLES
*==================*/

/*Enable the examples to be built with the library*/
#define LV_BUILD_EXAMPLES 1

/*===================
 * DEMO USAGE
 ====================*/

/*Show some widget. It might be required to increase `LV_MEM_SIZE` */
#define LV_USE_DEMO_WIDGETS 1
#if LV_USE_DEMO_WIDGETS
#define LV_DEMO_WIDGETS_SLIDESHOW 1
#endif

/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0

/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK 0
#if LV_USE_DEMO_BENCHMARK
/*Use RGB565A8 images with 16 bit color depth instead of ARGB8565*/
#define LV_DEMO_BENCHMARK_RGB565A8 0
#endif

/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS 0

/*Music player demo*/
#define LV_USE_DEMO_MUSIC 0
#if LV_USE_DEMO_MUSIC
    #define LV_DEMO_MUSIC_SQUARE    0
    #define LV_DEMO_MUSIC_LANDSCAPE 0
    #define LV_DEMO_MUSIC_ROUND     0
    #define LV_DEMO_MUSIC_LARGE     0
    #define LV_DEMO_MUSIC_AUTO_PLAY 0
#endif

/*--END OF LV_CONF_H--*/

#endif /*LV_CONF_H*/

#endif /*End of "Content enable"*/

📌 3. TFT_eSPI 디스플레이 드라이버 설치 CrowPanel 터치스크린은 RP2040 + ILI9488 (480x320) 디스플레이에 맞게 수정해야 합니다.

TFT_eSPI 라이브러리 설치
TFT_eSPI

참조기술문서 및 프로그램 다운로드
여기서 다운로드 받은 파일중에 User_Setup.h 를 아두이노 라이브러리 디렉토리에 복사 한다.
TFT_eSPI-1 World lvgl 라이브러리 설치
그림과 같이 꼭 8.3.11 버젼을 설치해야 프로그램이 실행 됩니다. 위에서 다운로드 받은 파일중에 lv_conf.h 를 아두이노 라이브러리 디렉토리에 복사 한다.
lvgl

  1. 드라이버 선택 기본값:
#define ILI9341_DRIVER

수정 후 (CrowPanel 제공):

#define ILI9488_DRIVER

👉 CrowPanel 3.5"는 ILI9488 드라이버 기반이므로, 드라이버를 ILI9341 → ILI9488로 변경.

  1. 디스플레이 해상도 기본값: 정의 없음 (ILI9341는 기본적으로 240x320) 수정 후:
#define TFT_WIDTH  320
#define TFT_HEIGHT 480

👉 CrowPanel 디스플레이 해상도(320×480)에 맞게 설정.

  1. 백라이트 제어 기본값:
// #define TFT_BL   32
// #define TFT_BACKLIGHT_ON HIGH

수정 후:

#define TFT_BL 18
#define TFT_BACKLIGHT_ON HIGH

👉 CrowPanel 보드에서 TFT 백라이트 제어 핀이 GPIO18이므로 핀 지정 추가.

  1. 핀 매핑 (SPI + 제어 핀)

기본값 (ESP8266/ESP32용 예시 핀):

#define TFT_MISO  PIN_D6
#define TFT_MOSI  PIN_D7
#define TFT_SCLK  PIN_D5
#define TFT_CS    PIN_D8
#define TFT_DC    PIN_D3
#define TFT_RST   PIN_D4

수정 후 (RP2040 CrowPanel 전용 핀맵):

#define TFT_MISO  12
#define TFT_MOSI  11
#define TFT_SCLK  10
#define TFT_CS     9
#define TFT_DC     8
#define TFT_RST   15
#define TOUCH_CS  16

👉 RP2040 Pico HMI 보드에 맞게 핀 번호를 10~16번 핀으로 새롭게 매핑.

  1. SPI 포트 선택 기본값: 없음

수정 후:

#define TFT_SPI_PORT 1

👉 RP2040은 SPI0/SPI1을 선택할 수 있는데, CrowPanel 보드는 SPI1을 사용.

  1. SPI 클럭 주파수 기본값:
#define SPI_FREQUENCY  27000000

수정 후:

#define SPI_FREQUENCY  80000000

👉 ILI9488 드라이버와 RP2040 성능에 맞춰 최대 80MHz로 변경.

  1. 추가된 항목 터치스크린 CS 핀 정의:
#define TOUCH_CS 16

👉 XPT2046 터치 컨트롤러용 CS 핀을 별도로 지정.

기본적인 문자를 출력해 봅니다.

💻 아두이노 예제 - 문자 출력
#include <lvgl.h>
#include <TFT_eSPI.h>

/* 디스플레이 해상도 */
static const uint16_t screenWidth  = 480;
static const uint16_t screenHeight = 320;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];

TFT_eSPI tft = TFT_eSPI();  // TFT 객체

/* 화면 갱신 함수 */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
  uint32_t w = area->x2 - area->x1 + 1;
  uint32_t h = area->y2 - area->y1 + 1;

  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

void setup() {
  Serial.begin(115200);
  lv_init();        // LVGL 초기화
  tft.begin();      // TFT 초기화
  tft.setRotation(3);  // 필요에 따라 회전값 변경

  // 버퍼 초기화
  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);

  // 디스플레이 드라이버 설정
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

  // 간단한 Hello World 라벨
  lv_obj_t *label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Hello World");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);  // 가운데 정렬
}

void loop() {
  lv_timer_handler();  // LVGL 내부 작업 처리
  delay(5);
}
💻 아두이노 예제 - 버튼 출력 프로그램
#include <lvgl.h>
#include <TFT_eSPI.h>

static const uint16_t screenWidth  = 480;
static const uint16_t screenHeight = 320;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];

TFT_eSPI tft = TFT_eSPI();  // TFT 인스턴스

/* 화면 갱신 콜백 */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
  uint32_t w = area->x2 - area->x1 + 1;
  uint32_t h = area->y2 - area->y1 + 1;

  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

/* 버튼 클릭 이벤트 핸들러 */
void btn_event_cb(lv_event_t *e) {
  lv_obj_t *btn = lv_event_get_target(e);
  const char *txt = lv_label_get_text(lv_obj_get_child(btn, 0));
  Serial.print("Button pressed: ");
  Serial.println(txt);
}

void setup() {
  Serial.begin(115200);
  lv_init();
  tft.begin();
  tft.setRotation(3);  // 필요 시 회전 변경

  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);

  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

  // 버튼 생성 (세로 정렬)
  lv_obj_t *btn_up = lv_btn_create(lv_scr_act());
  lv_obj_align(btn_up, LV_ALIGN_CENTER, 0, -60);
  lv_obj_add_event_cb(btn_up, btn_event_cb, LV_EVENT_CLICKED, NULL);
  lv_obj_t *label_up = lv_label_create(btn_up);
  lv_label_set_text(label_up, "up");

  lv_obj_t *btn_stop = lv_btn_create(lv_scr_act());
  lv_obj_align(btn_stop, LV_ALIGN_CENTER, 0, 0);
  lv_obj_add_event_cb(btn_stop, btn_event_cb, LV_EVENT_CLICKED, NULL);
  lv_obj_t *label_stop = lv_label_create(btn_stop);
  lv_label_set_text(label_stop, "stop");

  lv_obj_t *btn_down = lv_btn_create(lv_scr_act());
  lv_obj_align(btn_down, LV_ALIGN_CENTER, 0, 60);
  lv_obj_add_event_cb(btn_down, btn_event_cb, LV_EVENT_CLICKED, NULL);
  lv_obj_t *label_down = lv_label_create(btn_down);
  lv_label_set_text(label_down, "down");
}

void loop() {
  lv_timer_handler();
  delay(5);
}

📌 4.📘 LVGL 한글 폰트 적용 가이드

📌 📺 시연 영상 (IoT PLC HMI, 한글 띄우기)

한글 출력 예제 프로그램

💻 아두이노 예제 - 한글문자 출력
#include <lvgl.h>
#include <TFT_eSPI.h>
#include "NotoSansKR_20.h"  // 생성한 한글 포함 폰트

static const uint16_t screenWidth  = 480;
static const uint16_t screenHeight = 320;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];

TFT_eSPI tft = TFT_eSPI();

void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
  uint32_t w = area->x2 - area->x1 + 1;
  uint32_t h = area->y2 - area->y1 + 1;

  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp);
}

void setup() {
  Serial.begin(115200);
  lv_init();
  tft.begin();
  tft.setRotation(3);

  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);

  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

  lv_obj_t* label = lv_label_create(lv_scr_act());
  lv_obj_set_style_text_font(label, &NotoSansKR_20, LV_PART_MAIN);  // 폰트 직접 지정
  lv_label_set_text(label, "Hello 안녕");  // 테스트
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

}

void loop() {
  lv_timer_handler();
  delay(5);
}

1. Noto Sans KR 폰트 다운로드

LVGL에서 한글 UI를 만들기 위해서는 한글을 지원하는 폰트를 준비해야 합니다.

  • VGL에서 사용할 한글 폰트는 Google Fonts – Noto Sans KR 에서 다운로드할 수 있습니다.
  • "Get font" 버튼을 눌러 전체 폰트 패밀리를 다운로드합니다.
  • 압축을 해제하면 여러 굵기(Thin, Light, Regular, Medium, Bold 등)의 .ttf 파일이 들어있습니다.
    예) NotoSansKR-Regular.ttf
Google Fonts

2. LVGL 폰트 컨버터 사용하기

  • LVGL은 .ttf 폰트를 그대로 사용할 수 없으므로, LVGL 전용 C 소스 파일로 변환해야 합니다.

  • LVGL Font Converter 웹사이트에 접속합니다.

  • 다음과 같이 설정합니다:

Name : 사용할 이름 입력 → NotoSansKR_20 생성
Font size: 20 입력
Bpp: 4 (일반적으로 4bpp 권장)
Output format: C array 선택
Browse: NotoSansKR-Regular.ttf 선택
Range: 빈칸
Symbols: 사용할 글짜들 입력
Submit 버튼을 누르면 NotoSansKR_20.c 파일이 생성됩니다.

폰트다운로드 사이트 : https://fonts.google.com/noto/specimen/Noto+Sans+KR

Google Fonts

Symbols

아이티알 제어판 설정 사용자메뉴얼 온도 습도 와이파이 이름 비밀번호 이메일 사용법 연결과 블루투스 펌웨어설치 다음 이전 조도 주소  기기  로그인 타이머 입력 트리거 센서 트리거 홈 한글폰트 설치 모터제어 크라우드 입력 동영상시청 다음 정보를 입력하세요 안드로이드 어플 제품사용설명서 보기 닫기 스마트폰 노트북 다운로드가 완료되면 자동 리셋 됩니다 보드에 업데이트 명령 전송 중 재부팅합니다.실패. 다시해 ° 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefgh기ijklmnopqrstuvwxyz ~!@#$%^&*()_+-=[]{}|;':",./<>? 
  • 생성된 "NotoSansKR_20.c" 는 소스코드와 같은 디렉토리에 위치 합니다.
  • chatgpt 에 의뢰하면 NotoSansKR_20.h 파일을 작성해 줍니다.
💻 C code - NotoSansKR_20.h
#ifndef NOTOSANSKR_20_H
#define NOTOSANSKR_20_H

#include "lvgl.h"

#ifdef __cplusplus
extern "C" {
#endif

extern const lv_font_t NotoSansKR_20;

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif
  • NotoSansKR_20.c 파일 중 다음 두곳을 수정 합니다. 두 곳을 찿아서 아래와 같이 수정 하세요
/*
#ifdef __has_include
    #if __has_include("lvgl.h")
        #ifndef LV_LVGL_H_INCLUDE_SIMPLE
            #define LV_LVGL_H_INCLUDE_SIMPLE
        #endif
    #endif
#endif

#ifdef LV_LVGL_H_INCLUDE_SIMPLE
    #include "lvgl.h"
#else
    #include "lvgl/lvgl.h"
#endif
*/
#include "lvgl.h"
    //.static_bitmap = 0,
    .dsc = &font_dsc,          /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */

디렉토리에 생성한 파일입니다.


버튼을 두개 만들어줘 라벨 "1번켜" 버튼을 누르면 
{ "c": "so", "m": "D4:8C:49:50:46:F4", "n": 1, "v": 1 }
을 시리얼 통신으로 보내고 "1번꺼" 버튼을 누르면
{ "c": "so", "m": "D4:8C:49:50:46:F4", "n": 1, "v": 0 } 을 보내줘

✅ 12. 아마존 크라우드 AWS IoT 서버 (React)

react로 구축한 서버는 제 github "React"에 정리해 놓았습니다.

AWS Instal EC2 Ubuntu Server 22.04 AWS IOT Core Mqtt
AWS IOT Core Mqtt AWS IOT Core Mqtt
AWS IoT server AWS IoT 보안 server

mongoDB 설치순서

✅ 13. PC IoT 서버 (React)

** 앞으로 다른 바쁜 작업이 끝나고 나면 정리해서 올리겠습니다.

  • PC에서 React 와 mongoDB를 설치하여 인터넷 상에서 제어한다.
  • 어플에서 IoT-PLC를 와이파이에 접속만 시키면 데이터베이스에 자동 저장되고 제어판넬이 자동으로 생성되어서 모니터링/제어를 할 수 있다.
mongodb, compass 윈도우용 설치하기 mongoDB에 데이터가 자동으로 저장 된 모습
-----
💻 C code 예제
// ✅ 여기에 C 코드 작성

📺 콘텐츠 / 강의 구조 ▶️ 강의 시작 📺 유튜브 📌 개요 📝 설명 📋 요약 / 정리 🧩 구성

🎯 학습 / 교육 🎯 학습 목표 📚 학습 자료 🧠 이해 / 사고 ✏️ 연습 🧪 실험 🧑‍🎓 학생 과제

⚙️ 개발 / 준비 ⚙️ 개발 환경 🛠️ 도구 📦 라이브러리 🔧 설정 🧰 준비물

💻 소프트웨어 / 프로그래밍 💻 소프트웨어 🧑‍💻 코딩 📟 임베디드 🖥️ PC 📱 모바일

🚀 실행 / 동작 🚀 실행 ▶️ 시작 🟢 RUN / 동작중 ⏸️ 일시정지 ⏹️ 정지

🔍 테스트 / 결과 🔍 결과 확인 📈 분석 📊 데이터 ✅ 성공 ❌ 실패 ⚠️ 주의

🌐 통신 / 네트워크 / IoT 🌐 네트워크 📡 통신 🔗 연결 ☁️ 클라우드 📶 WiFi

🤖 AI / 로봇 / 자동화 🤖 로봇 🧠 AI ⚡ 자동화 🦾 액추에이터 👁️ 센서

💡 팁 / 확장 💡 팁 ✨ 확장 🔁 응용 🧠 아이디어 📌 참고

🔗 참고 / 문서 🔗 링크 📄 문서 📑 매뉴얼 📘 가이드

🛠️ 문제 해결 / 트러블슈팅 🛠️ 문제 해결 🐞 버그 🔎 디버그 🧯 오류 대응

Releases

No releases published

Packages

 
 
 

Contributors