도입
이번 포스팅은 Nextion LCD에서 여러개의 페이지를 만들고 선택에 따라서 각 페이지별로 독립된 기능이 실행되도록 아두이노와 연계하는 방법입니다. 그래서 Nextion에서 선택하는 메뉴에 따라 아두이노와 Nextion이 상호적으로 작동을하도록 해서 여러가지 기능을 할 수 있는 복합 디바이스를 만드는 그런 방법 되겠습니다.
말은 거창했지만 간단히 얘기해서, 이전에 소개했던 아래 2개의 포스팅을 하나로 합쳐보는 시간이 되겠습니다.
첫번째 기능은 Nextion에서 동작하는 타이머 기능입니다. Nextion 단독으로만 구현된 기능이며 시간을 설정하면 그 시간동안 남은 시간을 시각적으로 보여주고 간단한 기능을 수행합니다.
두번째는 아두이노와 PMS7003 센서를 이용한 미세먼지를 모니터링 하는 기능입니다. PMS7003 미세먼지 센서를 아두이노로 컨트롤해서 미세먼지 농도를 측정하고 측정된 값을 Nextion으로 표시해 주는 기능을 수행합니다.
그래서 이 두가지를 하나의 디바이스에서 수행할 수 있도록 통합 메뉴창을 만들고 그 메뉴에서 타이머 혹은 미세먼지 센서 두가지 중 하나를 선택하면 선택한 기능이 동작하도록 구성하고자 합니다.
준비물
기존 결과물을 재활용하는 내용이기 때문에 준비물은 생략하겠습니다. 이전 포스팅 참고해 주세요.
Hardware
하드웨어 구성은 기존 "아두이노 휴대용 미세먼지 측정기"와 동일합니다. 단지 소프트웨어적인 수정만 있기 때문에 역시 생략하겠습니다. 이전 포스팅 참고해 주세요.
Software
화면 구성 및 기능 설명
화면 및 메뉴간 이동에 대한 구성은 다음과 같습니다.
장치가 켜지면 첫화면으로 2가지 기능을 선택할 수 있는 메뉴페이지가 나타납니다. 위 그림에서 상단에 있는 화면입니다.
#1 미세먼지 측정기 기능을 누르면 화면은 미세먼지를 측정하는 화면으로 전환이 됩니다. 그리고 아두이노로 이 버튼이 눌려졌음의 신호를 보내고 아두이노는 미세먼지를 측정하는 로직을 수행합니다.
#2 그리고 좌측의 타이틀 버튼을 누르면 다시 첫번째 메뉴선택 화면으로 이동을하고 아두이노에게도 통보를 합니다. 그러면 아두이노는 미세먼지 측정 센서를 대기상태로 전환시킵니다.
#3 Time Timer 버튼을 누르면 화면은 타이머 화면으로 이동합니다. 이때는 아두이노가 개입할 일이 없기 때문에 아두이노는 계속 대기 상태입니다.
#4 타이틀 버튼은 다시 메인 페이지로 돌아갈 수 있도록 하였습니다.
이렇게 메뉴 페이지를 하나 더 추가해서 화면을 왔다갔다 하면서 2가지 기능을 사용하도록 구성해 봤습니다. 자 그럼 이제 본격적으로 수정을 해 보겠습니다.
Nextion
미세먼지 센서와 타이머 중에서 Nextion에서 작업량은 타이머가 훨씬 많았기 때문에 타이머를 구현한 기존 MHI파일을 기본으로 수정작업을 합니다.
먼저 첫화면이 될 메뉴 페이지의 이미지를 하나 만들고 기존에 만들어 둔 미세먼지 측정기 화면이미지를 HMI에 추가를 합니다.
그리고 다음과 같이 Nextion의 페이지 구성을 3개로 수정한 후 메인 페이지와 미세먼지 측정 페이지를 새로 추가한 이미지로 배경을 설정해 줍니다.
그리고 메인페이지에 다음과 같이 2개의 버튼을 구성해 주고 각 버튼에 링크를 걸어줄 페이지를 지정해 줍니다.
예를들어 타이머의 경우 다음과 같이 "Touch Release Event"에 "page [페이지명]"의 명령어를 추가합니다. 해당 버튼을 눌렀다가 손을 때는 동작이 발생하면 [페이지명]으로 지정된 페이지로 이동을 하라는 명령입니다. 반대로, 미세먼지 측정 화면이나 타이머 화면에서는 메인페이지로 돌아오는 버튼을 만들고 "page main"이라는 명령어를 동일하게 넣어주면 되겠죠.
이제 Nextion HMI 업데이트가 끝났습니다. 컴파일을 해서 새로운 UI를 Nextion에 업로드 해 줍니다.
Arduino
아두이노 코드는 추가적으로 최적화를 시켜야 할 부분들이 많이 남아 있으며 기능이 구현됨을 테스트하는데 목적이 있음을 참고해 주세요. 2개의 소프트웨어 시리얼을 사용하면서 명령어가 씹히는 현상을 방지한다던가, 타이머를 사용하고 있을 때 아두이노를 대기모드로 들어가게해서 베터리를 아낀다던가 등 추가로 해볼것들이 많이 남아 있습니다.
아래 코드에서 가장 중요한 부분만 언급을 하자면 Nextion에서 발생하는 이벤트를 아두이노에서 받아주기위한 콜백함수를 등록하고 호출하는 겁니다.
50~95행: 콜백함수를 정의하는 부분입니다. Nextion에서 이벤트가 발생했을 때 아두이노에서 해야될 일을 정의하였습니다.
155~160행: "attachPop"이라는 함수를 이용해서 이벤트가 발생하는 Nextion 개체와 콜백함수를 연결해주는 분입니다.
/*
Name: AEWT.ino
Created: 2019-03-07 오전 11:26:55
Author: EveryX
미세먼지 측정기 겸 타임타이머용 펌웨어 입니다.
아두이노 Nano의 소프트웨어시리얼 2개를 동시사용하여
Nextion디스플레이 및 PMS7003센서와 통신하고 하드웨어시리얼 포트는 디버깅용으로 사용합니다.
*/
#include <AltSoftSerial.h> // 소프트웨어시리얼 사용을 위한 헤더
#include <Nextion.h> // Nextion LCD용 라이브러리
#include <PMS.h> // PMS센서용 라이브러리
#include <string.h> // stoi 함수용
static int menu_selector = 0; // 0: 메뉴선택화면, 1: 미세먼지측정기, 2: 타임타이머
static uint32_t started = 0; // 타이머용 변수 선언 및 초기화
static bool pms_power = 0; // 0:PMS7003센서 sleep, 1: 센서 awake
// PMS7003센서 통신용 소프트웨어시리얼
#define PMS_TX 3 // Tx: D3
#define PMS_RX 4 // Rx: D4
SoftwareSerial SerialForPMS(PMS_RX, PMS_TX); // 소프트웨어시리얼포트 지정
PMS pms(SerialForPMS); // PMS센서통신포트로 지정
PMS::DATA data;
// Nextion 통신용 소프트웨어시리얼
#define NEX_TX 8 // Tx: D7
#define NEX_RX 7 // Rx: D6
SoftwareSerial SerialForNex(NEX_RX, NEX_TX); // 스프트웨어시리얼포트 지정
// Nextion 화면개체 선언 - (page id = 0, component id = 1, component name = "b0")
/// page0 - main
NexButton bMicrobe = NexButton(0, 1, "bMicrobe");
NexButton bTimetimer = NexButton(0, 2, "bTimetimer");
/// page1 - microbe
NexButton b0 = NexButton(1, 5, "b0");
NexText tPM1_0 = NexText(1, 2, "tPM1_0");
NexText tPM2_5 = NexText(1, 3, "tPM2_5");
NexText tPM10_0 = NexText(1, 4, "tPM10_0");
NexWaveform sPMS = NexWaveform(1, 1, "sPMS");
NexButton bTest = NexButton(1, 6, "bTest");
/// page2 - timetimer
NexButton b1 = NexButton(2, 1, "b1");
NexTouch* nex_event_list[] = {
&bMicrobe, &bTimetimer, &b0, &b1, &bTest, NULL
};
// bMicrobe 콜벡함수
void bMicrobe_Callback(void* ptr) {
Serial.println("Callback bMicrobe");
menu_selector = 1;
pms_power = 1;
SerialForPMS.listen();
delay(200);
pms.wakeUp();
}
// bTimetimer 콜벡함수
void bTimetimer_Callback(void* ptr) {
Serial.println("Callback bTimetimer");
menu_selector = 2;
pms_power = 0;
SerialForPMS.listen();
delay(1000);
pms.sleep();
}
// b0 콜벡함수
void b0_Callback(void* ptr) {
Serial.println("Callback Goto Menu");
menu_selector = 0;
pms_power = 0;
SerialForPMS.listen();
delay(1000);
pms.sleep();
}
// b1 콜벡함수
void b1_Callback(void* ptr) {
Serial.println("Callback Goto Menu");
menu_selector = 0;
pms_power = 0;
SerialForPMS.listen();
delay(500);
pms.sleep();
}
// bTest 콜벡함수
void bTest_Callback(void* ptr) {
Serial.println("Test: pms sensor power up!!");
SerialForPMS.listen();
delay(200);
pms.wakeUp();
}
// 미세먼지 농도를 측정하고 Nextion에 표시하기위한 함수
void UpdatePMS() {
Serial.println("update PMS");
// 변수 선언
uint16_t number[3] = { 0 };
char temp[10] = { 0 };
SerialForPMS.listen(); // PMS센서가 연결된 시리얼포트 활성화
while (SerialForPMS.available()) { SerialForPMS.read(); } // 버퍼에 남아있을 어떤 데이터를 미리 삭제
pms.requestRead(); // PMS센서에 측정요청신호 보내기
// 미세먼지 측정이 되었을 경우 측정값을 Nextion에 표시
if (pms.readUntil(data)) // 측정값이 저장된 버퍼 읽어오기
{
Serial.println("reding PMS1");
number[0] = data.PM_AE_UG_1_0; // PM 1.0 (ug/m3)값 읽어서 변수에 저장
utoa(number[0], temp, 10); // 정수를 텍스트로 변환
Serial.println(temp);
tPM1_0.setText(temp); // Nextion에 표시
number[1] = data.PM_AE_UG_2_5; // PM 2.5 (ug/m3)값 읽어서 변수에 저장
utoa(number[1], temp, 10);
tPM2_5.setText(temp);
number[2] = data.PM_AE_UG_10_0; // PM 10.0 (ug/m3)값 읽어서 변수에 저장
utoa(number[2], temp, 10);
tPM10_0.setText(temp);
Serial.println("reding PMS2");
for (int i = 0; i < 3; i++) { // waveform 0~2ch 차례대로 data전달
sPMS.addValue(i, (int)(number[i] / 1)); // 그래프 범위: 0~150ug/m3으로 스케일링
}
Serial.println("reding PMS3");
}
SerialForNex.listen(); // Nextion이 연결된 시리얼포트 활성화
}
void setup() {
menu_selector = 0; // 0: 메뉴선택화면
pms_power = 0; // pms센서 대기상태
// PMS센서 초기화
SerialForPMS.begin(PMS::BAUD_RATE); // PMS센서 소프트웨어시리얼 열기
if (SerialForPMS.isListening()) {
Serial.println("SerialForPMS is listening!");
}
else {
Serial.println("SerialForPMS is not listening!");
}
pms.passiveMode(); // 센서측정주기와 아두이노 수신주기가 동기되지 않을 수 있기 때문에 수동모드 사용
delay(500);
pms.sleep();
// 시리얼 포트 열기
Serial.begin(9600); // 디버깅용
// Nextion 초기화
nexInit(); // 경우에따라 NexConfig.h의 설정값 수정필요 (Nextion용 BaudRate는 9600)
// Nextion 이벤트를 콜백함수와 연결
bMicrobe.attachPop(bMicrobe_Callback, &bMicrobe);
bTimetimer.attachPop(bTimetimer_Callback, &bTimetimer);
b0.attachPop(b0_Callback, &b0);
b1.attachPop(b1_Callback, &b1);
bTest.attachPop(bTest_Callback, &bTest);
}
void loop() {
SerialForNex.listen();
nexLoop(nex_event_list); // Nextion에서 발생하는 이벤트를 전달
switch (menu_selector) {
case 1: // 미세먼지 측정기 모드
if (millis() - started >= 2000) // 2초간격으로 측정
{
started = millis(); // 타이머기준 리셋
Serial.println("This is Microbe mode");
UpdatePMS(); // 미세먼지관련 함수 호출
}
break;
case 2: // 타임타이머 모드
if (millis() - started >= 2000) // 2초간격으로 측정
{
started = millis(); // 타이머기준 리셋
Serial.println("This is Timetimer mode");
pms.sleep();
}
break;
default:
if (millis() - started >= 2000) // 2초간격으로 측정
{
started = millis(); // 타이머기준 리셋
Serial.println("Stay in Mainpage");
Serial.flush();
SerialForPMS.listen();
delay(1000);
pms.sleep();
}
break;
}
}
결과
끝!
'Hardware > MCU(Arduino,ESP8266)' 카테고리의 다른 글
와이파이 모듈 ESP8266-01의 모든것 (13) | 2020.06.12 |
---|---|
아두이노 + 블루투스 모듈 (HC05, HC06) 기본 사용법 (6) | 2020.05.22 |
아두이노 + 자외선(UV) 센서 모듈 사용법 (0) | 2020.05.02 |
안드로이드 스마트폰용 아두이노 IDE Arduinodroid의 업로드 에러 (0) | 2020.01.31 |
댓글