XStore

ESP32 Industrial Weather Monitoring System | NORVI X-AV4

Introduction #

Many industrial transmitters report humidity, wind, pressure, level or position as a 0–10 V signal that is proportional to the measured value. Reading such a signal well takes more than a wire: the voltage has to be brought into the ADC’s range, converted to engineering units with the right scaling, and filtered so the display stays steady. Angle signals such as wind direction also wrap around, which needs special care.

This tutorial builds an ESP32 industrial weather monitoring system on the NORVI X-CPU-ESPS3-X1 with the NORVI X-AV4 module. You connect three 0–10 V transmitters (a Sentera DSTHF-3 humidity transmitter, a WG2/O-10 wind speed transmitter and a WRG/O-40 wind direction transmitter), scale each one to %RH, m/s and degrees, average eight readings per update (with a circular average for direction), and show the values, gauges and a compass dial on the built-in TFT display. The same method suits any 0–10 V transmitter: pressure, level, damper or valve position, or a linear position sensor. Only the span constants change.

What you will need

  • NORVI X-CPU-ESPS3-X1 controller and NORVI X-AV4 module
  • 24 V DC supply
  • 0–10 V transmitters: humidity (Sentera DSTHF-3), wind speed (WG2/O-10) and wind direction (WRG/O-40), or equivalents with a 0–10 V output
  • Shielded twisted-pair cable, an adjustable 0–10 V source and a multimeter for commissioning
  • USB cable and the Arduino IDE with ESP32 board support

System Architecture #

The system forms a short signal chain from the field transmitters to the operator display.

Figure 1: Signal path of the ESP32 0–10 V weather monitoring system

Signal flow #

  • Sensing. Each transmitter outputs 0–10 V in proportion to its measuring range: 0–100 %RH, 0–50 m/s and 0–360°.
  • Conversion. The X-AV4 divides each input down so it fits the ADS1115 (3.3 V supply, 16-bit). The sketch runs the ADC at GAIN_ONE (±4.096 V, 125 µV per count).
  • Processing. The ESP32-S3 reads three channels over I²C, averages eight readings each, undoes the divider ratio, scales the result to engineering units and checks for faults.
  • Indication. The controller draws wind speed, a compass dial and humidity into an off-screen buffer and pushes it to the TFT twice a second.

Hardware components #

ComponentRoleKey details
NORVI X-CPU-ESPS3-X1ControllerESP32-S3-WROOM-1U-N16R2 with 16 MB flash and 2 MB QSPI PSRAM; 2.0 in ST7789 TFT (240 × 320); 24 V DC supply
NORVI X-AV40–10 V input module4 analog channels; ADS1115 16-bit ADC (3.3 V) at I²C address 0x49 with DIP 1 ON; powered through the expansion port (40 mA at 24 V)
Sentera DSTHF-3Humidity transmitterDuct transmitter, 0–100 %rH (non-condensing), ±3 %rH; 18–34 V DC, 80 mA; humidity on output AO2 (0–10 V)
WG2/O-10Wind speed transmitter0–50 m/s = 0–10 V; ±0.5 m/s or ±3 % of reading; 13–30 V DC or 24 V AC/DC, 50 mA
WRG/O-40Wind direction transmitter0–360° = 0–10 V; ±1.5°, 1° resolution; 15–28 V DC or 24 V AC

Pin allocation #

FunctionGPIO / channelNotes
I²C bus (SDA / SCL)IO8 / IO9Shared by the ADS1115 on the X-AV4 (0x49 here) and the on-board touch, I/O expander and RTC devices
TFT display (CS / DC / RST)IO45 / IO46 / IO47ST7789 panel on the SPI bus
Shared SPI (MOSI / MISO / SCLK)IO11 / IO13 / IO12Display, Ethernet controller and SD card share these lines
Humidity inputAIN1 (ADS1115 ch 0)readADC_SingleEnded(0)
Wind speed inputAIN2 (ADS1115 ch 1)readADC_SingleEnded(1)
Wind direction inputAIN3 (ADS1115 ch 2)readADC_SingleEnded(2)
Spare inputAIN4 (ADS1115 ch 3)Free for a fourth 0–10 V transmitter
Common returnAGNDShared analog ground for all inputs

