An Arduino Mega with an Ethernet shield is a clean Modbus TCP slave for a bench rig or pilot cell: analog pin 0 becomes a 16-bit holding register scaled 64:1 from the raw 0 to 1023 counts, and PWM pin 5 takes a 0 to 255 duty register the controller writes. Bring one input and one output up against a WebVisu meter and slider, then copy the pattern to the rest of the range.
An Arduino Mega with an Ethernet shield serves Modbus TCP on port 502, and two 16-bit holding registers carry everything this example needs. Register 0 reports analog pin 0, scaled from the raw 0 to 1023 counts into 16 bits. Register 1 holds the 0 to 255 duty value your controller writes out to PWM pin 5. Bring those two channels up first, prove each one against a WebVisu meter and slider, and only then copy the same pattern across the example's remaining range, analog pins 0 through 7 and PWM pins 5 through 9. The numbers below are bench-checkable end to end: a wiper at 3.30 V shows 43200 in the meter, and a slider at 154 runs pin 5 at 60.4 percent duty.
Register map and the two scales
Modbus holding registers are 16-bit words, and that drives both mappings:
| Holding register | Content | Range in the register | Source |
|---|---|---|---|
| 0 | Analog pin 0, counts scaled × 64 | 0 to 65472 | the Mega |
| 1 | PWM pin 5 duty | 0 to 255 | the controller |
Any consistent map works; the only rule is that the sketch and the WebVisu page agree on it.
The analog scale. The Mega's ADC is 10-bit, so analogRead(0) returns 0 to 1023 across the 0 to 5 V default reference, about 4.9 mV per count. Multiplying by 64 lifts 10 bits into 16: full scale lands at 1023 × 64 = 65472, a shade under the register ceiling of 65535. Divide by 64 in the controller to get counts back.
The PWM scale. analogWrite takes 0 to 255, an 8-bit duty: 128 is about 50 percent, 154 is 60.4 percent. If your page prefers a full-scale slider, scale by 257 instead: 255 × 257 = 65535, so 0 to 255 fills a 16-bit register exactly, and the sketch divides by 257 before the analogWrite call.
What you need
- An Arduino Mega 2560 with an Ethernet shield, powered over USB for the bench.
- The ModbusTCPSlave library, which implements the slave/server side of Modbus TCP and serves holding and input registers. Install it from the Arduino library manager and take the setup calls exactly from its own examples.
- A controller or runtime with a WebVisu page that reads and writes Modbus TCP registers.
- A 10 kΩ potentiometer, one LED, one 220 Ω resistor, and hookup wire.
- An Ethernet cable and a small switch on the controller's subnet.
Steps
-
Give the shield an identity and an address. Use the MAC printed on the shield's sticker; if yours shipped without one, set a locally administered address that is unique on your network. Give the Mega a fixed IP on the controller's subnet, 192.168.1.50 in this example, picked outside the DHCP pool so nothing else claims it. Modbus TCP answers on port 502; keep that port open between controller and Mega.
-
Declare the register array. The sketch serves a two-element array of unsigned 16-bit values, both initialized to 0, and the library answers Modbus TCP requests against it.
-
Scale A0 into register 0. The loop core:
uint16_t reg[2] = {0, 0}; // [0] A0 value, [1] pin 5 duty
void loop() {
word raw = analogRead(0); // documented: analogRead returns 0 to 1023
reg[0] = raw * 64; // scaled: 0 to 65472
analogWrite(5, reg[1]); // documented: analogWrite takes 0 to 255
// ModbusTCPSlave poll goes here; its examples show the exact call
}
Keep the multiply in an unsigned 16-bit variable. The Mega's signed int stops at 32767, so a reading of 675 scaled to 43200 would wrap negative before it reached the register.
-
Apply the duty from register 1.
analogWrite(5, reg[1])on every pass means the pin follows the register within one loop; the value only changes when your controller writes the register. -
Wire the bench. Pot ends to 5 V and GND, wiper to A0. LED anode to pin 5 through 220 Ω, cathode to GND: (5 V - 2 V) / 220 Ω = 13.6 mA, inside the pin's recommended 20 mA rating. An LED fits a pin; a relay coil or a motor does not, and a motor drive in particular should be matched to the motor's current, which is what this check on Arduino Mega motor drives and motor current walks through.
-
Build the proof page. In the WebVisu visualization, bind a meter to register 0 with a scale of 0 to 65535, and a slider to register 1 with a range of 0 to 255 that writes on change. If the file refuses to send after you add the objects, Fix a WebVisu file that will not send after compile walks that exact fault.
-
Prove both paths, then extend. Turn the pot and watch the meter settle; drag the slider and watch the LED. When both hold steady, add channels one at a time, in the order at the end of this page.
Worked example: a 3.30 V pot and a slider at 154
Set the pot so the wiper sits at 3.30 V. The ADC converts 3.30 / 5.00 × 1023 = 675 counts. The sketch writes register 0 as 675 × 64 = 43200. Your meter, scaled 0 to 65535, shows 43200, which is 65.9 percent of full scale (43200 / 65535 = 0.659); the wiper sits at 66.0 percent of supply (3.30 / 5.00 = 0.66). The 0.1 point gap is the ×64 scale topping out at 65472, and it is the same at every reading.
Now drag the slider to 154. Your controller writes 154 into register 1 and the sketch applies it: duty = 154 / 255 = 60.4 percent. A DC voltmeter across the LED and resistor averages the chopping and reads 5.00 × 0.604 = 3.02 V. Meter at 43200, voltmeter at 3.02 V: both paths are checked.
What a wrong reading tells you
- Meter reads 0 and the master times out. The Mega is unreachable: power, cable, switch port, the fixed IP against the controller's subnet, port 502 through anything in the path. The same cause-and-check order on the serial side is in Modbus RTU master no reply and address errors: fix both.
- Meter reads 675, not 43200. The page is showing raw counts, so one side kept the ×64 scale and the other dropped it. Restore the scale on one side at a time so the diagnosis stays simple.
- Meter flickers with the pot untouched. Wiggle the wiper wire: if the meter follows the wiggle, it is the breadboard joint, not the Modbus path.
- Slider moves, LED stays dark. Add a temporary text field bound to register 1: if it follows the slider, the register path is good and the fault sits at the pin wiring, the resistor, or the LED direction.
- Master errors after the Mega was off for rewiring. A TCP connection does not survive a power cut on the slave, and the recovery order belongs on the master side; Make a Modbus master reconnect after a slave outage gives that sequence.
Extending across the example's range
Analog pins 0 through 7 and PWM pins 5 through 9 are the ranges this example supports. The pattern per channel is the same two lines you already proved: one analogRead scaled × 64 into its own register, one register read out through analogWrite. Add one register per channel on both ends at the same time, bring each new channel up against the meter or slider before moving on, and keep the register numbers written down where the next person can find them. The Mega carries more analog inputs and PWM pins than the example maps; if you go past these ranges, the same per-channel discipline applies, and each channel still costs exactly one register.
If these values also belong on a shop-floor notice board next to the visualization, the signage-versus-dashboard decision in Live factory data display: signage or dashboard? is the next read.
Recommendation: bring up analog pin 0 and PWM pin 5 alone, prove both against the meter and the slider, and only then copy the two-line pattern across analog 0 to 7 and PWM 5 to 9. One channel at a time with one register per channel turns a fault into a single wrong object; bringing up all eight at once turns it into a hunt.
Questions
Which Modbus function codes does this setup use?
Read Holding Registers, function 03, is what the meter uses on register 0. The controller writes the duty register with Write Single Register, function 06. If you map several PWM pins, check the library's examples for Write Multiple Registers, function 16, support before pointing the page at a block write. Coils and discrete inputs stay unused here; every value in this example moves through holding registers as a plain 16-bit number.
Can I debug the sketch over USB while it serves Modbus TCP?
Yes. The USB serial monitor and the Ethernet hardware are independent, so the sketch can print the raw counts and the applied duty while the slave answers on port 502. Keep the print rate low, once a second is plenty: a print inside the loop's register path slows each pass and shows up as meter stutter.
What changes when the controller is on another subnet?
Modbus TCP is plain TCP/IP, so a Mega on another subnet needs its netmask and gateway set alongside the fixed IP in the sketch, and port 502 must pass through whatever sits between the two networks. A master that times out while the sketch still runs, with the cable and switch checked, usually points at exactly this.
Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.