# MPINO-8A4R-SU

MPINO-8A4R-SU는 디지털 입력 8점과 독립 COM 릴레이 출력 4점을 갖춘 일체형 산업용 아두이노입니다. 선택형 아날로그 입력, NTC 온도센서 입력, PWM 6점, RS-485·I²C·SPI 통신 기능을 한 장의 PCB에 구성했습니다.

Arduino IDE(Arduino C/C++) 또는 MPINO STUDIO 2(Arduino C/C++ & 래더로직)로 프로그램할 수 있습니다.

[MPINO Series 사양표](https://www.ilogics.net/docs/products:mpino_series#mpino_series_사양표 “products:mpino_series”)에서 MPINO-8A4R-SU의 보드 이름, 연결 방법과 첫 입출력 번호를 확인할 수 있습니다.

![MPINO-8A4R-SU 커넥터 안내 ](https://www.ilogics.net/docs/_media/products:mpino-8a4r-su-connectors.svg “MPINO-8A4R-SU 커넥터 안내 ”)

## 제품 사양서

구분 개수 접점명 사양
전원 - 전원전압 DC 24V 전용 (0.5A 이상)
디지털 입력 8점 (무접점·절연) P0~P7 동작 입력 전압: DC 0~40V HIGH 인식 전압: DC 5V 이상 4점/1COM NPN·PNP 입력 지원
릴레이 출력 4점 (유접점·절연) P32~P35 동작 연결 전압: DC 0~30V 또는 AC 0~250V 최대 출력 허용 전류: 16A/1점, 16A/1COM 1점/1COM
아날로그 입력 4점 (비절연) A0~A3 DC 0~5V: 10bit (0~1023), 입력저항 100MΩ (±1%) 0~20mA 또는 4~20mA: 10bit (0~1023 또는 0~818), 입력저항 250Ω (±1%) DC 0~10V: 10bit (0~1023), 입력저항 200kΩ (±1%), 선택 사양
온도센서 입력 2점 (비절연) NTEMP A4, A5 입력 온도: -40~120℃ 센서: NTC 3950K, 10kΩ(25℃) 분해능: 0.1℃ (0~40℃ 기준)
인터럽트 고속카운터·엔코더 4점 (비절연) D24(CLK), D25(DIO) D18(SCL), D19(SDA) 입력 전압: DC 0~5V HIGH 인식 전압: DC 3V 이상 입력 주파수: 최대 50kHz 10kΩ 풀업 내장 D24·D25 사용 시 FND 사용 불가 D18·D19 사용 시 I²C 사용 불가
펄스 출력 6점 (비절연) PWM D21, D22, D23 D26, D27, D28 출력 전압: LOW(DC 0V), HIGH(DC 5V) 최대 출력 전류: 30mA
통신 채널 1채널 (비절연) I²C I²C 마스터·슬레이브 지원 인터럽트 포트로 사용 가능 (10kΩ 풀업)
SPI SPI 마스터·슬레이브 지원 풀업·풀다운 없음
RS-485 Serial1 사용 Modbus RTU Master & Slave 내장 명령어 지원 LS산전 Cnet 내장 명령어 지원
메모리 - Flash 128KB
SRAM 4KB
EEPROM 4KB

## 디지털 입력

[처음 결선할 때는 디지털 입력 한 채널 확인 순서를 함께 보십시오. →](https://www.ilogics.net/docs/guide:wiring#디지털_입력_한_채널_확인 “guide:wiring”)

단자 Arduino 핀 입력 구성 단자대 및 결선 예시
P0~P3 D0~D3 디지털 입력 4점, COM0 공유 [MPINO-8A4R-SU 디지털 입력 단자대와 NPN·PNP 결선](https://www.ilogics.net/docs/_media/products:mpino-8a4r-su-digital-input.svg “products:mpino-8a4r-su-digital-input.svg”)
P4~P7 D4~D7 디지털 입력 4점, COM1 공유

디지털 입력은 포토커플러로 절연되어 있으며 DC 5~24V의 NPN 또는 PNP 입력을 연결할 수 있습니다. 입력 방식은 COM 결선 방향으로 선택합니다.

### 예제 프로그램 · 디지털 입력

digitalRead()로 D0~D7의 입력 상태를 200ms마다 표시합니다. HIGH는 1, LOW는 0입니다.

```cpp void setup() {

Serial.begin(115200);

}

void loop() {

for (uint8_t pin = 0; pin <= 7; pin++) {
  Serial.print(F("D"));
  Serial.print(pin);
  Serial.print(F(": "));
  Serial.println(digitalRead(pin));
}
delay(200);

} ```

관련 내장 명령어

#### 함수 예제 · 연속 판독 필터: IdigitalRead()

함수 원형

```cpp bool IdigitalRead(uint8_t pin, uint8_t samples); ```

IdigitalRead(0, 5)는 D0을 5회 연속 판독합니다. 모두 HIGH 또는 모두 LOW일 때 상태를 갱신하고, 값이 섞이면 이전 상태를 유지합니다. 시간 기준의 채터링 필터와 달리 판독 사이에 지연을 넣지 않습니다.

```cpp void setup() {

Serial.begin(115200);

}

void loop() {

const bool inputOn = IdigitalRead(0, 5);  // D0을 5회 연속 판독해 안정된 상태 반환
Serial.println(inputOn ? F("D0 ON") : F("D0 OFF"));
delay(200);

} ```

#### 함수 예제 · 채터링 방지: Ibounce(), IbounceOn(), IbounceOff()

함수 원형

```cpp bool Ibounce(uint8_t pin, uint32_t debounceTime); bool IbounceOn(uint8_t pin, uint32_t debounceTime); bool IbounceOff(uint8_t pin, uint32_t debounceTime); ```

D0 입력을 세 가지 필터로 비교합니다. 반환값은 필터 처리된 ON/OFF 상태입니다.

- `Ibounce(0, 30)`: ON과 OFF 모두 30ms 이상 안정된 뒤 반영합니다. - `IbounceOn(0, 500)`: ON만 500ms 지연하고 OFF는 즉시 반영합니다. - `IbounceOff(0, 500)`: OFF만 500ms 지연하고 ON은 즉시 반영합니다.

각 함수는 같은 핀에서도 독립적으로 동작합니다. 필터를 loop()마다 실행하고 결과만 200ms 간격으로 표시합니다.

```cpp unsigned long lastPrint = 0;

void setup() {

Serial.begin(115200);

}

void loop() {

const bool stable = Ibounce(0, 30);  // D0의 ON/OFF 채터링을 30ms로 필터링
const bool onDelayed = IbounceOn(0, 500);  // D0의 ON만 500ms 지연, OFF는 즉시 반영
const bool offDelayed = IbounceOff(0, 500);  // D0의 OFF만 500ms 지연, ON은 즉시 반영

if (millis() - lastPrint >= 200) {

  lastPrint = millis();
  Serial.print(F("Ibounce: "));
  Serial.print(stable);
  Serial.print(F(", IbounceOn: "));
  Serial.print(onDelayed);
  Serial.print(F(", IbounceOff: "));
  Serial.println(offDelayed);
}

} ```

#### 함수 예제 · 입력 토글: Ialt()

함수 원형

```cpp void Ialt(bool input, bool &state_var); ```

Ialt(input, state)는 입력이 OFF에서 ON으로 바뀔 때마다 state를 반전합니다. D0을 한 번 누르면 D32가 ON, 다시 누르면 OFF가 되며 누르고 있는 동안은 유지됩니다. 예제는 Ibounce(0, 30)로 채터링을 걸러 Ialt()에 전달합니다. outputOn은 호출 사이에 유지되는 전역 변수이며, Ialt()가 이 값을 직접 변경합니다.

```cpp bool outputOn = false;

void setup() {

Serial.begin(115200);
digitalWrite(32, LOW);

}

void loop() {

const bool previous = outputOn;
const bool inputOn = Ibounce(0, 30);  // D0의 ON/OFF 채터링을 30ms로 필터링
Ialt(inputOn, outputOn);  // 입력이 OFF→ON일 때 outputOn 반전
digitalWrite(32, outputOn ? HIGH : LOW);

if (outputOn != previous) {

  Serial.println(outputOn ? F("D32 ON") : F("D32 OFF"));
}

} ```

## 릴레이 출력

[처음 결선할 때는 릴레이 출력 한 채널 확인 순서를 함께 보십시오. →](https://www.ilogics.net/docs/guide:wiring#릴레이_출력_한_채널_확인 “guide:wiring”)

단자 Arduino 핀 접점 정격 COM 구성 단자대 및 결선 예시
P32 D32 AC 250V 또는 DC 30V, 최대 16A COM2 독립 [MPINO-8A4R-SU 릴레이 출력 단자대와 부하 결선](https://www.ilogics.net/docs/_media/products:mpino-8a4r-su-relay-output.svg “products:mpino-8a4r-su-relay-output.svg”)
P33 D33 AC 250V 또는 DC 30V, 최대 16A COM3 독립
P34 D34 AC 250V 또는 DC 30V, 최대 16A COM4 독립
P35 D35 AC 250V 또는 DC 30V, 최대 16A COM5 독립

각 릴레이는 1점마다 독립된 COM 단자를 사용합니다. 릴레이 접점은 전원을 출력하지 않는 무전압 접점이며, 릴레이 동작에는 DC 24V 전원이 필요합니다.

### 예제 프로그램 · 릴레이 출력

D0~D3 입력을 D32~D35 출력에 순서대로 대응시킵니다. 입력이 HIGH이면 해당 출력이 ON됩니다.

```cpp void setup() {

for (uint8_t pin = 32; pin <= 35; pin++) digitalWrite(pin, LOW);

}

void loop() {

for (uint8_t ch = 0; ch < 4; ch++) {
  digitalWrite(32 + ch, digitalRead(0 + ch));
}

} ```

## 아날로그·온도 입력

채널 입력 종류 입력 범위 커넥터 핀 배열
A0~A3 선택형 아날로그 입력 DC 0~5V 또는 0~20mA·4~20mA DC 0~10V는 선택 사양 [MPINO-8A4R-SU 아날로그·온도 입력 커넥터 핀 배열](https://www.ilogics.net/docs/_media/products:mpino-8a4r-su-analog-input.svg “products:mpino-8a4r-su-analog-input.svg”)
A4, A5 NTC 온도센서 입력 NTC 10kΩ, 3950K

A0~A3은 출하 시 0~20mA 모드이며, 채널별 SELECT 점퍼를 제거하면 DC 0~5V 입력으로 사용할 수 있습니다.

### 예제 프로그램 · 아날로그 입력

analogRead()로 A0~A3의 ADC 원시값(0~1023)을 표시합니다. 각 채널의 전압·전류 모드는 실제 제품 설정을 따릅니다.

```cpp const uint8_t analogPins[] = {A0, A1, A2, A3};

void setup() {

Serial.begin(115200);

}

void loop() {

for (uint8_t ch = 0; ch < 4; ch++) {
  Serial.print(F("A"));
  Serial.print(ch);
  Serial.print(F(" ADC: "));
  Serial.println(analogRead(analogPins[ch]));
}
delay(500);

} ```

관련 내장 명령어

#### 함수 예제 · 입력 범위 환산: IanalogRead(), IanalogReadf()

함수 원형

```cpp int32_t IanalogRead(uint8_t ch, int32_t min, int32_t max); float IanalogReadf(uint8_t ch, float min, float max); ```

A0를 0~5V 입력으로, A2를 0(4)~20mA 입력으로 설정한 경우의 예제입니다. 두 함수는 ADC 입력을 지정한 min~max 범위로 환산합니다. IanalogRead()는 소수부를 버린 정수, IanalogReadf()는 실수를 반환합니다. A0의 0~5V 입력을 0~100%와 전압으로 각각 표시합니다. A2의 0~20mA 입력은 0~20 범위를 지정하여 실제 전류로 읽습니다.

```cpp void setup() {

Serial.begin(115200);

}

void loop() {

const int32_t percent = IanalogRead(A0, 0, 100);  // A0 입력을 0~100% 정수로 환산
const float voltage = IanalogReadf(A0, 0.0f, 5.0f);  // A0 입력을 0~5V 실수로 환산
const float current = IanalogReadf(A2, 0.0f, 20.0f);  // A2 입력을 0~20mA 실수로 환산
Serial.print(F("A0: "));
Serial.print(percent);
Serial.print(F(" %, "));
Serial.print(voltage, 2);
Serial.print(F(" V, A2: "));
Serial.print(current, 2);
Serial.println(F(" mA"));
delay(500);

} ```

#### 함수 예제 · 4~20mA 범위 환산: IanalogRead2(), IanalogRead2f()

함수 원형

```cpp int32_t IanalogRead2(uint8_t ch, int32_t min, int32_t max); float IanalogRead2f(uint8_t ch, float min, float max); ```

A0를 0~5V 입력으로, A2를 0(4)~20mA 입력으로 설정한 경우의 예제입니다. A2에 연결한 4~20mA 센서 입력을 사용자 범위로 환산합니다. IanalogRead2(A2, 0, 100)는 4mA를 0%, 20mA를 100%로 변환한 정수를 반환합니다. IanalogRead2f(A2, 4.0f, 20.0f)는 전류를 실수로 반환합니다. 약 3.6mA 미만은 두 함수 모두 ANALOG\_READ2\_ERROR(65535), 약 3.6~4mA는 지정한 최솟값을 반환합니다.

```cpp void setup() {

Serial.begin(115200);

}

void loop() {

const int32_t percent = IanalogRead2(A2, 0, 100);  // A2의 4~20mA를 0~100% 정수로 환산
const float current = IanalogRead2f(A2, 4.0f, 20.0f);  // A2의 4~20mA를 전류 실수값으로 환산
if (percent == ANALOG_READ2_ERROR ||
    current == (float)ANALOG_READ2_ERROR) {
  Serial.println(F("A2: 4-20mA input error"));
} else {
  Serial.print(F("A2: "));
  Serial.print(percent);
  Serial.print(F(" %, "));
  Serial.print(current, 2);
  Serial.println(F(" mA"));
}
delay(500);

} ```

#### 함수 예제 · 이동 평균: analogReadAvg(), analogRead2Avg()

함수 원형

```cpp int32_t analogReadAvg(uint8_t ch, uint8_t samples); int32_t analogRead2Avg(uint8_t ch, uint8_t samples); ```

A0를 0~5V 입력으로, A2를 0(4)~20mA 입력으로 설정한 경우의 예제입니다. analogReadAvg(A0, 5)는 A0의 ADC 원시값(0~1023)을, analogRead2Avg(A2, 5)는 A2의 4~20mA 보정값(4mA=0, 20mA=32767)을 이동 평균으로 반환합니다. 최근 호출에서 읽은 정상값을 최대 5개까지 누적해 평균하며, 한 번에 5회 읽는 함수가 아닙니다. samples는 최대 20입니다. analogRead2Avg()는 약 3.6mA 미만 입력에서 ANALOG\_READ2\_AVG\_ERROR(-1)를 반환합니다.

```cpp void setup() {

Serial.begin(115200);

}

void loop() {

const int32_t voltageAvg = analogReadAvg(A0, 5);  // 최근 A0 ADC 값 최대 5개의 이동평균
const int32_t currentAvg = analogRead2Avg(A2, 5);  // 최근 A2의 4~20mA 보정값 최대 5개의 이동평균
Serial.print(F("A0 ADC average: "));
Serial.print(voltageAvg);
Serial.print(F(", A2 4-20mA average: "));
if (currentAvg == ANALOG_READ2_AVG_ERROR) {
  Serial.println(F("input error"));
} else {
  Serial.println(currentAvg);
}
delay(500);

} ```

### 예제 프로그램 · 온도센서 입력

analogRead()로 A4, A5의 온도센서 ADC 원시값(0~1023)을 표시합니다. 섭씨 환산은 아래 내장 명령어에서 설명합니다.

```cpp void setup() {

Serial.begin(115200);

}

void loop() {

Serial.print(F("A4 ADC: "));
Serial.println(analogRead(A4));
Serial.print(F("A5 ADC: "));
Serial.println(analogRead(A5));
delay(500);

} ```

관련 내장 명령어

#### 함수 예제 · NTC 온도 읽기: ntcRead(), ntcReadf()

함수 원형

```cpp int ntcRead(uint8_t ch); float ntcReadf(uint8_t ch); ```

NTC 입력 A4에 연결한 NTC 10kΩ(25℃), B=3950K 센서의 온도를 읽습니다. ntcRead()는 섭씨 온도의 10배 정수(25.3℃ → 253), ntcReadf()는 섭씨 실수(25.3)를 반환합니다. 두 함수의 분해능은 동일하게 0.1℃입니다. 변환 범위 밖 값은 -40℃ 또는 120℃로 제한되므로 단선·단락 판정값으로 사용하지 않습니다.

```cpp void setup() {

Serial.begin(115200);

}

void loop() {

const int temperature10 = ntcRead(A4);  // A4 온도를 섭씨×10 정수로 반환
const float temperature = ntcReadf(A4);  // A4 온도를 섭씨 실수로 반환
Serial.print(F("ntcRead: "));
Serial.print(temperature10);
Serial.print(F(" (0.1 C), ntcReadf: "));
Serial.print(temperature, 1);
Serial.println(F(" C"));
delay(500);

} ```

## PWM·고속 입력

기능 Arduino 핀 점수 커넥터 핀 배열
PWM 출력 D21, D22, D23, D26, D27, D28 6점 [MPINO-8A4R-SU PWM 출력 커넥터 핀 배열](https://www.ilogics.net/docs/_media/products:mpino-8a4r-su-pwm-output.svg “products:mpino-8a4r-su-pwm-output.svg”)
고속 입력·인터럽트 D18, D19, D24, D25 4점
엔코더 입력 D24, D25 2점

D18과 D19는 I²C의 SCL·SDA 기능을 공유하고, D24와 D25는 FND/ENCO의 CLK·DIO 기능을 공유합니다. 같은 핀의 두 기능은 동시에 사용할 수 없습니다.

### 예제 프로그램 · PWM 출력

analogWrite()의 0~255 듀티값 중 128을 D21, D22, D23, D26, D27, D28에 지정해 약 50% PWM을 출력합니다.

```cpp void setup() {

analogWrite(21, 128);
analogWrite(22, 128);
analogWrite(23, 128);
analogWrite(26, 128);
analogWrite(27, 128);
analogWrite(28, 128);

}

void loop() { } ```

관련 내장 명령어

#### 함수 예제 · 듀티값 지정: PWM()

함수 원형

```cpp void PWM(uint8_t pin, uint16_t val, bool onDutybit16 = false); ```

PWM(pin, val, onDutybit16)은 듀티값을 지정합니다. 8비트는 0~255, 세 번째 인수가 true인 16비트는 0~65535입니다. D21에 16비트 중간값 32768을 출력합니다. D21~D23은 Timer3, D26~D28은 Timer1를 공유하므로 같은 타이머는 같은 분해능으로 사용합니다. PWM\_RESET() 후 시작합니다.

```cpp void setup() {

PWM_RESET();  // PWM 타이머를 정지하고 모드 초기화
PWM(21, 32768, true);  // D21에 16비트 중간 듀티값 출력

}

void loop() { } ```

#### 함수 예제 · 주파수·듀티 설정: FDPWM()

함수 원형

```cpp void FDPWM(uint8_t pin, int32_t intHz, float Duty); ```

FDPWM(pin, intHz, Duty)는 주파수(Hz)와 듀티(%)를 지정합니다. D21에 1kHz, 50%를 출력합니다. D21~D23은 Timer3, D26~D28은 Timer1를 공유하므로 같은 타이머의 출력은 같은 주파수를 지정합니다. 별도 FPWM() 함수는 없으며 50% 출력은 FDPWM(pin, hz, 50.0f)로 설정합니다.

```cpp void setup() {

PWM_RESET();  // PWM 타이머를 정지하고 모드 초기화
FDPWM(21, 1000, 50.0f);  // D21에 1kHz, 50% 듀티 출력

}

void loop() { } ```

#### 함수 예제 · 지정 개수 펄스 출력: NPWM\_BEGIN(), NPWM()

함수 원형

```cpp void NPWM_BEGIN(uint8_t pin, uint32_t intHz, float Duty, uint32_t N); void NPWM(uint8_t pin); ```

NPWM\_BEGIN()으로 D21의 1kHz·50% 펄스 100개를 준비하고 NPWM()을 loop()마다 실행해 출력 및 완료 처리를 합니다. delay()를 넣지 않습니다. D21~D23은 Timer3, D26~D28은 Timer1를 공유하며, 같은 타이머의 다른 출력과 혼합하지 않습니다.

```cpp void setup() {

PWM_RESET();  // PWM 타이머를 정지하고 모드 초기화
NPWM_BEGIN(21, 1000, 50.0f, 100);  // D21에 1kHz, 50% 펄스 100개 준비

}

void loop() {

NPWM(21);  // 준비한 펄스 출력 진행 및 완료 처리

} ```

#### 함수 예제 · 채널 출력 정지·재개: PWMOFF()

함수 원형

```cpp void PWMOFF(uint8_t pin, bool POff); ```

PWMOFF(pin, true)는 채널 정지 플래그를 설정하고 false는 해제합니다. 설정 직후 FDPWM()을 호출해 출력 연결에 반영합니다. D21을 2초 켜고 2초 끄며, 정지 후 digitalWrite()로 LOW를 유지합니다. NPWM()에는 이 정지 플래그가 적용되지 않습니다.

```cpp void setup() {

PWM_RESET();  // PWM 타이머를 정지하고 모드 초기화

}

void loop() {

PWMOFF(21, false);  // D21 정지 상태 해제
FDPWM(21, 1000, 50.0f);  // 해제를 반영해 1kHz, 50% 출력 시작
delay(2000);

PWMOFF(21, true); // D21 정지 상태 설정

FDPWM(21, 1000, 50.0f);  // 정지를 반영해 PWM 출력 연결 해제
digitalWrite(21, LOW);
delay(2000);

} ```

#### 함수 예제 · PWM 타이머 정지·초기화: PWM\_RESET()

함수 원형

```cpp void PWM_RESET(); ```

PWM\_RESET()은 Timer1·Timer3 제어 레지스터와 카운터를 초기화합니다. D21에 1kHz를 2초 출력한 후 전체 타이머를 정지합니다. 같은 타이머의 PWM·카운터에도 영향을 줍니다. PWMOFF()의 정지 플래그까지 해제하지는 않습니다.

```cpp void setup() {

PWM_RESET();  // PWM 타이머를 정지하고 모드 초기화
FDPWM(21, 1000, 50.0f);  // D21에 1kHz, 50% 듀티 출력
delay(2000);
PWM_RESET();  // Timer1·Timer3의 모든 PWM 정지 및 초기화
digitalWrite(21, LOW);

}

void loop() { } ```

### 예제 프로그램 · 펄스 카운트

CLK(D24)의 FALLING 에지를 계수합니다. 입력은 10kΩ 풀업입니다. 32비트 값을 ATOMIC\_BLOCK으로 읽어 500ms마다 표시하는 소프트웨어 인터럽트 예제입니다. 최대 입력 주파수에서 무손실을 보장하지 않습니다.

```cpp static_assert(digitalPinToInterrupt(CLK) != NOT_AN_INTERRUPT, “Invalid interrupt input”);

#include <util/atomic.h>

volatile uint32_t pulseCount = 0;

void countPulse() {

pulseCount++;

}

void setup() {

Serial.begin(115200);
// 입력 신호의 선택한 에지에서 계수
attachInterrupt(digitalPinToInterrupt(CLK), countPulse, FALLING);

}

void loop() {

uint32_t snapshot;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  snapshot = pulseCount;
}
Serial.print(F("CLK pulses: "));
Serial.println(snapshot);
delay(500);

} ```

### 예제 프로그램 · 엔코더 입력

A상은 CLK(D24), B상은 DIO(D25)에 연결합니다. 10kΩ 풀업이며 FALLING 에지에서 B상이 LOW이면 증가하는 1배 계수 예제입니다. 실제 회전 방향은 A·B상 결선에 따라 달라집니다.

```cpp static_assert(digitalPinToInterrupt(CLK) != NOT_AN_INTERRUPT, “Invalid interrupt input”);

#include <util/atomic.h> #include <stdint.h>

volatile int32_t position = 0;

void readEncoder() {

// A상 하강 시 B상이 LOW이면 증가 (1배 카운트)
if (digitalRead(DIO) == LOW) {
  if (position < INT32_MAX) position++;
} else {
  if (position > INT32_MIN) position--;
}

}

void setup() {

Serial.begin(115200);
// 엔코더 A상 → CLK(D24), B상 → DIO(D25)
attachInterrupt(digitalPinToInterrupt(CLK), readEncoder, FALLING);

}

void loop() {

int32_t snapshot;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  snapshot = position;
}
Serial.print(F("Encoder: "));
Serial.println(snapshot);
delay(500);

} ```

## 통신과 다운로드

기능 Arduino 핀 사용 객체 상대 기기 결선
USB 다운로드·모니터링 D16(RX0), D17(TX0) `Serial` [MPINO-8A4R-SU 통신 커넥터와 상대 기기 결선](https://www.ilogics.net/docs/_media/products:mpino-8a4r-su-communication.svg “products:mpino-8a4r-su-communication.svg”)
RS-485 Serial1 전용 핀 `Serial1`
I²C D18(SCL), D19(SDA) `Wire`
SPI D8(SS), D9(SCK), D10(MOSI), D11(MISO) `SPI`

RS-485에는 자동 방향제어 회로가 내장되어 있어 별도의 송수신 방향제어 핀이 필요하지 않습니다.

### 예제 프로그램 · 기본 통신

Serial(115200bps)과 RS-485 Serial1(9600bps, 8-N-1) 사이에 문자를 전달합니다. availableForWrite()로 송신 버퍼 여유를 확인합니다. RS-485는 자동 방향제어 방식으로 별도 방향 핀을 설정하지 않습니다.

```cpp // MP 다운로드 케이블의 Serial 모니터 ↔ RS-232 Serial1 void setup() {

Serial.begin(115200);
Serial1.begin(9600);  // 상대 기기도 9600 bps, 8-N-1 설정

}

void loop() {

if (Serial.available() && Serial1.availableForWrite()) {
  Serial1.write(Serial.read());
}
if (Serial1.available() && Serial.availableForWrite()) {
  Serial.write(Serial1.read());
}

} ```

### 예제 프로그램 · I²C 통신

Wire로 주소 0x08~0x77을 검색하고 응답한 주소를 3초마다 표시합니다. SDA·SCL을 사용하는 엔코더/인터럽트 예제와 동시에 실행하지 않습니다.

```cpp #include <Wire.h>

void setup() {

Serial.begin(115200);
Wire.begin();
Wire.setClock(100000);

}

void loop() {

uint8_t found = 0;
for (uint8_t address = 0x08; address <= 0x77; address++) {
  Wire.beginTransmission(address);
  const uint8_t result = Wire.endTransmission();
  if (result == 0) {
    Serial.print(F("I2C: 0x"));
    if (address < 16) Serial.print('0');
    Serial.println(address, HEX);
    found++;
  }
}
Serial.print(F("Devices: "));
Serial.println(found);
delay(3000);

} ```

### 예제 프로그램 · Modbus RTU 통신

관련 내장 명령어

#### 함수 예제 · Modbus RTU Slave: ImodbusRTUMem(), ImodbusRTUAdr(), ImodbusRTUInit(), ImodbusRTU()

함수 원형

```cpp void ImodbusRTUMem(uint16_t m_size, uint16_t d_size); void ImodbusRTUAdr(uint16_t m_offset, uint16_t d_offset); void ImodbusRTUInit(HardwareSerial& serialPort, uint8_t slaveId, long baudrate); void ImodbusRTU(); ```

D0 상태를 M[0], A0 원시값(0~1023)을 D[0]에 넣어 상대 기기에서 읽습니다. RS-485(Serial1), 국번 1, 9600bps, 8N1로 설정하고, M[0]은 FC01·시작 주소 0·1비트, D[0]은 FC03·시작 주소 0·1워드로 읽습니다. 주소는 실제 전송 주소(0부터) 기준입니다. 통신 함수는 loop()마다 호출하며 출력은 제어하지 않습니다.

M과 D는 코어가 제공하는 공유 전역 메모리입니다. 이 스케치는 단독 실행하며, 다른 프로토콜과 함께 사용할 때는 메모리를 중복 할당하지 않습니다.

```cpp unsigned long lastPrint = 0; bool memoryReady = false;

void setup() {

Serial.begin(115200);
ImodbusRTUMem(8, 8);  // M 8비트와 D 8워드 메모리 할당
ImodbusRTUAdr(0, 0);  // M과 D의 통신 시작 주소를 0으로 설정
ImodbusRTUInit(Serial1, 1, 9600);  // RS-485를 국번 1, 9600bps로 시작
memoryReady = (M != nullptr && D != nullptr);
if (!memoryReady) Serial.println(F("Memory allocation failed"));

}

void loop() {

if (!memoryReady) return;
M[0] = digitalRead(0);
D[0] = analogRead(A0);
ImodbusRTU();  // 수신한 Modbus RTU 요청 처리

if (millis() - lastPrint >= 1000) {

  lastPrint = millis();
  Serial.print(F("M[0]: "));
  Serial.print(M[0]);
  Serial.print(F(", D[0]: "));
  Serial.println(D[0]);
}

} ```

#### 함수 예제 · Modbus RTU Master: ImodbusRTUmasterInit(), ImodbusRTUmaster()

함수 원형

```cpp void ImodbusRTUmasterInit(HardwareSerial& serialPort, long baudrate, uint8_t config); uint8_t ImodbusRTUmaster(HardwareSerial& serialPort, uint8_t slaveId, uint8_t functionCode, uint16_t address, uint16_t quantity, uint16_t* data, uint16_t timeoutMs = 100); ```

RS-485(Serial1)로 상대 슬레이브의 홀딩 레지스터 1개를 1초마다 읽습니다. 상대 기기는 국번 1, 9600bps, 8N1, FC03·실제 시작 주소 0으로 준비합니다. 응답은 received[0]에 저장하며 성공한 경우에만 표시합니다. 오류 코드는 16진수로 표시합니다. 위 슬레이브 예제와는 별도 스케치이며, M/D 메모리 대신 수신 배열을 사용합니다.

```cpp unsigned long lastRequest = 0; uint16_t received[1];

void setup() {

Serial.begin(115200);
ImodbusRTUmasterInit(Serial1, 9600, SERIAL_8N1);  // RS-485를 Modbus 마스터로 시작

}

void loop() {

if (millis() - lastRequest < 1000) return;
lastRequest = millis();
const uint8_t result = ImodbusRTUmaster(Serial1, 1, 3, 0, 1, received, 200);  // 국번 1의 주소 0에서 1워드 읽기, 제한 200ms

if (result == IMODBUS_RTU_SUCCESS) {

  Serial.print(F("Register 0: "));
  Serial.println(received[0]);
} else {
  Serial.print(F("Modbus error: 0x"));
  Serial.println(result, HEX);
}

} ```

### 예제 프로그램 · LS산전 Cnet 통신

관련 내장 명령어

#### 함수 예제 · LS산전 Cnet SLave: ICnetMem(), ICnetAdr(), ICnetInit(), ICnet()

함수 원형

```cpp void ICnetMem(uint16_t m_size, uint16_t d_size, uint16_t r_size = 100); void ICnetAdr(uint16_t m_offset, uint16_t d_offset, uint16_t r_offset = 0); void ICnetInit(HardwareSerial& serialPort, uint8_t slaveId, long baudrate); void ICnet(); ```

D0 상태를 M[0], A0 원시값(0~1023)을 D[0]에 넣어 HMI에서 읽습니다. 상대 기기는 LS산전 Cnet, RS-485, 국번 1, 9600bps, 8N1로 설정합니다. 비트는 %MX00000(M[0]), 워드는 %DW0(D[0])을 읽습니다. 통신 함수는 loop()마다 호출하며 출력은 제어하지 않습니다.

M·D·R은 코어가 제공하는 공유 전역 메모리이며, 이 예제는 R을 사용하지 않습니다. Modbus 예제와는 단독 실행하며, 다른 프로토콜과 함께 사용할 때는 메모리를 중복 할당하지 않습니다.

```cpp unsigned long lastPrint = 0; bool memoryReady = false;

void setup() {

Serial.begin(115200);
ICnetMem(8, 8, 0);  // M 8비트와 D 8워드 할당, R은 사용하지 않음
ICnetAdr(0, 0, 0);  // M과 D의 통신 시작 주소를 0으로 설정
ICnetInit(Serial1, 1, 9600);  // RS-485를 국번 1, 9600bps로 시작
memoryReady = (M != nullptr && D != nullptr);
if (!memoryReady) Serial.println(F("Memory allocation failed"));

}

void loop() {

if (!memoryReady) return;
M[0] = digitalRead(0);
D[0] = analogRead(A0);
ICnet();  // 수신한 LS Cnet 요청 처리

if (millis() - lastPrint >= 1000) {

  lastPrint = millis();
  Serial.print(F("M[0]: "));
  Serial.print(M[0]);
  Serial.print(F(", D[0]: "));
  Serial.println(D[0]);
}

} ```

[← MPINO Series 비교표로 돌아가기](https://www.ilogics.net/docs/products:mpino_series “products:mpino_series”)

[온라인 상세설명](https://www.ilogics.net/docs/products:mpino_8a4r_su)