Hardware and Wiring #

X-AV4 at a glance #

ParameterValue
Inputs4 channels, 0–10 V, AIN1 to AIN4 with a common return AGND
ADCADS1115, 16-bit, I²C (SDA IO8, SCL IO9), 3.3 V supply
I²C addressSet by DIP switch: DIP 1 ON = 0x49 (used here)
TerminalsAIN1 to AIN4 and AGND (push-in)
Supply24 V DC through the expansion port, 40 mA
Environment−40 to +85 °C

Connect the module and the sensors #

  1. Power off the system.
  2. Mount the X-AV4 on the DIN rail beside the X-CPU and seat it on the expansion bus connector.
  3. Set DIP 1 ON and DIP 2 to 4 OFF for address 0x49.
  4. Wire each transmitter’s signal output to its input terminal as in the table below, and tie the sensor 0 V to AGND as in Figure 2.
  5. Connect the 24 V DC supply to the X-CPU power terminals and to the supply inputs of the three transmitters, with the supply 0 V joined to AGND, then power on.
TerminalTransmitterSignal usedScaling
AIN1Sentera DSTHF-3AO2 (relative humidity)0–10 V = 0–100 %RH
AIN2WG2/O-10Wind speed output0–10 V = 0–50 m/s
AIN3WRG/O-40Wind direction output0–10 V = 0–360°
AIN4Not used——
AGNDAll sensor 0 V and supply 0 VCommon return—
Figure 2: Wiring the transmitters to AIN1 to AIN3 and the common 0 V (AGND)
Figure 2: Wiring the transmitters to AIN1 to AIN3 and the common 0 V (AGND)

Sensor wiring practice #

Use shielded twisted-pair cable for the signal wires and connect the shield at one end only. Route the cables away from motor, VFD, AC and relay wiring. Follow the pin table supplied with each transmitter: Figure 2 shows the signal functions.

  • Supply. Connect 24 V DC to the supply input of each transmitter in parallel, as in Figure 2.
  • Signals. Wire the humidity output (AO2) to AIN1, the wind speed output to AIN2 and the wind direction output to AIN3, with each transmitter’s GND going to AGND.
  • Common ground. Join the supply 0 V to AGND once, at the X-AV4.
  • DSTHF-3. Wire the supply GND to the supply 0 V and the GND beside AO2 to AGND, using separate wires.

How a 0–10 V Signal Is Read #

The ADS1115 runs from 3.3 V and cannot accept 10 V directly, so the X-AV4 divides each input down first. The sketch sets the ADC to GAIN_ONE (±4.096 V, 125 µV per count), reads the divided voltage and undoes the divider in software:

V_terminal = V_adc ÷ DIVIDER (DIVIDER = 0.3125)

DIVIDER comes from the resistor divider on each X-AV4 input: a 3.3 kΩ series resistor and a 1.5 kΩ resistor to AGND, so DIVIDER = 1.5 ÷ (3.3 + 1.5) = 0.3125. With this ratio a full-scale 10 V signal reaches the ADC as 3.125 V. Use the values below to check the chain during commissioning.

TerminalADC inputExpected countHumidityWind speedDirection
0.0 V0.000 V00 %RH0 m/s0°
2.5 V0.781 V625025 %RH12.5 m/s90°
5.0 V1.563 V1250050 %RH25 m/s180°
7.5 V2.344 V1875075 %RH37.5 m/s270°
10.0 V3.125 V25000100 %RH50 m/s360°
10.5 V (limit)3.281 V26250FAULTFAULTFAULT

