본문 바로가기
Hardware/MCU(Arduino,ESP8266)

아두이노, 미세먼지 센서 + Time Timer 하나로 합치기

by lovey25 2020. 5. 6.
반응형

도입

이번 포스팅은 Nextion LCD에서 여러개의 페이지를 만들고 선택에 따라서 각 페이지별로 독립된 기능이 실행되도록 아두이노와 연계하는 방법입니다. 그래서 Nextion에서 선택하는 메뉴에 따라 아두이노와 Nextion이 상호적으로 작동을하도록 해서 여러가지 기능을 할 수 있는 복합 디바이스를 만드는 그런 방법 되겠습니다.

말은 거창했지만 간단히 얘기해서, 이전에 소개했던 아래 2개의 포스팅을 하나로 합쳐보는 시간이 되겠습니다.

첫번째 기능은 Nextion에서 동작하는 타이머 기능입니다. Nextion 단독으로만 구현된 기능이며 시간을 설정하면 그 시간동안 남은 시간을 시각적으로 보여주고 간단한 기능을 수행합니다.

 

Nextion LCD만으로 Time Timer MOD(타이머) 만들기

이번 포스팅은 아두이노 카테고리에 있지만 아두이노가 필요없는 주제입니다. 만능 LCD인 Nextion display(이하 Nextion)만을 가지고 구글 타이머로 유명한 Timetimer 흉내내기 프로젝트 입니다. 먼저 Time Timer..

kwonkyo.tistory.com

두번째는 아두이노와 PMS7003 센서를 이용한 미세먼지를 모니터링 하는 기능입니다. PMS7003 미세먼지 센서를 아두이노로 컨트롤해서 미세먼지 농도를 측정하고 측정된 값을 Nextion으로 표시해 주는 기능을 수행합니다.

 

아두이노 휴대용 미세먼지 측정기 만들기 완결편 - 아두이노 나노, PMS7003 센서, Nextion LCD, 3D프린팅 패키징까지

2020-03-16 update log: 결선도 수정(아두이노 나노에서 5V외부입력 사용시 5V단자를 사용해야 함, Vin단자는 7V이상 전원 사용) 배경 지금 이 글을 쓰고 있는 2019년 7월 벌써 1주일이 넘게 미세먼지가 나쁨 이..

kwonkyo.tistory.com

그래서 이 두가지를 하나의 디바이스에서 수행할 수 있도록 통합 메뉴창을 만들고 그 메뉴에서 타이머 혹은 미세먼지 센서 두가지 중 하나를 선택하면 선택한 기능이 동작하도록 구성하고자 합니다.

준비물

기존 결과물을 재활용하는 내용이기 때문에 준비물은 생략하겠습니다. 이전 포스팅 참고해 주세요. 

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;
	}
}

결과

 

끝!

반응형

댓글