When programming microcontrollers without a hardware Floating-Point Unit (FPU)—like the classic ATmega328P on an Arduino Uno, or low-power Cortex-M0 cores—relying on float or double variables for sensor scaling introduces severe performance penalties and code bloat. To map a raw Analog-to-Digital Converter (ADC) reading to a real-world physical value efficiently, you must act as an integer calculator step by step through a linear interpolation formula. By strictly ordering your multiplication and division operations, you avoid both truncation-to-zero errors and 16-bit register overflows.

The direct answer for mapping any linear sensor using only integer math is the scaled linear interpolation equation. You multiply the delta of your raw input by your target physical span before dividing by the raw input span, ensuring intermediate values retain their precision prior to the final integer truncation.

The Core Integer Scaling Formula

The foundation of deterministic embedded sensor math is the integer linear scaling equation. This formula translates a dimensionless ADC count into a meaningful engineering unit (millivolts, tenths of a degree, or PSI) without ever invoking a decimal point.

Formula:
Y = Y_min + ((X - X_min) * (Y_max - Y_min)) / (X_max - X_min)

Symbol Definition and Data Types
Symbol Description Typical Embedded Type Example Value
X Raw ADC reading (current sample) uint16_t 612
X_min Raw ADC reading at physical minimum uint16_t 204
X_max Raw ADC reading at physical maximum uint16_t 1023
Y Calculated physical output value int32_t 500 (meaning 50.0)
Y_min Physical value at minimum span int32_t 0
Y_max Physical value at maximum span int32_t 1000

The critical rule when executing this as an integer calculator step by step is operator precedence. You must execute the multiplication (X - X_min) * (Y_max - Y_min) completely before the division / (X_max - X_min). If you divide first, integer truncation will collapse your numerator to zero, destroying your data.

Rearranged Forms for Calibration and Diagnostics

On the bench, you rarely just read sensors; you also need to diagnose them or calibrate your spans. Here are the algebraically rearranged forms of the core formula, optimized for integer math execution.

  • Solving for X (Expected Raw ADC):
    X = X_min + ((Y - Y_min) * (X_max - X_min)) / (Y_max - Y_min)
    Use case: Generating simulated ADC sweeps in a hardware-in-the-loop test rig.
  • Solving for Y_max (Calibrating Span):
    Y_max = Y_min + ((Y - Y_min) * (X_max - X_min)) / (X - X_min)
    Use case: You know the sensor reads 500 counts at exactly 50.0 units, and you need to auto-calculate the full-scale Y_max constant for production firmware.
  • Solving for the Scale Factor (Slope):
    Slope_Numerator = (Y_max - Y_min)
    Slope_Denominator = (X_max - X_min)
    Use case: Pre-calculating constants at compile time to save CPU cycles in the main loop.

Step-by-Step Solved Problems with Unit Tracking

Let's run two concrete scenarios. Tracking units through the intermediate steps is what separates robust firmware from buggy prototype code.

Problem 1: 10-Bit ADC to Millivolt Scaling

Scenario: An Arduino Uno (5V logic, 10-bit ADC) reading a 0-5000 mV signal. X_min = 0, X_max = 1023, Y_min = 0 mV, Y_max = 5000 mV. Raw reading X = 512.

  1. Calculate Raw Delta: X - X_min = 512 counts - 0 counts = 512 counts.
  2. Calculate Target Span: Y_max - Y_min = 5000 mV - 0 mV = 5000 mV.
  3. Multiply (Numerator): 512 counts * 5000 mV = 2,560,000 count-mV.
    Check: This fits safely inside a standard 32-bit signed integer (max 2,147,483,647).
  4. Calculate Raw Span (Denominator): 1023 counts - 0 counts = 1023 counts.
  5. Divide: 2,560,000 count-mV / 1023 counts = 2502.44 mV.
  6. Integer Truncation: The fractional .44 is dropped. Final Y = 2502 mV.

Problem 2: 12-Bit ADC to Signed Temperature (Handling Negative Offsets)

Scenario: An ESP32 (12-bit ADC, 0-4095) reading a sensor mapped to -40 °C to +85 °C. To avoid floating-point math, we scale the output to tenths of a degree (-400 to 850). X_min = 0, X_max = 4095, Y_min = -400, Y_max = 850. Raw reading X = 1000.

  1. Raw Delta: 1000 - 0 = 1000 counts.
  2. Target Span: 850 - (-400) = 850 + 400 = 1250 tenths-of-degrees.
  3. Multiply: 1000 * 1250 = 1,250,000.
  4. Divide by Raw Span (4095): 1,250,000 / 4095 = 305.25 -> truncates to 305.
  5. Add Y_min Offset: 305 + (-400) = -95.
  6. Final Interpretation: -95 tenths of a degree = -9.5 °C.