Ideal values for DIVIDER = 0.3125, GAIN_ONE and spans of 100 %RH, 50 m/s and 360°. Real modules vary with resistor tolerance (see Section 5.3).

  • Why GAIN_ONE. It covers the divided signal with headroom above 10 V (up to 13.1 V at the terminal), so an over-range input can be detected, and it keeps the resolution at 125 µV per count. The X-AV4 user guide example uses GAIN_TWOTHIRDS (±6.144 V), which also works at 187.5 µV per count.
  • Resolution. One count is 0.4 mV at the terminal (125 µV ÷ 0.3125): 0.004 %RH, 0.002 m/s or 0.014°. That is far finer than the transmitters themselves (±3 %rH, ±0.5 m/s or ±3 %, ±1.5°), so the sensor, not the ADC, limits the accuracy.
  • Negative and over-range readings. The sketch clamps negative voltages to 0 V and treats anything above 10.5 V as a fault (Section 6.2).

Scaling to Engineering Units #

Linear scaling #

A 0–10 V transmitter is linear, so one formula converts the terminal voltage to the measured value. For a span that starts at zero it is a simple ratio; for any span it is:

value = min + (V ÷ 10) × (max − min)

Figure 3: Terminal voltage to engineering units, with one worked point per channel
Figure 3: Terminal voltage to engineering units, with one worked point per channel
  • Humidity. 5.85 V ÷ 10 × 100 = 58.5 %RH. The ADC sees 1.828 V, a count of 14625.
  • Wind speed. 3.42 V ÷ 10 × 50 = 17.1 m/s. The ADC sees 1.069 V, a count of 8550.
  • Wind direction. 6.20 V ÷ 10 × 360 = 223.2°. The ADC sees 1.938 V, a count of 15500. The compass name comes from 16 sectors of 22.5°, so 223.2° falls in SW.

Position transmitters and other spans #

The wind vane is an angular position transmitter, and the same method serves any 0–10 V position transmitter: change the span constant to 100 mm for a linear transducer, or to 100 % for a damper or valve position. If a transmitter uses an offset range such as 2–10 V, subtract the offset in the formula: min + (V − 2) ÷ 8 × (max − min). Check which output range your transmitter is set to before you scale it. For a 0–360° signal, 0° and 360° are the same physical position, so averaging needs the circular method in Section 6.1.

Calibrate the divider ratio #

DIVIDER = 0.3125 follows from the 3.3 kΩ and 1.5 kΩ resistors on each X-AV4 input. Resistor tolerance can move the true ratio slightly, so check it once with a known voltage.

  1. Disconnect the transmitter from one channel (for example AIN2) and apply a known voltage between that input and AGND, using a calibrator or an adjustable supply measured with a multimeter. Try 5.00 V and 10.00 V and never go above 10 V.
  2. Convert the displayed value back to volts. For wind speed, volts = m/s ÷ 50 × 10.
  3. Set the new ratio: DIVIDER (new) = DIVIDER (old) × displayed volts ÷ applied volts.

For example, if 10.00 V is applied and the display shows 48.5 m/s (9.70 V), the new ratio is 0.3125 × 9.70 ÷ 10.00 = 0.3031. If the error is not proportional (different percentages at 5 V and 10 V), look for a wiring or ground problem first.

Filtering and Fault Detection #

Averaging and the circular mean #

On every update the sketch reads each channel eight times and averages the readings. Random pickup noise falls by about √8, roughly 2.8 times. Each reading is a single-shot conversion at the library’s default 128 samples per second (about 8 ms), so one update takes roughly 0.2 s, and the screen is refreshed every 500 ms. Raise SAMPLES for a steadier display; each extra sample adds about 25 ms for the three channels.

Wind direction needs a circular average. The output jumps from 10 V to 0 V when the vane crosses north. A plain average of two readings taken either side of north, 359° (9.972 V) and 1° (0.028 V), gives 5.0 V, which is 180° and points the wrong way. The sketch instead turns each reading into an angle, sums the cosines and sines, and takes the angle of the result, which gives 0° for this pair:

