STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
ValvePosition.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Motorized ball valve position: raw ADC counts -> % open.
5 *
6 * The valve reports its position as a 4-20 mA loop (4 mA closed, 20 mA open) across a 100 ohm
7 * shunt into the ball valve board's 12-bit ESP32 ADC (firmware/Hotfire_Code/BallValve_Hotfire,
8 * analogRead at 11 dB, 3.3 V full scale), so 0.4-2.0 V on the pin. The board sends raw counts and
9 * the conversion happens here, like every other board.
10 */
11
12#include <algorithm>
13#include <cmath>
14#include <cstdint>
15
16namespace fsw {
17namespace calibration {
18namespace valve_position {
19
20constexpr double kAdcMaxCounts = 4095.0; // 12-bit
21constexpr double kAdcRefVolts = 3.3;
22constexpr double kShuntOhms = 100.0;
23constexpr double kLoopMinMa = 4.0; // closed
24constexpr double kLoopMaxMa = 20.0; // open
25/**
26 * Below this the loop is open or unpowered: no position, not "fully closed". Anything from here
27 * up to 4 mA is a closed valve, rounded to 0 % open.
28 *
29 * Not 3.5 mA (or NAMUR's 3.6): the ESP32's ADC1 at 11 dB, read raw as counts / 4095 * 3.3 V,
30 * reads about 0.1 V low at this end, about 1 mA of loop current. On 2026-11-05 the Ox Vent valve,
31 * physically closed, read a steady 353-356 counts = 2.86 mA by this formula, and was shown as
32 * NO SIGNAL; the same valve read 0 counts while its loop was unpowered. 2 mA sits clear of both.
33 */
34constexpr double kNoSignalMa = 2.0;
35
36/** cal_status values published beside the position. */
37constexpr uint8_t kStatusOk = 1;
38constexpr uint8_t kStatusNoSignal = 2;
39
40inline double counts_to_ma(uint32_t counts) {
41 return static_cast<double>(counts) / kAdcMaxCounts * kAdcRefVolts / kShuntOhms * 1000.0;
42}
43
44/** % open, clamped to 0-100 (a loop a little outside 4-20 mA is still the end of travel). */
45inline double ma_to_percent_open(double ma) {
46 if (!std::isfinite(ma))
47 return 0.0;
48 const double pct = (ma - kLoopMinMa) / (kLoopMaxMa - kLoopMinMa) * 100.0;
49 return std::clamp(pct, 0.0, 100.0);
50}
51
52struct Reading {
54 uint8_t status; // kStatusOk or kStatusNoSignal
55};
56
57inline Reading from_counts(uint32_t counts) {
58 const double ma = counts_to_ma(counts);
59 if (ma < kNoSignalMa)
60 return {0.0f, kStatusNoSignal};
61 return {static_cast<float>(ma_to_percent_open(ma)), kStatusOk};
62}
63
64} // namespace valve_position
65} // namespace calibration
66} // namespace fsw
double ma_to_percent_open(double ma)
% open, clamped to 0-100 (a loop a little outside 4-20 mA is still the end of travel).
double counts_to_ma(uint32_t counts)
constexpr double kNoSignalMa
Below this the loop is open or unpowered: no position, not "fully closed".
Reading from_counts(uint32_t counts)
constexpr uint8_t kStatusOk
cal_status values published beside the position.
Output smoothing for calibrated PT pressure, per sensor uid (ESP32 and LabJack T7 alike).
Motorized ball valve position: raw ADC counts -> % open.