Real-World Bench Scenario: The 4-20mA Overflow Trap

Formulas on a whiteboard are clean; formulas on a workbench are messy. Here is a real-world failure mode that occurs when engineers forget to act as an integer calculator step by step and ignore hardware register limits.

The Setup: You are reading an industrial 0-100 PSI pressure transmitter using a 4-20mA current loop. The loop passes through a 250-ohm precision shunt resistor, generating a 1V to 5V drop. This feeds into an older 10-bit microcontroller ADC (0-1023 counts). Therefore, 4mA (1V) = 204 counts, and 20mA (5V) = 1023 counts. You want to output PSI in tenths (0 to 1000) for a serial display.

The Numbers:
X_min = 204, X_max = 1023. Span = 819 counts.
Y_min = 0, Y_max = 1000. Span = 1000 tenths-of-PSI.

The Outcome & What Went Wrong:
A junior firmware developer wrote the C++ equation exactly as it appears in standard algebra: int psi = ((adc - 204) / 819) * 1000;.
When the pressure was at 50 PSI (approx 2.98V, or 612 counts), the math executed as:
1. 612 - 204 = 408
2. 408 / 819 = 0 (Integer truncation destroys the value here)
3. 0 * 1000 = 0.
The display stubbornly read 0.0 PSI until the pipe nearly burst at 100 PSI, at which point it finally jumped to 100.0.

To fix the truncation, the developer swapped the order: int psi = ((adc - 204) * 1000) / 819;. This worked at 50 PSI. However, when they later ported the code to a higher-resolution 16-bit ADC (0-65535) and multiplied by 10,000 for extra precision, the intermediate multiplication (65535 * 10000) resulted in 655,350,000. Because they used a standard 16-bit int on an AVR chip (max value 32,767), the variable overflowed into negative garbage, and the system triggered a false over-pressure shutdown.

The Fix: Always cast the first operand to a 32-bit integer before the math cascade begins: int32_t psi = ((int32_t)(adc - X_min) * Y_span) / X_span;. For a deep dive on ADC hardware behaviors that compound these math errors, refer to the official Arduino analogRead() documentation regarding sampling anomalies.

Execution Rules: When It Applies and Fatal Unit Mistakes

Using integer math for sensor scaling is a powerful optimization, but it operates under strict physical and computational boundaries.

When the Formula Applies (and Assumptions)

  • Linear Transfer Functions: The sensor output must be strictly linear (e.g., resistive dividers, 4-20mA loops, basic op-amp scaling). It will fail catastrophically on non-linear sensors like NTC thermistors or CdS photoresistors, which require lookup tables or Steinhart-Hart logarithmic math.
  • Monotonic ADC Behavior: Assumes your ADC does not have massive dead-zones or non-monotonic steps. As noted in Analog Devices' primer on current loops, signal conditioning hardware must filter high-frequency noise before the ADC samples it, or integer truncation will cause the output to jitter wildly around the true value.
  • Deterministic Timing Environments: Use this when writing Interrupt Service Routines (ISRs) or high-speed motor control loops where floating-point emulation delays (often 50+ clock cycles) would cause missed deadlines.

Unit Mistakes That Break the Math

The most common way to break this formula is mixing base units with scaled units in the spans. If your Y_max is defined in Volts (e.g., 5V) and your Y_min is 0V, your Y_span is 5. Multiplying an ADC delta of 500 by 5 yields 2500. Dividing by 1023 yields 2. You just lost all your resolution. Rule: Always scale your physical targets to the smallest acceptable integer unit (millivolts, tenths of a degree, milliamps) before defining your constants.

Realistic Answer Magnitudes and Limits

When planning your Y_max scaling factor, you must calculate the absolute maximum intermediate numerator to ensure it fits in your target architecture's register. For a 32-bit signed integer (int32_t), the hard ceiling is 2,147,483,647.

If you are using a 16-bit ADC (X_span = 65535) and you want to scale to microvolts (Y_span = 5,000,000), the intermediate product is 327,675,000,000. This overflows a 32-bit integer. In this scenario, you must either reduce your precision (scale to millivolts instead) or implement a 64-bit integer cast (int64_t), accepting the slight CPU penalty for the wider bus width. By treating your firmware as an integer calculator step by step, you catch these architectural limits at compile time rather than debugging mysterious sensor dropouts at 2:00 AM on the factory floor.