const float a = d / 10.0f * 2.0f * (float)M_PI;   // volts -> radians
sx += cosf(a);  sy += sinf(a);                     // inside the 8-sample loop
...
float deg = atan2f(sy, sx) * 180.0f / (float)M_PI;
if (deg < 0) deg += 360.0f;

Optional extra smoothing. The sketch has no filter beyond the eight-sample average. For a slower, calmer display add an exponential filter, x = x + alpha × (new − x), with alpha between 0.1 (slow) and 0.5 (quick). For direction, filter the sine and cosine sums, never the angle itself. Weather reporting commonly uses longer averages, for example 10 minutes, so add a slower filter if you need that.

Fault detection #

ConditionMeaningFirmware action
Voltage above 10.5 V on a channelOut-of-range output, wrong sensor variant or wiring, or supply voltage on the signal lineThat channel shows FAULT and “check wiring”; header LED turns red
No answer from the ADS1115X-AV4 not seated, DIP address does not match ADS_ADDR, or an I²C faultAll channels show “—” and “no X-AV4”; red LED; retries every 500 ms
0 V on a channelA valid zero, or an open signal wire or unpowered transmitter (a 0–10 V signal has no live zero)Not detected: shown as 0
0 to 10.5 VValid measurementScale to engineering units and display

Because 0 V is also a valid reading, a broken wire on the wind speed input looks like calm air. For humidity, 0 %RH is unrealistic, so you can add a low limit (for example below 1 %RH) that raises a fault, and you should always confirm the wind sensors by turning them by hand during commissioning.

Reading the screen #

  • Header LED blinks green on every update while all channels are healthy and turns red on any fault.
  • Wind speed shows the value in m/s and a 20-segment gauge over 0–50 m/s: green below 50 % of range (25 m/s), amber from 50 to 80 %, and red from 80 % (40 m/s).
  • Wind direction shows a compass dial with a red N, an amber needle, the 16-point name and the angle in degrees.
  • Humidity shows the value in %RH and a teal gauge over 0–100 %RH.
  • A faulty channel shows FAULT or “—” in red, with its gauge empty.

Arduino Code for the Weather Monitoring System #

Set up the IDE #

  • Select ESP32S3 Dev Module, then set USB CDC On Boot to Enabled, Flash Size to 16MB (128Mb) and PSRAM to QSPI PSRAM.
  • Install Adafruit ADS1X15 (with its BusIO dependency) and LovyanGFX from the Library Manager. Wire, SPI and math come with the ESP32 core.

How the sketch works #

  • Non-blocking timing. The loop returns at once until 500 ms have passed since the last update, so nothing stalls the controller.
  • Off-screen sprite. Each frame is drawn into a 240 × 320 buffer in PSRAM and pushed to the TFT in one go, which removes flicker. If the buffer cannot be allocated the sketch prints a message and shows “Display buffer failed”.
  • Hot recovery. Every update checks that the ADS1115 still answers. If the X-AV4 drops off the bus the screen shows the offline state, and the ADC is restarted automatically when it answers again.

What the main parts of the code do #

The complete sketch is available on GitHub: https://github.com/NORVIControllers/NORVI-X-Version-02/tree/main/Application%20Guides/weather-monitoring-system-on-x-av4 (also listed under Resources). The settings at the top are the values you change to suit your sensors:

