준비물
- IDE: Arduino, VSCode(PlatformIO, ESP-IDF)
- MCU: Keyestudio ESP32-WROOM-32 Module (AliExpress Store)
- E-PAPER: Waveshare 5.65 7Color 600x448 (AliExpress Store)
- OLED: SSD1306 I2C (AliExpress)
- GNSS: NEO-M8N (AliExpress)
- RTC: DS3231 (AliExpress)
각 모듈을 따로 사용하기 복잡하다면 GNSS 모듈과 OLED가 부착된 LoRa 제품을 추천한다.
https://s.click.aliexpress.com/e/_oBXvsgX
https://s.click.aliexpress.com/e/_om5uAoB
라이브러리
- GxEPD2: ePaper에 그릴 때 사용.
- SSD1306Wire:OLED에 그릴 때 사용.
- TinyGPSPlus: GPS 모듈용.
- Time: RTC, 현재 시각 클래스.
관련 글
ePaper: 🛒 Waveshare EPD 5.65" 7C (600x448), e-Paper(전자종이)
OLED: (Arduino) ESP32 Core WROOM + Adafruit SSD1351 1.5 RGB OLED H/W SPI 4wire 연결 테스트
제작 목표
- GPS로부터 현재 시각을 수신할 것 (완료)
- 매일 0시에 EPD를 갱신할 것 (완료)
GPS 불가시 통신으로 날짜를 수신할 것(완료)- OTA 업데이트 (작업중)
RTC로 백업할 것 (완료)
설명
개발 환경
Arduino IDE로 업로드 하려면 환경설정에서 ✅외부 에디터 사용에 체크하면 된다.
(VSCode에서 업로드 하는 것이 훨씬 빠르지만 시리얼 모니터 자동 동기화는 되지 않는다.)
코드가 너무 길어서 여러 파일로 분할했다.
VS2019의 F2(리네이밍) 기능이 VS Code에서는 같은 객체가 아니더라도 문자열만 같으면 전부 바뀌는 문제가 있어서 불편하다.
회로도
※ 주의: IO34 연결선을 IO33에 연결해야 한다. IO34는 기본값이 입력용이므로 ESP32 부팅시 오류가 발생한다.
코드 부분
GPS 모듈은 두번째 시리얼 포트에 연결했다.
HardwareSerial SerialGPS(1);
void setup()
{
SerialGPS.begin(9600, SERIAL_8N1, 16, 17);
}
포트 번호는 1로 설정한다.
통신 포트가 아닌 임의 포트에 연결했다면 SoftwareSerial 클래스를 사용한다.
EPD 모듈은 HSPI에 연결했다.
#include <GxEPD2_7C.h>
GxEPD2_7C < GxEPD2_565c, GxEPD2_565c::HEIGHT / 2 > EPD(GxEPD2_565c(/*CS=*/15, /*DC=*/27, /*RST=*/26, /*BUSY=*/25));
void setup()
{
EPD.init(115200); // default 20ms reset pulse, e.g. for bare panels with DESPI-C02
SPI.end();
SPI.begin(14, -1, 13, 15); // map and init HSPI pins SCK(14), MISO(12), MOSI(13), SS(15)
}
데이터 통신용 핀 번호는 인스턴스를 만들 때 설정하고, SPI 번호는 setup() 문에서 설정한다.
사용하지 않는 핀은 -1로 설정한다.
SPI 연결에 문제가 있으면, SPI를 종료하고 다시 시작하면 정상 연결 된다.
처음 부팅 시 EPD를 갱신할 때는 상당히 오래 걸렸지만, 그 다음 EPD 갱신은 30초 정도 걸렸다.
OLED 모듈은 VSPI에 연결했다.
#include "SSD1306Wire.h"
#define I2C_SDA 21
#define I2C_SCL 22
SSD1306Wire display(0x3c, I2C_SDA, I2C_SCL);
- Hardware I2C의 SDA 핀은 21, SCL 핀은 22
RTC 모듈은 DS3231을 사용한다. 왜냐하면 오차율이 1분/년(=5초/월)이기 때문.
LIR2032 버튼 전지를 사용하면 충전도 되기 때문에 추천.
(DS1037/DS1032는 각각 1분/월, 1분/주로 성능이 낮다.)
전원이 항상 연결되어 있거나 GPS 수신이 잘 되는 공간이라면 RTC 모듈은 필요가 없다. 대신, 내장 타이머를 사용하면 시간이 지남에 따라 GPS 모듈의 시간과 어긋난다. 이럴때에는 초 단위로 보정하면 된다.
// 초 보정
void updateRTCSecond() {
static unsigned long lastUpdateMs; // 보정 실행 타이머
if (gps.time.isValid() && gps.time.second() != second() && millis() - lastUpdateMs > 60 * 1000) {
while (SerialGPS.available()) gps.encode(SerialGPS.read());
setTime(hour(), minute(), gps.time.second(), day(), month(), year());
lastUpdateMs = millis();
}
}
1분 마다 교정 실행
GPS 시각으로 내장 타이머 갱신
bool checkGPSTime(unsigned long ms) {
TimeElements gpsdt;
breakTime(latestGPSTimestamp, gpsdt);
if ((gps.date.isValid() && gps.time.isValid())
&& (gpsdt.Year + 1970 != gps.date.year() || gpsdt.Month + 1 != gps.date.month() || gpsdt.Day != gps.date.day())
&& (gps.date.year() > 2000 && gps.date.month() > 0 && gps.date.day() > 0)) {
return true;
} else return false;
}
전자 종이는 갱신 과정에서 깜빡이기 때문에 실시간으로 현재 시각을 갱신하면 안 된다. 달력에서는 날짜가 변경되었을 때에만 갱신하면 된다.
지저분한 소스 코드 공개는 보류...ㅎㅎ