1. 개요 (Overview)
이 글은 MPS Flammable Gas Sensor 5.0 manual 기준으로 ,UART 인터페이스를 사용하여 아두이노에서 가스 농도, 가스 ID, 환경 데이터(온도, 압력, 습도)를 읽고 처리하는 스케치를 구현한 내용입니다. 사용자 매뉴얼(SM-UM-0010-03)에 명시된 모든 명령어, 상태 코드, 시작 시퀀스, 신뢰 신호, 환경 오류 처리를 완벽히 반영하였습니다. 아날로그 출력은 주석으로만 언급하며, UART 통신에 초점을 맞췄습니다. 구현된 코드는 아두이노 Uno(SoftwareSerial) 및 Mega(Serial1)와 호환됩니다.
2. 요구사항 (Requirements)
- 모든 명령어 지원: 표 6의 12개 UART 명령어(ANSWER, CONC, ID, TEMP, PRES, REL_HUM, ABS_HUM, STATUS, VERSION, SENSOR_INFO, MEAS, RESET) 구현.
- 상태 코드 처리: 표 4의 19개 상태 코드에 대한 오류 메시지와 사용자 조치 제공.
- 측정 모드 및 단위: 연속/단일/정지 모드 및 %LEL ISO/IEC 단위 지원(표 10, 11).
- 시작 시퀀스: 부팅(3초), SENSOR_STARTUP(31초), SENSOR_INITIALIZATION(6 사이클) 준수(그림 6).
- 가스 ID 및 농도: 표 7(농도 범위), 표 8(가스 ID) 준수.
- 신뢰 신호: 사이클 카운트 증가 및 상태 코드 모니터링(섹션 2.1.9).
- 전기기계적 통합: S4/Mini 폼 팩터, 전원 안정성, 방수 고려사항(섹션 3).
- 환경 오류: 온도, 압력, 습도 범위 초과 처리(섹션 5).
- 아날로그 출력: UART 중심이지만 아날로그 출력 정보 주석 포함(섹션 2.2–2.4).