ConstantDefaultChange it when
ADS_ADDR0x49The X-AV4 DIP switches are set to another address
DIVIDER0.3125Calibration shows a different divider ratio (Section 5.3)
HUM_MAX_PCT100The humidity output is ranged to something other than 0–100 %RH at 10 V
WIND_MAX_MS50The wind speed transmitter has a different full scale (the WG2/O is 0–50 m/s)
OVER_RANGE_V10.5You want more or less headroom before a channel is called faulty
SAMPLES8You want a steadier display (more samples) or a faster update (fewer)
UPDATE_MS500You want a faster or slower screen refresh
  • Reading a channel. Takes one ADS1115 reading, converts it to volts and divides by DIVIDER to give terminal volts, clamped at 0 V.
  • Averaging. Reads all three channels SAMPLES times, averages humidity and wind speed as plain voltages, and averages direction as a vector.
  • Scaling. Turns each averaged voltage into %RH, m/s and degrees with V ÷ 10 × span, and names the compass point from 16 sectors of 22.5°.
  • Fault check. Marks a channel faulty above OVER_RANGE_V, and marks all channels offline when the ADS1115 does not answer.
  • Drawing the screen. Draws the header, three panels, the gauges and the compass dial off-screen, then sends the finished picture to the display in one step.
  • Start-up. Starts I²C on IO8 and IO9, starts the display on the shared SPI bus, creates the PSRAM buffer and starts the ADC.

The scaling lines inside the update loop are:

const float wind = vWind / 10.0f * WIND_MAX_MS;   // 0-10 V -> 0-50 m/s
const float hum  = vHum  / 10.0f * HUM_MAX_PCT;   // 0-10 V -> 0-100 %RH

Commissioning and Test #

Commissioning checklist #

  • Power supply. Verify that the 24 V DC supply is stable and correctly wired to the X-CPU and to the three transmitters before you energize the system.
  • I²C address. Confirm DIP 1 is ON and the Serial Monitor shows no “X-AV4 not found” message. For a full check, run the I²C scan below.
  • Voltage injection. With the transmitters disconnected, apply 0, 2.5, 5, 7.5 and 10 V between each input and AGND. The display should match the table in Section 4. If it is off by a constant percentage, calibrate the divider (Section 5.3).
  • Wind direction. Reconnect the transmitter and turn the vane to each compass point, checking N, E, S and W against a compass. Align the sensor’s north mark as its installation instructions describe, since the sketch has no direction offset.
  • Wind speed. Spin the cups by hand. The value should rise and fall back to about 0 m/s when they stop.
  • Humidity. Measure the voltage at AIN1: it should equal %RH ÷ 10 (58.5 %RH is 5.85 V). Compare with a reference hygrometer in the same air, and exhale near the probe to see a quick rise. If the reading is low at high humidity, see the load note in Section 3.3.
  • Fault response. Power off, remove the X-AV4 and power on: all channels should show “—” and “no X-AV4” with a red LED. Power off again, reseat the module and power on to confirm normal operation. Do not connect or disconnect the module while powered.

To scan the I²C bus, call this function from setup() after Wire.begin(). The scan lists 0x49 (X-AV4), 0x41 (PCA9536), 0x15 (touch controller), 0x68 (RTC) and 0x75 (I/O expander). It is not part of the weather sketch, so add it while commissioning.

void i2cScan() {
  for (byte a = 1; a < 127; a++) {
    Wire.beginTransmission(a);
    if (Wire.endTransmission() == 0) Serial.printf("  found 0x%02X\n", a);
  }
}

Expected output #

The Serial Monitor prints one line every 500 ms, for example wind=17.1 m/s dir=223 deg SW hum=58.5 %. If the X-AV4 does not answer it prints X-AV4 not found. Figure 4 shows four display states, and Figure 5 shows the finished device running with live readings.

Figure 4: Normal, strong wind, channel fault and X-AV4 offline screens (simulated preview with example values)
Figure 4: Normal, strong wind, channel fault and X-AV4 offline screens (simulated preview with example values)
Figure 5: The finished weather display on the NORVI X-CPU-ESPS3-X1 with the X-AV4 module, showing live wind speed, wind direction and humidity
Figure 5: The finished weather display on the NORVI X-CPU-ESPS3-X1 with the X-AV4 module, showing live wind speed, wind direction and humidity

