# MPINO-8A4R-S
MPINO-8A4R-S is an all-in-one industrial Arduino with 8 digital inputs and 4 relay with independent COM terminals outputs. A single PCB integrates voltage and current analog inputs, NTC temperature sensor inputs, PWM, high-speed inputs, and RS-232 communication.
Program the controller with Arduino IDE (Arduino C/C++) or MPINO STUDIO 2 (Arduino C/C++ & ladder logic).
See the [MPINO Series specifications](https://www.ilogics.net/docs/en-us_products:mpino_series#mpino_series_specifications “en-us_products:mpino_series”) for the board name, connection method, and first I/O numbers for MPINO-8A4R-S.

## Product Specifications
| Category | Quantity | Terminal / Item | Specifications |
| — | — | — | — |
| Power | - | Supply voltage | DC 12~24V (0.5A or more when using DC 24V) |
| Digital inputs | 8 points (Solid-state, isolated) | P0~P7 | operating input voltage: DC 0~40V HIGH detection voltage: DC 5V or higher 8 points/1COM NPN/PNP input support |
| Relay outputs | 4 points (Mechanical contacts, isolated) | P32~P35 | operating connection voltage: DC 0~30V or AC 0~250V maximum allowable output current: 5A/1 points, 5A/1COM 1 points/1COM |
| Analog inputs | 2 points (Non-isolated) | A0, A1 | input voltage: DC 0~5V resolution: 10bit (0~1023) input resistance: 100MΩ (±1%) |
| Analog inputs | 2 points (Non-isolated) | A2, A3 | input current: 0~20mA or 4~20mA Resolution: 0~20mA is 10bit (0~1023); 4~20mA is 0~818 input resistance: 250Ω (±1%) |
| Analog inputs | 2 points (Non-isolated) | A4, A5 | input voltage: DC 0~10V resolution: 10bit (0~1023) input resistance: 200kΩ (±1%) |
| Temperature sensor inputs | 2 points (Non-isolated) | NTEMP A6, A7 | input temperature: -40~120℃ Sensor: NTC 3950K, 10kΩ(25℃) Resolution: 0.1℃ (at 0~40℃) |
| Interrupts High-Speed Counter / Encoder | 4 points (Non-isolated) | D24(CLK), D25(DIO) D18(SCL), D19(SDA) | input voltage: DC 0~5V HIGH detection voltage: DC 3V or higher input frequency: Max. 50kHz D24/D25: built-in 4.7kΩ pull-down D18/D19: built-in 4.7kΩ pull-up |
| Pulse outputs | 3 points (Non-isolated) | PWM D21, D22, D23 | output voltage: LOW(DC 0V), HIGH(DC 5V) maximum output current: 30mA |
| Communication Channels | 1 channel (Non-isolated) | I²C | I²C master/slave supported Can be used as interrupt ports (4.7kΩ pull-up) |
| RS-232 | Uses Serial1 Built-in Modbus RTU Master & Slave commands supported Built-in LS Industrial Systems Cnet commands supported | ||
| Memory | - | Flash | 128KB |
| SRAM | 4KB | ||
| EEPROM | 4KB |
## Digital inputs
[See the one-channel digital-input check before wiring for the first time. →](https://www.ilogics.net/docs/en-us_guide:wiring#digital_input_one_channel_check “en-us_guide:wiring”)
| Terminal | Arduino Pin | Input Configuration | Terminals and Wiring Example |
| — | — | — | — |
| P0~P3 | D0~D3 | 4 digital inputs, sharing COM0 | [MPINO-8A4R-S digital input terminals and NPN/PNP wiring](https://www.ilogics.net/docs/_media/en-us_products:mpino-8a4r-s-digital-input.svg “en-us_products:mpino-8a4r-s-digital-input.svg”) |
| P4~P7 | D4~D7 | 4 digital inputs, sharing COM1 |
The digital inputs are isolated by optocouplers. DC 0~40V in the specification is the input circuit's operating voltage range; the wiring below is an example using a DC 5~24V sensor supply. The input is recognized as HIGH when the voltage between the input terminal and COM is at least DC 5V. Select NPN or PNP operation by wiring COM accordingly.
### Example program · digital inputs
Use digitalRead() to display the D0~D7 input states every 200ms. HIGH is 1 and LOW is 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);
} ```
Related built-in functions
#### Function example · consecutive-read filter: IdigitalRead()
Function prototype
```cpp bool IdigitalRead(uint8_t pin, uint8_t samples); ```
IdigitalRead(0, 5) reads D0 5 times consecutively. It updates the state when all readings are HIGH or all are LOW; mixed readings retain the previous state. Unlike a time-based debounce filter, it inserts no delay between readings.
```cpp void setup() {
Serial.begin(115200);
}
void loop() {
const bool inputOn = IdigitalRead(0, 5); // Read D0 five times consecutively and return the stable state
Serial.println(inputOn ? F("D0 ON") : F("D0 OFF"));
delay(200);
} ```
#### Function example · debouncing: Ibounce(), IbounceOn(), IbounceOff()
Function prototype
```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); ```
Compare three filters on the D0 input. Each returns the filtered ON/OFF state.
- `Ibounce(0, 30)`: Apply either ON or OFF after it remains stable for at least 30ms. - `IbounceOn(0, 500)`: Delay ON by 500ms; apply OFF immediately. - `IbounceOff(0, 500)`: Delay OFF by 500ms; apply ON immediately.
Each function operates independently, even on the same pin. Run the filters on every loop() iteration and display only the results at 200ms intervals.
```cpp unsigned long lastPrint = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
const bool stable = Ibounce(0, 30); // Filter D0 ON/OFF chatter over 30ms const bool onDelayed = IbounceOn(0, 500); // Delay only D0 ON by 500ms; apply OFF immediately const bool offDelayed = IbounceOff(0, 500); // Delay only D0 OFF by 500ms; apply ON immediately
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);
}
} ```
#### Function example · input toggle: Ialt()
Function prototype
```cpp void Ialt(bool input, bool &state_var); ```
Ialt(input, state) inverts state whenever the input changes from OFF to ON. Pressing D0 once turns D32 ON; pressing again turns it OFF. Holding the input does not change the state again. This example debounces the input with Ibounce(0, 30) before passing it to Ialt(). outputOn is a global variable retained between calls, and Ialt() modifies it directly.
```cpp bool outputOn = false;
void setup() {
Serial.begin(115200); digitalWrite(32, LOW);
}
void loop() {
const bool previous = outputOn; const bool inputOn = Ibounce(0, 30); // Filter D0 ON/OFF chatter over 30ms Ialt(inputOn, outputOn); // Invert outputOn on an OFF→ON input transition digitalWrite(32, outputOn ? HIGH : LOW);
if (outputOn != previous) {
Serial.println(outputOn ? F("D32 ON") : F("D32 OFF"));
}
} ```
## Relay outputs
[See the one-channel relay-output check before wiring for the first time. →](https://www.ilogics.net/docs/en-us_guide:wiring#relay_output_one_channel_check “en-us_guide:wiring”)
| Terminal | Arduino Pin | Contact ratings | COM configuration | Terminals and Wiring Example |
| — | — | — | — | — |
| P32 | D32 | AC 250V or DC 30V, Max. 5A | Separate COM2 | [MPINO-8A4R-S relay output terminals and load wiring](https://www.ilogics.net/docs/_media/en-us_products:mpino-8a4r-s-relay-output.svg “en-us_products:mpino-8a4r-s-relay-output.svg”) |
| P33 | D33 | AC 250V or DC 30V, Max. 5A | Separate COM3 | |
| P34 | D34 | AC 250V or DC 30V, Max. 5A | Separate COM4 | |
| P35 | D35 | AC 250V or DC 30V, Max. 5A | Separate COM5 |
Each relay point has its own independent COM terminal. Relay contacts are dry contacts that do not supply power; connect a separate load power supply.
### Example program · relay outputs
Map inputs D0~D3 to outputs D32~D35 in order. When an input is HIGH, its corresponding output turns 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));
}
} ```
## Analog / Temperature inputs
| channel | Input Type | Input Range | Connector Pinout |
| — | — | — | — |
| A0, A1 | Voltage input | DC 0~5V | [MPINO-8A4R-S analog/temperature input connector pinout](https://www.ilogics.net/docs/_media/en-us_products:mpino-8a4r-s-analog-input.svg “en-us_products:mpino-8a4r-s-analog-input.svg”) |
| A2, A3 | Current input | 0~20mA or 4~20mA | |
| A4, A5 | Voltage input | DC 0~10V | |
| A6, A7 | NTC temperature sensor inputs | NTC 10kΩ, 3950K |
### Example program · analog inputs
Use analogRead() to display the ADC raw values (0~1023) of A0~A5. Each channel's voltage/current mode follows the actual product settings.
```cpp const uint8_t analogPins[] = {A0, A1, A2, A3, A4, A5};
void setup() {
Serial.begin(115200);
}
void loop() {
for (uint8_t ch = 0; ch < 6; ch++) {
Serial.print(F("A"));
Serial.print(ch);
Serial.print(F(" ADC: "));
Serial.println(analogRead(analogPins[ch]));
}
delay(500);
} ```
Related built-in functions
#### Function example · input range scaling: IanalogRead(), IanalogReadf()
Function prototype
```cpp int32_t IanalogRead(uint8_t ch, int32_t min, int32_t max); float IanalogReadf(uint8_t ch, float min, float max); ```
Both functions scale the ADC input to the specified min~max range. IanalogRead() returns an integer with the fractional part discarded; IanalogReadf() returns a floating-point value. This example displays the 0~5V input on A0 as both 0~100% and voltage. For the 0~20mA input on A2, specify a range of 0~20 to read the actual current.
```cpp void setup() {
Serial.begin(115200);
}
void loop() {
const int32_t percent = IanalogRead(A0, 0, 100); // Scale A0 to an integer from 0~100%
const float voltage = IanalogReadf(A0, 0.0f, 5.0f); // Scale A0 to a floating-point voltage from 0~5V
const float current = IanalogReadf(A2, 0.0f, 20.0f); // Scale A2 to a floating-point current from 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);
} ```
#### Function example · 4~20mA range scaling: IanalogRead2(), IanalogRead2f()
Function prototype
```cpp int32_t IanalogRead2(uint8_t ch, int32_t min, int32_t max); float IanalogRead2f(uint8_t ch, float min, float max); ```
Scale a 4~20mA sensor input connected to A2 to a user-defined range. IanalogRead2(A2, 0, 100) returns an integer with 4mA mapped to 0% and 20mA to 100%. IanalogRead2f(A2, 4.0f, 20.0f) returns the current as a floating-point value. Below approximately 3.6mA, both functions return ANALOG\_READ2\_ERROR(65535); from approximately 3.6~4mA, they return the specified minimum.
```cpp void setup() {
Serial.begin(115200);
}
void loop() {
const int32_t percent = IanalogRead2(A2, 0, 100); // Scale A2 4~20mA to an integer from 0~100%
const float current = IanalogRead2f(A2, 4.0f, 20.0f); // Convert A2 4~20mA to a floating-point current
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);
} ```
#### Function example · moving average: analogReadAvg(), analogRead2Avg()
Function prototype
```cpp int32_t analogReadAvg(uint8_t ch, uint8_t samples); int32_t analogRead2Avg(uint8_t ch, uint8_t samples); ```
analogReadAvg(A0, 5) returns a moving average of the raw A0 ADC readings (0~1023). analogRead2Avg(A2, 5) averages the corrected A2 4~20mA values (4mA=0, 20mA=32767). Each function accumulates up to 5 valid readings from recent calls; it does not take 5 readings in one call. The maximum samples value is 20. analogRead2Avg() returns ANALOG\_READ2\_AVG\_ERROR(-1) for inputs below approximately 3.6mA.
```cpp void setup() {
Serial.begin(115200);
}
void loop() {
const int32_t voltageAvg = analogReadAvg(A0, 5); // Moving average of up to 5 recent A0 ADC values
const int32_t currentAvg = analogRead2Avg(A2, 5); // Moving average of up to 5 recent corrected A2 4~20mA values
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);
} ```
### Example program · temperature sensor inputs
Use analogRead() to display the temperature-sensor ADC raw values (0~1023) of A6, A7. Celsius conversion is explained in the built-in functions below.
```cpp void setup() {
Serial.begin(115200);
}
void loop() {
Serial.print(F("A6 ADC: "));
Serial.println(analogRead(A6));
Serial.print(F("A7 ADC: "));
Serial.println(analogRead(A7));
delay(500);
} ```
Related built-in functions
#### Function example · read NTC temperature: ntcRead(), ntcReadf()
Function prototype
```cpp int ntcRead(uint8_t ch); float ntcReadf(uint8_t ch); ```
Read the temperature of the NTC 10kΩ (25℃), B=3950K sensor connected to NTC input A6. ntcRead() returns an integer equal to 10 times the Celsius temperature (25.3℃ → 253); ntcReadf() returns the Celsius temperature as a floating-point value (25.3). Both have a resolution of 0.1℃. Values outside the conversion range are clamped to -40℃ or 120℃, so do not use these values to detect an open or short circuit.
```cpp void setup() {
Serial.begin(115200);
}
void loop() {
const int temperature10 = ntcRead(A6); // Return A6 temperature as an integer in Celsius ×10
const float temperature = ntcReadf(A6); // Return A6 temperature as floating-point Celsius
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 and High-Speed Inputs
| Function | Arduino Pin | Points | Connector Pinout |
| — | — | — | — |
| PWM outputs | D21, D22, D23 | 3 points | [MPINO-8A4R-S PWM output connector pinout](https://www.ilogics.net/docs/_media/en-us_products:mpino-8a4r-s-pwm-output.svg “en-us_products:mpino-8a4r-s-pwm-output.svg”) |
| High-Speed Inputs / Interrupts | D18, D19, D24, D25 | 4 points | |
| Encoder Inputs | D24, D25 | 2 points |
D18 and D19 share the I²C SCL and SDA functions. These pins cannot be used as high-speed inputs while I²C communication is active.
### Example program · PWM outputs
Set analogWrite() duty value 128 in the 0~255 range on D21, D22, D23 to output approximately 50% PWM.
```cpp void setup() {
analogWrite(21, 128); analogWrite(22, 128); analogWrite(23, 128);
}
void loop() { } ```
Related built-in functions
#### Function example · specify duty value: PWM()
Function prototype
```cpp void PWM(uint8_t pin, uint16_t val, bool onDutybit16 = false); ```
PWM(pin, val, onDutybit16) sets the duty value. The 8-bit range is 0~255; with the third argument true, the 16-bit range is 0~65535. Output the 16-bit midpoint value 32768 on D21. D21~D23 share Timer3; use the same resolution on the same timer. Start after calling PWM\_RESET().
```cpp void setup() {
PWM_RESET(); // Stop PWM timers and reset their modes PWM(21, 32768, true); // Output a midrange 16-bit duty value on D21
}
void loop() { } ```
#### Function example · set frequency/duty: FDPWM()
Function prototype
```cpp void FDPWM(uint8_t pin, int32_t intHz, float Duty); ```
FDPWM(pin, intHz, Duty) specifies the frequency (Hz) and duty cycle (%). Output 1kHz at 50% duty on D21. D21~D23 share Timer3; specify the same frequency for outputs on the same timer. There is no separate FPWM() function; use FDPWM(pin, hz, 50.0f) for 50% duty output.
```cpp void setup() {
PWM_RESET(); // Stop PWM timers and reset their modes FDPWM(21, 1000, 50.0f); // Output 1kHz at 50% duty on D21
}
void loop() { } ```
#### Function example · output a specified pulse count: NPWM\_BEGIN(), NPWM()
Function prototype
```cpp void NPWM_BEGIN(uint8_t pin, uint32_t intHz, float Duty, uint32_t N); void NPWM(uint8_t pin); ```
Use NPWM\_BEGIN() to prepare 100 pulses at 1kHz and 50% duty on D21, then run NPWM() on every loop() iteration to handle output and completion. Do not insert delay(). D21~D23 share Timer3; do not combine this with other outputs on the same timer.
```cpp void setup() {
PWM_RESET(); // Stop PWM timers and reset their modes NPWM_BEGIN(21, 1000, 50.0f, 100); // Prepare 100 pulses at 1kHz, 50% duty on D21
}
void loop() {
NPWM(21); // Process the prepared pulse output and completion
} ```
#### Function example · stop/resume channel output: PWMOFF()
Function prototype
```cpp void PWMOFF(uint8_t pin, bool POff); ```
PWMOFF(pin, true) sets the channel stop flag; false clears it. Call FDPWM() immediately after setting the flag to apply it to the output connection. Turn D21 on for 2 seconds and off for 2 seconds; after stopping, hold LOW with digitalWrite(). This stop flag does not apply to NPWM().
```cpp void setup() {
PWM_RESET(); // Stop PWM timers and reset their modes
}
void loop() {
PWMOFF(21, false); // Clear the stop state of D21 FDPWM(21, 1000, 50.0f); // Apply the cleared flag and start 1kHz, 50% output delay(2000);
PWMOFF(21, true); // Set the stop state of D21
FDPWM(21, 1000, 50.0f); // Apply the stop flag and disconnect PWM output digitalWrite(21, LOW); delay(2000);
} ```
#### Function example · stop/reset PWM timers: PWM\_RESET()
Function prototype
```cpp void PWM_RESET(); ```
PWM\_RESET() resets the Timer1·Timer3 control registers and counters. Output 1kHz on D21 for 2 seconds, then stop all timers. This also affects PWM and counters on the same timer. It does not also clear the PWMOFF() stop flag.
```cpp void setup() {
PWM_RESET(); // Stop PWM timers and reset their modes FDPWM(21, 1000, 50.0f); // Output 1kHz at 50% duty on D21 delay(2000); PWM_RESET(); // Stop and reset all Timer1/Timer3 PWM outputs digitalWrite(21, LOW);
}
void loop() { } ```
### Example program · pulse counting
Count FALLING edges on CLK(D24). The input has a 10kΩ pull-up. This software interrupt example reads the 32-bit value with ATOMIC\_BLOCK and displays it every 500ms. Lossless counting is not guaranteed at the maximum input frequency.
```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); // Count at the selected edge of the input signal 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);
} ```
### Example program · encoder inputs
Connect phase A to CLK(D24) and phase B to DIO(D25). This x1 counting example uses a 10kΩ pull-up and increments when phase B is LOW at a FALLING edge. The actual rotation direction depends on the phase A/B wiring.
```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() {
// Increment if phase B is LOW at the phase A falling edge (x1 decoding)
if (digitalRead(DIO) == LOW) {
if (position < INT32_MAX) position++;
} else {
if (position > INT32_MIN) position--;
}
}
void setup() {
Serial.begin(115200); // Encoder phase A → CLK(D24), phase 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);
} ```
## Communication and Upload
| Function | Arduino Pin | Object | Device Wiring |
| — | — | — | — |
| USB Upload / Monitoring | D16(RX0), D17(TX0) | `Serial` | [MPINO-8A4R-S communication connectors and device wiring](https://www.ilogics.net/docs/_media/en-us_products:mpino-8a4r-s-communication.svg “en-us_products:mpino-8a4r-s-communication.svg”) |
| RS-232 | D28(RX1), D29(TX1) | Serial1 | |
| I²C | D18(SCL), D19(SDA) | `Wire` |
### Example program · basic communication
Forward characters between Serial(115200bps) and RS-232 Serial1(9600bps, 8-N-1). Check the free space in the transmit buffer with availableForWrite().
```cpp // Serial Monitor via MP download cable ↔ RS-232 Serial1 void setup() {
Serial.begin(115200); Serial1.begin(9600); // Also configure the other device for 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());
}
} ```
### Example program · I²C communication
Use Wire to scan addresses 0x08~0x77 and display responding addresses every 3 seconds. Do not run this simultaneously with encoder/interrupt examples that use 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);
} ```
### Example program · Modbus RTU communication
Related built-in functions
#### Function example · Modbus RTU Slave: ImodbusRTUMem(), ImodbusRTUAdr(), ImodbusRTUInit(), ImodbusRTU()
Function prototype
```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(); ```
Store the D0 state in M[0] and the raw A0 reading (0~1023) in D[0] for the other device to read. Configure RS-232(Serial1), station 1, 9600bps, 8N1. Read M[0] with FC01, start address 0, quantity 1 bit; read D[0] with FC03, start address 0, quantity 1 word. Addresses are zero-based on-wire addresses. Call the communication function on every loop() iteration. This example does not control outputs.
M and D are shared global memory provided by the core. Run this sketch on its own. When combining protocols, do not allocate the shared memory more than once.
```cpp unsigned long lastPrint = 0; bool memoryReady = false;
void setup() {
Serial.begin(115200);
ImodbusRTUMem(8, 8); // Allocate 8 M bits and 8 D words
ImodbusRTUAdr(0, 0); // Set the M and D communication start addresses to 0
ImodbusRTUInit(Serial1, 1, 9600); // Start RS-232 at station 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(); // Process incoming Modbus RTU requests
if (millis() - lastPrint >= 1000) {
lastPrint = millis();
Serial.print(F("M[0]: "));
Serial.print(M[0]);
Serial.print(F(", D[0]: "));
Serial.println(D[0]);
}
} ```
#### Function example · Modbus RTU Master: ImodbusRTUmasterInit(), ImodbusRTUmaster()
Function prototype
```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); ```
Read one holding register from the remote slave over RS-232(Serial1) every 1 second. Configure the other device for station 1, 9600bps, 8N1, FC03, and on-wire start address 0. The response is stored in received[0] and displayed only on success. Error codes are displayed in hexadecimal. This is a separate sketch from the slave example above and uses a receive array instead of M/D memory.
```cpp unsigned long lastRequest = 0; uint16_t received[1];
void setup() {
Serial.begin(115200); ImodbusRTUmasterInit(Serial1, 9600, SERIAL_8N1); // Start RS-232 as a Modbus master
}
void loop() {
if (millis() - lastRequest < 1000) return; lastRequest = millis(); const uint8_t result = ImodbusRTUmaster(Serial1, 1, 3, 0, 1, received, 200); // Read 1 word at address 0 from station 1, timeout 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);
}
} ```
### Example program · LS Industrial Systems Cnet communication
Related built-in functions
#### Function example · LS Industrial Systems Cnet SLave: ICnetMem(), ICnetAdr(), ICnetInit(), ICnet()
Function prototype
```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(); ```
Store the D0 state in M[0] and the raw A0 reading (0~1023) in D[0] for an HMI to read. Configure the other device for LS Industrial Systems Cnet, RS-232, station 1, 9600bps, 8N1. Read the bit at %MX00000(M[0]) and the word at %DW0(D[0]). Call the communication function on every loop() iteration. This example does not control outputs.
M, D, and R are shared global memory provided by the core; this example does not use R. Run this separately from the Modbus example. When combining protocols, do not allocate the shared memory more than once.
```cpp unsigned long lastPrint = 0; bool memoryReady = false;
void setup() {
Serial.begin(115200);
ICnetMem(8, 8, 0); // Allocate 8 M bits and 8 D words; R is unused
ICnetAdr(0, 0, 0); // Set the M and D communication start addresses to 0
ICnetInit(Serial1, 1, 9600); // Start RS-232 at station 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(); // Process incoming LS Cnet requests
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 comparison](https://www.ilogics.net/docs/en-us_products:mpino_series “en-us_products:mpino_series”)
[Online product details](https://www.ilogics.net/docs/en-us/products:mpino_8a4r_s)