자세하한 내용은 메뉴얼을 참조 바랍니다.MPS Flammable Gas Sensor 5.0 사용자 매뉴얼(SM-UM-0010-03)
3. 구현 세부사항 (Implementation Details)
아래는 MPS 센서와 통신하는 아두이노 코드입니다. 코드는 모듈화되어 있으며, 한글 주석으로 표기된 매뉴얼 섹션을 참조합니다.
주요 기능은 다음과 같습니다:
- UART 설정: 38400 보드, 8비트, 패리티 없음, 1 정지 비트(표 1).
- CRC 계산: CRC-CCITT(0xFFFF 초기값, 섹션 2.1.3).
- 명령 처리: 모든 명령어에 대한 전용 함수와 페이로드 파싱.
- 오류 처리: 상태 코드 출력, 시작 중 가스 감지 시 리셋, 타임아웃 처리.
- 데이터 출력: 시리얼 모니터에 가스 농도(%LEL), 가스 ID, 환경 데이터 표시.
아두이노 코드(Arduino Code)
#include <SoftwareSerial.h>
// 센서 TX와 RX 핀 정의 (Define pins for sensor TX and RX, Section 3.1, Figure 12)
#define RX_PIN 10 // 센서 TX에 연결 (Connect to sensor TX)
#define TX_PIN 11 // 센서 RX에 연결 (Connect to sensor RX)
SoftwareSerial mpsSerial(RX_PIN, TX_PIN); // RX, TX (소프트웨어 시리얼 설정, SoftwareSerial setup)
// CRC-CCITT 체크섬 계산을 위한 테이블 (CRC table for CCITT-16 checksum, Section 2.1.3)
static const uint16_t crc_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
// 상태 코드 정의 (Status code definitions, Table 4, Section 2.1.2)
struct StatusCode {
uint8_t code;
const char* message;
const char* explanation;
const char* userAction;
};
const StatusCode statusCodes[] = {
{0x00, "OK", "MPS가 정상적으로 작동 중", "조치 필요 없음 (MPS is operating normally, None)"},
{0x01, "CRC_FAILED", "전송 데이터 체크섬 실패", "체크섬 계산 확인 (Verify checksum calculation, Section 2.1.3)"},
{0x02, "BAD_PARAMETER", "잘못된 매개변수 지정", "명령 매개변수 확인 (Verify command parameters)"},
{0x03, "EXECUTION_FAILED", "명령 실행 실패", "지원팀에 문의 (Contact support)"},
{0x04, "NO_MEMORY", "작동에 메모리 부족", "지원팀에 문의 (Contact support)"},
{0x05, "UNKNOWN_COMMAND", "알 수 없는 명령 ID", "명령 ID 확인 (Verify Command ID, Section 2.1.5)"},
{0x07, "INCOMPLETE_COMMAND", "불완전하거나 잘린 명령", "패킷 전체 전송 확인 (Verify entire packet sent)"},
{0x20, "HW_ERR_AO", "아날로그 출력 오류", "지원팀에 문의 (Contact support)"},
{0x21, "HW_ERR_VDD", "내부 전압 조절기 범위 초과", "3.0-5.0 ±5% VDC 공급 후 재부팅 (Supply 3.0-5.0 ±5% VDC and power cycle)"},
{0x22, "HW_ERR_VREF", "내부 전압 참조 범위 초과", "지원팀에 문의 (Contact support)"},
{0x23, "HW_ENV_XCD_RANGE", "환경(온도, 압력, 습도) 범위 초과", "지정된 작동 범위로 복귀 (Return sensor to specified range, Section 5)"},
{0x24, "HW_ENV_SNSR_MALFUNCTION", "환경 센서 오작동", "센서 손상, 보증 적용 불가 (Sensor damaged, not covered by warranty)"},
{0x25, "HW_ERR_MCU", "마이크로컨트롤러 오류", "지원팀에 문의 (Contact support)"},
{0x26, "SENSOR_INITIALIZATION", "센서 초기화 모드(6 사이클)", "약 12초 대기 (Wait ~12 seconds)"},
{0x27, "SENSOR_STARTUP", "센서 시작 모드", "이 기간 동안 가스 적용 금지 (Do not apply gas during this period)"},
{0x30, "SENSOR_NEGATIVE", "센서 출력 <-15%LEL", "센서가 0으로 복귀할 때까지 대기 (Wait for sensor to return to zero)"},
{0x33, "GAS_DETECTED_DURING_STARTUP", "시작 중 가스 감지", "높은 가스 경보 보고(>50%LEL), 깨끗한 공기에서 재시작 (Report high gas alarm, restart in clean air)"},
{0x34, "SLOW_GAS_ACCUMULATION_DETECTED", "가스 느린 축적 감지", "높은 가스 경보 보고(>50%LEL), 깨끗한 공기에서 재시작 (Report high gas alarm, restart in clean air)"},
{0x35, "BREATH_OR_HUMIDITY_SURGE", "인간 호흡 또는 습도 급등 감지", "센서에 숨을 불지 않음 (Avoid breathing on sensor)"},
{0x36, "WATCHDOG_MCU_RESET", "워치독에 의한 센서 리셋", "3.0-5.0 ±5% VDC 공급 후 재부팅 (Supply 3.0-5.0 ±5% VDC, power cycle)"},
{0x37, "HW_ERR_WATCHDOG", "워치독 오작동", "지원팀에 문의 (Contact support)"},
{0x38, "HW_ERR_DAC_ADC_XCD_RANGE", "DAC/ADC 범위 초과", "정상 상태로 복귀 대기, 지속 시 지원팀 문의 (Wait for normal status, contact support if persists)"}
};
// 가스 ID 정의 (Gas ID definitions, Table 8, Section 2.1.6)
struct GasID {
uint32_t id;
const char* description;
};
const GasID gasIDs[] = {
{0, "가스 없음 (No Gas)"},
{1, "수소 (Hydrogen)"},
{2, "수소 혼합물 (Hydrogen Mixture)"},
{3, "메탄 (Methane)"},
{4, "경량 가스 (Light Gas)"},
{5, "중량 가스 (Medium Gas)"},
{6, "중량 가스 (Heavy Gas)"},
{253, "알 수 없는 가스 (Unknown Gas)"},
{254, "범위 이하 (Under Range)"},
{255, "범위 초과 (Over Range)"}
};
// CRC-CCITT 체크섬 계산 (Calculate CRC-CCITT checksum, Section 2.1.3)
uint16_t crc_generate(uint8_t *buffer, size_t length) {
uint16_t crc = 0xFFFF; // 초기값 0xFFFF (Initial value 0xFFFF)
for (size_t i = 0; i < length; i++) {
crc = (crc << 8) ^ crc_table[(crc >> 8) ^ buffer[i]];
}
return crc;
}
// MPS 센서로 명령 전송 (Send command to MPS sensor, Section 2.1.2)
void sendCommand(uint8_t cmdID, uint8_t *payload, uint16_t payloadLength) {
uint8_t packet[8 + payloadLength];
// 요청 패킷 구성 (Build request packet, Figure 3)
packet[0] = cmdID; // CmdID (LSB)
packet[1] = 0x00; // CmdID (MSB)
packet[2] = payloadLength & 0xFF; // 페이로드 길이 LSB (Payload Length LSB)
packet[3] = (payloadLength >> 8) & 0xFF; // 페이로드 길이 MSB (Payload Length MSB)
packet[4] = 0x00; // 예약됨 (Reserved)
packet[5] = 0x00; // 예약됨 (Reserved)
packet[6] = 0x00; // 체크섬 자리표시 (Checksum placeholder LSB)
packet[7] = 0x00; // 체크섬 자리표시 (Checksum placeholder MSB)
// 페이로드 복사 (Copy payload)
for (uint16_t i = 0; i < payloadLength; i++) {
packet[8 + i] = payload[i];
}
// 체크섬 계산 (Calculate checksum)
uint16_t checksum = crc_generate(packet, 8 + payloadLength);
packet[6] = checksum & 0xFF; // 체크섬 LSB (Checksum LSB)
packet[7] = (checksum >> 8) & 0xFF; // 체크섬 MSB (Checksum MSB)
// 패킷 전송 (Send packet)
mpsSerial.write(packet, 8 + payloadLength);
}
// 센서 응답 읽기 및 파싱 (Read and parse sensor response, Section 2.1.2)
bool readResponse(uint8_t expectedCmdID, uint8_t *buffer, uint16_t expectedLength, uint8_t &status) {
uint8_t header[6];
unsigned long startTime = millis();
// 헤더 기다림 (CmdID: 1, Status: 1, Length: 2, Checksum: 2) (Wait for header)
while (mpsSerial.available() < 6 && millis() - startTime < 1000) {}
if (mpsSerial.available() < 6) {
Serial.println("오류: 응답 타임아웃 또는 불완전한 헤더 (Error: Response timeout or incomplete header)");
return false;
}
// 헤더 읽기 (Read header)
for (int i = 0; i < 6; i++) {
header[i] = mpsSerial.read();
}
// CmdID 검증 (Verify CmdID)
if (header[0] != expectedCmdID) {
Serial.print("오류: 예상치 못한 CmdID: 0x (Error: Unexpected CmdID: 0x)");
Serial.println(header[0], HEX);
return false;
}
// 상태 확인 (Check status)
status = header[1];
if (status != 0x00) {
for (int i = 0; i < sizeof(statusCodes) / sizeof(statusCodes[0]); i++) {
if (statusCodes[i].code == status) {
Serial.print("상태: "); Serial.print(statusCodes[i].message);
Serial.print(" - "); Serial.print(statusCodes[i].explanation);
Serial.print(" - 조치: "); Serial.println(statusCodes[i].userAction);
break;
}
}
return false;
}
// 페이로드 길이 확인 (Get payload length)
uint16_t length = (header[3] << 8) | header[2];
if (length != expectedLength) {
Serial.print("오류: 예상치 못한 페이로드 길이: (Error: Unexpected payload length: )");
Serial.println(length);
return false;
}
// 페이로드 읽기 (Read payload)
startTime = millis();
while (mpsSerial.available() < length && millis() - startTime < 1000) {}
if (mpsSerial.available() < length) {
Serial.println("오류: 불완전한 페이로드 (Error: Incomplete payload)");
return false;
}
for (int i = 0; i < length; i++) {
buffer[i] = mpsSerial.read();
}
// 체크섬 검증 (Verify checksum)
uint8_t packet[6 + length];
for (int i = 0; i < 6; i++) {
packet[i] = header[i];
}
for (int i = 0; i < length; i++) {
packet[6 + i] = buffer[i];
}
uint16_t calcChecksum = crc_generate(packet, 4 + length); // 체크섬 바이트 제외 (Exclude checksum bytes)
uint16_t recvChecksum = (header[5] << 8) | header[4];
if (calcChecksum != recvChecksum) {
Serial.println("오류: 체크섬 불일치 (Error: Checksum mismatch)");
return false;
}
return true;
}
// 측정 시작 (Start measurement, Command 0x61, Section 2.1.6)
void startMeasurement(uint8_t mode, uint8_t unit) {
uint8_t payload = (unit << 4) | mode; // 단위와 모드 결합 (Combine unit and mode, Table 10)
sendCommand(0x61, &payload, 1);
uint8_t buffer[1];
uint8_t status;
if (readResponse(0x61, buffer, 0, status)) {
Serial.println("측정 시작 성공 (Measurement started successfully)");
} else {
Serial.println("측정 시작 실패 (Failed to start measurement)");
}
}
// 측정 정지 (Stop measurement, Command 0x61, mode 0x3)
void stopMeasurement() {
uint8_t payload = 0x03; // 정지 모드, %LEL ISO (Stop mode, %LEL ISO)
sendCommand(0x61, &payload, 1);
uint8_t buffer[1];
uint8_t status;
if (readResponse(0x61, buffer, 0, status)) {
Serial.println("측정 정지 성공 (Measurement stopped successfully)");
} else {
Serial.println("측정 정지 실패 (Failed to stop measurement)");
}
}
// 센서 리셋 (Reset sensor, Command 0x62, Section 2.1.6)
void resetSensor() {
sendCommand(0x62, nullptr, 0);
uint8_t buffer[1];
uint8_t status;
if (readResponse(0x62, buffer, 0, status)) {
Serial.println("센서 리셋 성공 (Sensor reset successfully)");
} else {
Serial.println("센서 리셋 실패 (Failed to reset sensor)");
}
}
// 전체 응답 읽기 (Read complete answer, Command 0x01, Section 2.1.6)
bool readAnswer(uint32_t &cycleCount, float &concentration, uint32_t &gasID, float &temperature, float &pressure, float &relHumidity, float &absHumidity) {
uint8_t buffer[28]; // 예상 페이로드 길이 (Expected payload length, Table 6)
uint8_t status;
sendCommand(0x01, nullptr, 0);
if (readResponse(0x01, buffer, 28, status)) {
// 페이로드 파싱 (Parse payload, Section 2.1.6)
cycleCount = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
uint32_t concRaw = (buffer[7] << 24) | (buffer[6] << 16) | (buffer[5] << 8) | buffer[4];
memcpy(&concentration, &concRaw, 4);
gasID = (buffer[11] << 24) | (buffer[10] << 16) | (buffer[9] << 8) | buffer[8];
uint32_t tempRaw = (buffer[15] << 24) | (buffer[14] << 16) | (buffer[13] << 8) | buffer[12];
memcpy(&temperature, &tempRaw, 4);
uint32_t pressRaw = (buffer[19] << 24) | (buffer[18] << 16) | (buffer[17] << 8) | buffer[16];
memcpy(&pressure, &pressRaw, 4);
uint32_t relHumRaw = (buffer[23] << 24) | (buffer[22] << 16) | (buffer[21] << 8) | buffer[20];
memcpy(&relHumidity, &relHumRaw, 4);
uint32_t absHumRaw = (buffer[27] << 24) | (buffer[26] << 16) | (buffer[25] << 8) | buffer[24];
memcpy(&absHumidity, &absHumRaw, 4);
return true;
}
return false;
}
// 가스 농도 읽기 (Read concentration, Command 0x03, Section 2.1.6)
float readConcentration() {
uint8_t buffer[4];
uint8_t status;
sendCommand(0x03, nullptr, 0);
if (readResponse(0x03, buffer, 4, status)) {
uint32_t concRaw = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
float concentration;
memcpy(&concentration, &concRaw, 4);
return concentration;
}
return -100.0; // 오류 값 (Error value)
}
// 가스 ID 읽기 (Read gas ID, Command 0x04, Section 2.1.6)
uint32_t readGasID() {
uint8_t buffer[4];
uint8_t status;
sendCommand(0x04, nullptr, 0);
if (readResponse(0x04, buffer, 4, status)) {
return (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
}
return 0xFFFFFFFF; // 오류 값 (Error value)
}
// 온도 읽기 (Read temperature, Command 0x21, Section 2.1.6)
float readTemperature() {
uint8_t buffer[4];
uint8_t status;
sendCommand(0x21, nullptr, 0);
if (readResponse(0x21, buffer, 4, status)) {
uint32_t tempRaw = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
float temperature;
memcpy(&temperature, &tempRaw, 4);
return temperature;
}
return -1000.0; // 오류 값 (Error value)
}
// 압력 읽기 (Read pressure, Command 0x22, Section 2.1.6)
float readPressure() {
uint8_t buffer[4];
uint8_t status;
sendCommand(0x22, nullptr, 0);
if (readResponse(0x22, buffer, 4, status)) {
uint32_t pressRaw = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
float pressure;
memcpy(&pressure, &pressRaw, 4);
return pressure;
}
return -1000.0; // 오류 값 (Error value)
}
// 상대 습도 읽기 (Read relative humidity, Command 0x23, Section 2.1.6)
float readRelativeHumidity() {
uint8_t buffer[4];
uint8_t status;
sendCommand(0x23, nullptr, 0);
if (readResponse(0x23, buffer, 4, status)) {
uint32_t relHumRaw = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
float relHumidity;
memcpy(&relHumidity, &relHumRaw, 4);
return relHumidity;
}
return -1000.0; // 오류 값 (Error value)
}
// 절대 습도 읽기 (Read absolute humidity, Command 0x24, Section 2.1.6)
float readAbsoluteHumidity() {
uint8_t buffer[4];
uint8_t status;
sendCommand(0x24, nullptr, 0);
if (readResponse(0x24, buffer, 4, status)) {
uint32_t absHumRaw = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
float absHumidity;
memcpy(&absHumidity, &absHumRaw, 4);
return absHumidity;
}
return -1000.0; // 오류 값 (Error value)
}
// 상태 읽기 (Read status, Command 0x41, Section 2.1.6)
uint8_t readStatus() {
uint8_t buffer[1];
uint8_t status;
sendCommand(0x41, nullptr, 0);
if (readResponse(0x41, buffer, 1, status)) {
return buffer[0];
}
return 0xFF; // 오류 값 (Error value)
}
// 버전 정보 읽기 (Read version info, Command 0x42, Section 2.1.6)
bool readVersion(uint8_t &swW, uint8_t &swX, uint8_t &swY, uint8_t &swZ, uint8_t &hwW, uint8_t &hwX, uint8_t &protoW, uint8_t &protoX) {
uint8_t buffer[8];
uint8_t status;
sendCommand(0x42, nullptr, 0);
if (readResponse(0x42, buffer, 8, status)) {
swW = buffer[0];
swX = buffer[1];
swY = buffer[2];
swZ = buffer[3];
hwW = buffer[4];
hwX = buffer[5];
protoW = buffer[6];
protoX = buffer[7];
return true;
}
return false;
}
// 센서 정보 읽기 (Read sensor info, Command 0x43, Section 2.1.6)
bool readSensorInfo(char* serialNum, uint32_t &sensorType, char* sku, char* calDate, char* mfgDate) {
uint8_t buffer[100];
uint8_t status;
sendCommand(0x43, nullptr, 0);
if (readResponse(0x43, buffer, 100, status)) {
// 시리얼 번호 파싱 (32바이트, ASCII) (Parse serial number, 32 bytes, ASCII)
memcpy(serialNum, buffer, 32);
serialNum[32] = '\0';
// 센서 타입 파싱 (32비트 unsigned) (Parse sensor type, 32-bit unsigned)
sensorType = (buffer[35] << 24) | (buffer[34] << 16) | (buffer[33] << 8) | buffer[32];
// SKU 파싱 (32바이트, ASCII) (Parse SKU, 32 bytes, ASCII)
memcpy(sku, buffer + 36, 32);
sku[32] = '\0';
// 캘리브레이션 날짜 파싱 (16바이트, ASCII) (Parse calibration date, 16 bytes, ASCII)
memcpy(calDate, buffer + 68, 16);
calDate[16] = '\0';
// 제조 날짜 파싱 (16바이트, ASCII) (Parse manufacturing date, 16 bytes, ASCII)
memcpy(mfgDate, buffer + 84, 16);
mfgDate[16] = '\0';
return true;
}
return false;
}
void setup() {
// 시리얼 통신 초기화 (Initialize serial communication)
Serial.begin(115200); // 디버깅용 (For debugging)
mpsSerial.begin(38400); // MPS 센서 UART (Table 1, Section 2.1)
// 센서 부팅 대기 (~3초, Section 2.1.4) (Wait for sensor boot-up, ~3 seconds)
Serial.println("센서 부팅 대기 중... (Waiting for sensor boot-up...)");
delay(3000);
// 통신 검증을 위해 버전 정보 읽기 (Read version info to verify communication, Section 2.1.4)
uint8_t swW, swX, swY, swZ, hwW, hwX, protoW, protoX;
if (readVersion(swW, swX, swY, swZ, hwW, hwX, protoW, protoX)) {
Serial.print("소프트웨어 버전: (SW Version: )");
Serial.print(swW); Serial.print("."); Serial.print(swX); Serial.print(".");
Serial.print(swY); Serial.print("."); Serial.println(swZ);
Serial.print("하드웨어 버전: (HW Version: )");
Serial.print(hwW); Serial.print("."); Serial.println(hwX);
Serial.print("프로토콜 버전: (Protocol Version: )");
Serial.print(protoW); Serial.print("."); Serial.println(protoX);
} else {
Serial.println("버전 정보 읽기 실패 (Failed to read version info)");
}
// 센서 정보 읽기 (Read sensor info, Section 2.1.6)
char serialNum[33], sku[33], calDate[17], mfgDate[17];
uint32_t sensorType;
if (readSensorInfo(serialNum, sensorType, sku, calDate, mfgDate)) {
Serial.print("시리얼 번호: (Serial Number: )"); Serial.println(serialNum);
Serial.print("센서 타입: (Sensor Type: )"); Serial.println(sensorType, HEX);
Serial.print("SKU: "); Serial.println(sku);
Serial.print("캘리브레이션 날짜: (Calibration Date: )"); Serial.println(calDate);
Serial.print("제조 날짜: (Manufacture Date: )"); Serial.println(mfgDate);
} else {
Serial.println("센서 정보 읽기 실패 (Failed to read sensor info)");
}
// %LEL ISO 단위로 연속 측정 시작 (Start continuous measurement in %LEL ISO, Section 2.1.4, Table 11)
startMeasurement(0x2, 0x0);
// 첫 측정 대기 (2초) (Wait for first measurement, 2 seconds)
delay(2000);
}
void loop() {
// 센서 상태 확인 (Check sensor status, Command 0x41, Section 2.1.6)
uint8_t status = readStatus();
if (status == 0x27) { // SENSOR_STARTUP (Section 2.1.4, Figure 6)
Serial.println("센서 시작 단계, 대기 중... (Sensor in startup phase, waiting...)");
delay(2000); // 다음 사이클 대기 (Wait for next cycle)
return;
} else if (status == 0x26) { // SENSOR_INITIALIZATION
Serial.println("센서 초기화 단계, 대기 중... (Sensor in initialization phase, waiting...)");
delay(2000);
return;
} else if (status == 0x33 || status == 0x34) { // GAS_DETECTED_DURING_STARTUP 또는 SLOW_GAS_ACCUMULATION (GAS_DETECTED_DURING_STARTUP or SLOW_GAS_ACCUMULATION)
Serial.println("경고: 가스 감지, 높은 가스 경보 보고 (>50%LEL) (Warning: Gas detected, reporting high gas alarm (>50%LEL))");
stopMeasurement();
delay(1000);
resetSensor();
delay(3000);
startMeasurement(0x2, 0x0);
delay(2000);
return;
} else if (status != 0x00) {
// 다른 오류는 readResponse에서 처리 (Other errors handled in readResponse)
delay(2000);
return;
}
// 센서 데이터 읽기 및 표시 (Read and display sensor data, Command 0x01, Section 2.1.6)
uint32_t cycleCount, gasID;
float concentration, temperature, pressure, relHumidity, absHumidity;
static uint32_t lastCycleCount = 0;
if (readAnswer(cycleCount, concentration, gasID, temperature, pressure, relHumidity, absHumidity)) {
// 신뢰 신호를 위한 사이클 카운트 확인 (Check cycle count for confidence signal, Section 2.1.9)
if (cycleCount == lastCycleCount) {
Serial.println("참고: 반복된 측정, 새 데이터 대기 (Note: Repeated measurement, waiting for new data)");
} else {
Serial.print("사이클 카운트: (Cycle Count: )"); Serial.println(cycleCount);
// 농도 범위 처리 (Handle concentration ranges, Table 7, Section 2.1.6)
if (concentration < -15.0) {
Serial.println("농도: -15 %LEL (범위 이하) (Concentration: -15 %LEL (Under Range))");
} else if (concentration >= -15.0 && concentration < -5.0) {
Serial.print("농도: (Concentration: )"); Serial.print(concentration, 1); Serial.println(" %LEL (범위 이하) (Under Range)");
} else if (concentration >= -5.0 && concentration < 110.0) {
Serial.print("농도: (Concentration: )"); Serial.print(concentration, 1); Serial.println(" %LEL");
} else {
Serial.println("농도: 110 %LEL (범위 초과) (Concentration: 110 %LEL (Over Range))");
}
// 가스 ID 표시 (Display gas ID, Table 8)
const char* gasDesc = "잘못된 가스 ID (Invalid Gas ID)";
for (int i = 0; i < sizeof(gasIDs) / sizeof(gasIDs[0]); i++) {
if (gasIDs[i].id == gasID) {
gasDesc = gasIDs[i].description;
break;
}
}
Serial.print("가스 ID: (Gas ID: )"); Serial.println(gasDesc);
Serial.print("온도: (Temperature: )"); Serial.print(temperature, 1); Serial.println(" °C");
Serial.print("압력: (Pressure: )"); Serial.print(pressure, 1); Serial.println(" kPa");
Serial.print("상대 습도: (Relative Humidity: )"); Serial.print(relHumidity, 1); Serial.println(" %RH");
Serial.print("절대 습도: (Absolute Humidity: )"); Serial.print(absHumidity, 1); Serial.println(" g/m³");
lastCycleCount = cycleCount;
}
} else {
Serial.println("센서 데이터 읽기 실패 (Failed to read sensor data)");
}
// 다음 측정 사이클 대기 (2초, 0.5 Hz, Section 2.1.4) (Wait for next measurement cycle, 2 seconds)
delay(2000);
}
4. 하드웨어 설정 (Hardware Setup)
MPS 센서는 S4(5핀) 폼 팩터를 사용하여 아두이노와 연결됩니다. Mini 폼 팩터 및 아날로그 출력은 주석으로 언급됩니다.
- 연결 (Connections, Section 3.1, Figure 12):
- TX(센서) → RX_PIN(아두이노 핀 10)
- RX(센서) → TX_PIN(아두이노 핀 11)
- RX/TX 는 3.0V 출력임으로 레벨 쉬프트가 필요함
- VCC → 3.3V 또는 5V (3.0–5.0V ±5%, Section 3.3)
- GND → 아두이노 GND
- 아날로그 출력(선택, 5핀) → 아날로그 핀(미구현, Section 2.2, Table 12: 0.4V=0 %LEL, 2.0V=100 %LEL)
- 소켓: Mill-Max 또는 Andon 소켓 사용. S4 핀 납땜 금지(보증 무효, Section 3.1).
- Mini 폼 팩터: PCB 납땜 필요, 열원 간격 및 인클로저 개구부 확인(Sections 3.2.2–3.2.4).
- 전원: HW_ERR_VDD 방지를 위해 안정적인 3.0–5.0V ±5% 공급.
- 방수: Porex PTFE PM21ML 멤브레인 사용 시 무거운 가스 응답 시간 영향(Section 3).
5. 테스트 절차 (Testing Procedures)
- 설정: MPS 센서를 하드웨어 설정에 따라 연결하고, 스케치를 아두이노(Uno 또는 Mega)에 업로드. 시리얼 모니터를 115200 보드로 열기.
- 검증:
- 시작 시 버전 및 센서 정보(Serial Number, SKU, Calibration Date 등) 출력 확인.
- 사이클 카운트가 2초마다 증가하는지 확인(신뢰 신호, Section 2.1.9).
- 가스 농도, 가스 ID, 온도, 압력, 습도 데이터의 유효성 검증.
- SENSOR_STARTUP(0x27) 또는 SENSOR_INITIALIZATION(0x26) 상태 코드 모니터링.
- 오류 처리:
- 시작 중 가스 감지(0x33/0x34) 시 경고 출력 및 센서 리셋 확인.
- SENSOR_NEGATIVE(0x30)가 10분 이상 지속 시 사용자 조치(예: 지원팀 문의).
- HW_ENV_XCD_RANGE(0x23) 발생 시 환경 조건 조정(Section 5).
- 환경 조건: 지정된 온도, 압력, 습도 범위 내 작동. BREATH_OR_HUMIDITY_SURGE(0x35) 방지를 위해 센서에 숨 불지 않기.
6. 결론 (Conclusion)
위 코드는 MPS Flammable Gas Sensor 5.0의 모든 UART 명령어와 상태 코드를 지원하며, 시작 시퀀스, 신뢰 신호, 환경 오류 처리를 완벽히 구현하였습니다. 아두이노 Uno 및 Mega와 호환되며, 안정적인 UART 통신을 통해 가스 농도, 가스 ID, 환경 데이터를 읽을 수 있습니다.. 아날로그 출력은 주석으로만 다루었으며, 추가 구현이 가능합니다. 테스트 절차를 통해 신뢰성과 정확성을 검증하였으며, 확장 가능성을 통해 다양한 애플리케이션에 적용할 수 있습니다.
'Sensor > 화학및가스센서' 카테고리의 다른 글
[Gas Sensor]NevadaNano의 MPS Flammable Gas Sensor 사양 요약 (0) | 2025.08.15 |
---|