Troubleshooting #

SymptomLikely causeRemedy
Display blank or whiteWrong SPI pins, display bus not initialized, buffer not allocated, or Tools settingsConfirm CS 45, DC 46 and RST 47 in the LGFX class. Set USB CDC On Boot, Flash Size and PSRAM as in 7.1.
Display flickers or shows noiseEthernet or SD chip-select lines floating on the shared SPI busIn setup(), before tft.init(), set pinMode(1, OUTPUT) and pinMode(42, OUTPUT) and drive both HIGH.
“—” and “no X-AV4” on all channelsModule not seated, or DIP address does not match ADS_ADDRPower off and reseat the X-AV4. Set DIP 1 ON for 0x49 and run the I²C scan.
FAULT on one channelMore than 10.5 V on that input: wrong sensor variant, supply voltage on the signal wire, or a faulty transmitterPower off and check the wiring. Confirm a 0–10 V variant (-10 or -40) and measure the terminal voltage.
Reading stuck at zeroSignal wire open, transmitter unpowered, or sensor 0 V not tied to AGNDCheck the supply at the transmitter and the voltage between AIN and AGND.
All values wrong by the same percentageDIVIDER differs from your moduleCalibrate with a known voltage (Section 5.3).
Humidity low at high humidityOutput loaded by the X-AV4 input, or wrong range or output modeCheck 0–10 V mode and the 0–100 %RH range. Add a 0–10 V buffer if needed (Section 3.3).
Noisy or erratic valuesCable pickup, unshielded cable or a ground loopUse shielded twisted pair, ground the shield at one end, keep away from motors and relays, and raise SAMPLES.
Direction wrong by a fixed angleNorth mark of the vane not alignedRotate the mounting, or add an offset to the angle in the sketch.
Reset or sag when heaters switch onSupply too small for the sensor heatersSize the supply for up to 20 W per heater, or use a separate heater supply.

Safety. Disconnect all power before wiring, and let a qualified electrician install the system. This display provides indication only. Never use it as a safety function.

FAQ #

Can an ESP32 read a 0–10 V signal directly?

No. The ESP32 inputs only accept up to about 3.3 V and its own ADC is noisy and non-linear. The X-AV4 divides the signal down and a 16-bit ADS1115 reads it cleanly over I²C.

Why does the sketch average direction as a vector?

Direction wraps from 360° back to 0°, so a plain average of readings on either side of north points the wrong way (Section 6.1). Summing sines and cosines avoids that.

How do I change the range, for example 0–50 m/s?

Set WIND_MAX_MS to the transmitter’s full scale at 10 V. For another humidity range set HUM_MAX_PCT, and make sure the transmitter itself is set to the same range.

Can I read a fourth transmitter?

Yes. Wire it to AIN4, read it with readVolts(3) and apply your own span. Two X-AV4 modules on one bus need different DIP settings and a separate ADS1115 object each.

Why 0–10 V and not 4–20 mA?

A 0–10 V signal is simple and cheap, but it is more sensitive to cable length and noise and has no live zero to reveal a broken wire. For long runs choose a 4–20 mA transmitter and the X-AI4 module.

Conclusion #

You now have a working ESP32 weather display that reads humidity, wind speed and wind direction as 0–10 V signals on the X-AV4, scales them to %RH, m/s and degrees, filters them with an averaged and circular-mean read, and flags faults on the X-CPU display. To go further, publish the readings over MQTT or Modbus, log them to the SD card, add 10-minute averages and gust values, or drive a relay output when wind speed passes a limit.

Resources

NORVI X-CPU-ESPS3-X1 user guide | NORVI X-CPU-ESPS3-X1 datasheet

NORVI X-AV4 datasheet | Code on GitHub: Weather monitoring system on X-AV4

Sentera DSTHX-3 datasheet | WG2/O and WRG/O datasheets (FuehlerSysteme)