Fix CODESYS showing 0 for Modbus TCP analog values

5 min read · Published November 5, 2026

When a Modbus TCP test client reads the analog value and CODESYS still shows 0, the link is fine: the mapped variables are unused and Always update variables is off, so they sit gray in the I/O mapping tab. Turn the option on or read the variables in the program, and republish each analogRead result in its own 16-bit register instead of packing six readings into six bits of one word.

When a Modbus TCP test client reads the analog value from the Arduino slave and CODESYS still shows 0, the cable and the slave are doing their job. The fault sits in two places, and neither is the network. First, CODESYS leaves a mapped input variable at 0 when the program never reads it and the Always update variables option is left at its default of off; those variables show gray in the I/O mapping tab. Second, the sketch packs six analogRead results into six bits of one register, so a 10-bit measurement cannot survive the trip. Turn on the update option in CODESYS, then give each analog channel its own 16-bit register. This article gives the symptoms, both fixes, a worked example with the numbers, and the three checks that verify the path end to end.

What each symptom points to

Read the symptoms in order and the fault locates itself:

  • Test client shows the analog value, CODESYS shows 0. The slave and the network work. The mapped variables in CODESYS are the part that is not updating.
  • The values appear gray in the I/O mapping tab. The program never reads them and Always update variables is off. Gray is the marker for unused mapped variables.
  • Test client and CODESYS both show 0. The register itself is empty. Work on the sketch side.
  • Digital inputs and outputs behave, analog does not. Expected here. Outputs travel from CODESYS to the Arduino, so they moved all along; the analog inputs are the first values that must arrive the other way into variables the program reads.
  • The register reads only 0 or 1 and never tracks the input. The sketch is writing single bits, not counts.

If the slave side itself is still to be built, the article on an Arduino Mega as a Modbus TCP slave with analog and PWM I/O covers that setup from the wiring up.

Turn on Always update variables

CODESYS refreshes a mapped input variable when the program uses it. When nothing in the program reads it, the Always update variables option forces the refresh. Left off, the default, an unused input stays gray at 0 even when the test client shows a live value in the same register.

  1. Double-click the Modbus TCP slave device in the device tree and open its I/O mapping tab.
  2. Find the analog input variables that stay at 0. Gray text marks them.
  3. Tick Always update variables for this device mapping.
  4. Alternative that needs no option: read each mapped variable somewhere in the program. A used variable updates on its own each scan.
  5. Download, run, and compare the mapped variable against the test client reading of the same register.

That single change moves a gray 0 to a live value.

One register per analog channel

A Modbus register is 16 bits, so one word holds 0-65535. analogRead returns 0-1023 counts on a 10-bit converter with a 5 V reference, per the analogRead language reference. A count fits a word easily. It never fits a bit.

Six bitWrite calls into bits 0 through 5 of Mb.MbData[2] turn each reading into a 0 or 1: bitWrite sets a single bit, and any nonzero count sets it to 1, so an input of 512 counts lands in the register as 1. Publish each channel to its own word:

// one raw reading per word, counts 0-1023
Mb.MbData[2] = analogRead(A0);

Repeat for every channel in its own free Mb.MbData word, and map the CODESYS input variables to the same word offsets.

The sketch also carries a scale bug. The line analogRead(AN0 * 0.48875) applies the factor to the pin argument instead of the measurement, so the sketch samples a different channel and the factor never touches the value. Read first, scale after:

Mb.MbData[2] = analogRead(A0) * 0.48875; // hundredths of a volt

The register word is an integer, so the assignment keeps the whole part: 512 counts x 0.48875 = 250.24, stored as 250, which reads back as 2.50 V. To carry the decimal into CODESYS, send the value as a REAL, and the article on converting a CODESYS REAL into two Modbus WORD registers shows the two-register packing.

Worked example: A0 at 2.5 V arriving as 512 counts

Inputs. The analog reference is 5.0 V. A0 sits at half supply through a divider, so the converter reports 512 counts out of the 0-1023 range. The scale is 5.0 V / 1023 counts = 0.00489 V per count. The reference voltage and the 10-bit range come from the analogRead documentation; the 512 is the bench case.

Rule. The register carries raw counts. Volts = counts x full-scale volts / top count.

Arithmetic. The fixed sketch stores 512 in Mb.MbData[2]. The test client reads the register and sees 512. CODESYS, with the mapping updating, receives the same 512 and the scaling converts it: 512 x 5.0 / 1023 = 2560 / 1023 = 2.5024 V, which rounds to 2.50 V. With the hundredths-of-a-volt scale instead: 512 x 0.48875 = 250.24, stored as 250, and 250 hundredths = 2.50 V. Both roads agree.

Result. Serial print, test client and mapped variable all read 512 raw or 2.50 V scaled.

Decision. Sweep A0 from 2.0 V to 3.0 V. A correct path reads 409 to 614 counts, from counts = volts x 1023 / 5.0: 2.0 x 1023 / 5.0 = 409 and 3.0 x 1023 / 5.0 = 614. A word stuck at 0 or 1 across the sweep means the sketch still packs bits. A register at 512 with a gray mapped variable at 0 means Always update variables is still off.

Check the path in three places

Three readings must move together:

  1. Serial. Send 0 to the sketch over the serial monitor at 9600 baud; the sketch prints its Mb.MbData words one by one. The analog word must track the input.
  2. Test client. Read the same word offset with a Modbus TCP client using function code 0x03, the read-holding-register request and the usual call for analog words. The value must match the serial print.
  3. CODESYS. Go online and watch the mapped variable. It must match both.

Serial and client agree while CODESYS reads 0: the update option or the word offsets in the mapping are wrong. Client also reads 0: the sketch still writes bits or samples the wrong channel. If the test client cannot reach the slave at all, that is a connection and addressing fault, and the Modbus RTU master troubleshooting article covers the equivalent diagnosis for no-reply and address errors.

Recommendation: enable Always update variables for the Modbus TCP slave's I/O mapping and publish every analog channel to its own Mb.MbData word as raw counts. Scale in one place only: counts x 5.0 / 1023 in CODESYS, or analogRead(A0) x 0.48875 in the sketch for hundredths of a volt. Keep bitWrite for genuine on/off signals. Sign off only when serial print, test client and mapped variable read the same number across a 2.0 to 3.0 V sweep, 409 to 614 counts.

Questions

Why do the digital inputs and outputs work while the analog values stay at 0?

Outputs travel the other way: CODESYS writes them into the slave's registers, so they move even with the update option off. That is how a board can drive outputs correctly while every input reads 0. Digital inputs follow the same rule as analog ones: a mapped input the program never reads sits gray at 0 until Always update variables is enabled or the variable is used in the program.

Should I divide by 1023 or 1024 when scaling a 10-bit analogRead value?

Use counts x 5.0 / 1023 = 0.00489 V per count, because the readable range ends at 1023. The analogRead reference quotes 5 V / 1024 units, 0.0049 V per step. The two figures differ by about 0.1 percent, well below converter noise; pick one and use it in the sketch, the test client and CODESYS alike.

What does the 0.48875 factor in the original sketch multiply?

As written, the pin: A0 is pin 14 on an Uno-class board, so 14 x 0.48875 = 6.84, and an integer pin argument truncates, so the sketch samples channel 6. Applied to the result, counts x 0.48875 is hundredths of a volt on a 5 V scale: 0.48875 is about 500 / 1023 = 0.48876, and 512 counts store as 250, which reads back as 2.50 V.

Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.

Keep reading

Build your signAsk for a quote