Skip to main content
emobility.academy
M.TechEdge AI & estimationAdvanced About 10 weeksKit + add-ons

TinyML Neural-Network SOC Estimator in Fixed-Point

You build a small neural network that estimates the state of charge (SOC) of the kit's Li-ion cell from voltage, current and temperature, train it on the PC and run it in fixed-point integer arithmetic on the STM32F103. You compare its accuracy, memory and execution time with a coulomb-counting baseline.

Uses: ADC · UART

The problem

SOC is not directly measurable, and model-based estimators need careful cell parameterisation. Data-driven estimators learn the relationship from measurements, but they must fit into the few kilobytes of RAM and flash of a low-cost BMS controller with no floating-point unit.

Objectives

  • Build a measured dataset of voltage, current, temperature and reference SOC from the kit's cell.
  • Train and compare small feed-forward networks on the PC, optionally pre-trained on a public dataset.
  • Quantise the chosen network to 8-bit weights and run it in integer arithmetic on the STM32.
  • Measure SOC error, flash and RAM use and inference time against coulomb counting.

What you need

From the EV Embedded Development Kit

  • Second STM32F103C8T6 as the Smart BMS unit
  • 1S battery with connector and T-sense input
  • Battery charging unit with its own switch
  • Charge/discharge control relay
  • Load section: three switchable resistive loads with LEDs
  • BMS power switch, OLED and six navigation buttons
  • Serial UART interface

Software

  • STM32CubeIDE
  • Embedded C with HAL drivers
  • Python (NumPy, pandas, matplotlib)
  • TensorFlow with TensorFlow Lite conversion, or a hand-written NumPy quantiser
  • Optional: NASA PCoE or CALCE battery datasets for pre-training

Extra parts

  • INA219 I2C current/power monitor module — The kit has no current sensor. Coulomb counting for the reference SOC and the network's current input both need measured current, in charge and discharge.

This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.

Key concepts

State of charge (SOC)
The remaining charge as a fraction of the cell's usable capacity. It is inferred from voltage, current and temperature history, never measured directly.
Post-training quantisation
Converting trained floating-point weights and activations to 8-bit integers with a scale and zero point, so inference uses integer multiply-accumulate only.
Input window features
Because voltage depends on recent current, the network is given short histories such as the mean current and voltage change over the last few seconds, not a single sample.
Reference SOC
The training label, computed here by coulomb counting from a full charge with a capacity measured in a complete discharge test. Its own error bounds what the network can be shown to achieve.

Step-by-step guide

  1. 1

    Wire the current sensor and plan tests

    Fit the INA219 in series with the cell's load path on the I2C bus, following the module's polarity markings, with the cell disconnected while wiring. Plan a test campaign: a full capacity test, constant-load discharges at each load combination, pulsed and random load profiles, and supervised charges. Lithium safety rules apply throughout: check polarity, never short the cell, stay within its rated voltage limits and never leave it charging unattended.

    Checkpoint: INA219 reads current within 2% of a multimeter in series for each load combination, and a written test plan with voltage cut-offs.

  2. 2

    Configure the BMS controller and logger

    On the Smart BMS STM32 set 72 MHz, ADC with DMA for cell voltage and T-sense, I2C at 400 kHz for the INA219, GPIOs for the relay and load switches, and a UART at 115200 baud. Sample at 10 Hz, timestamping with a hardware timer. Stream voltage, current, temperature and the relay and load states as CSV. Show live values on the BMS OLED and use the navigation buttons to start and stop a test.

    Checkpoint: A ten-minute log at a steady 10 Hz with no dropped samples.

  3. 3

    Measure capacity and build reference SOC

    Charge the cell fully (supervised), rest an hour, then discharge at a constant load to the cut-off while integrating current. That charge is the usable capacity. For every later test, start from a full charge and compute reference SOC by coulomb counting with this capacity. Repeat the capacity test at the end of the campaign to check for drift. Write both values and the test conditions into the dataset documentation.

    Checkpoint: Two capacity measurements that agree within a few per cent, and reference SOC traces for every test.

  4. 4

    Prepare the dataset

    Assemble at least ten complete tests with varied load profiles. Compute input features per sample: present voltage, current and temperature, plus moving averages of current and voltage over 10 and 60 seconds. Normalise features with statistics from the training set only. Split by test, keeping whole profiles for validation and test. Optionally pre-train on a public dataset such as NASA PCoE or CALCE, then fine-tune on kit data.

    Checkpoint: Train, validation and test files split by test, with a documented feature list and scaling constants.

  5. 5

    Train and select the network

    In TensorFlow train feed-forward networks with one or two hidden layers of 8 to 32 ReLU units and a sigmoid or clipped linear output. Use mean squared error and early stopping. Record RMSE and maximum absolute error on validation for each size, along with parameter count. Choose the smallest network within an agreed margin of the best, because flash, RAM and time on the F103 are tight.

    Checkpoint: A table of network size against error, and a chosen model with fewer than about 2,000 parameters.

  6. 6

    Quantise and verify on the PC

    Apply full-integer post-training quantisation with a representative dataset, either through the TensorFlow Lite converter or your own per-layer scale computation. Export weights as int8 arrays and biases as int32 with the requantisation multipliers and shifts. Write a NumPy reference that performs exactly the integer maths the firmware will do and compare its output with the float model. If the loss is larger, try per-channel scales or a slightly wider hidden layer.

    Checkpoint: The integer reference model's SOC error on the test set is within 0.5 percentage points of the float model.

  7. 7

    Implement integer inference in C

    Write a small inference routine: int8 dot products accumulated in int32, bias add, requantisation by fixed-point multiply and shift, and ReLU as a clamp. Store weights as const arrays in flash. Optionally use CMSIS-NN kernels. Feed test-set inputs from the PC over UART and compare outputs with the NumPy reference element by element. Measure inference time with the DWT cycle counter.

    Checkpoint: Bit-exact agreement with the NumPy integer model, and inference time and memory use recorded from the map file.

  8. 8

    Run live and compare with the baseline

    Run the network live at 1 Hz on the BMS controller alongside a coulomb counter started from an OCV-based initial SOC, as a realistic baseline. Show both on the OLED. Run new load profiles not in the training set, including one started from a partially charged cell, where coulomb counting suffers from an unknown starting point. Keep the cell within its safe voltage limits and supervise every charge.

    Checkpoint: Live logs of network SOC, coulomb-counting SOC and reference SOC for at least three unseen profiles.

  9. 9

    Evaluate and report

    Report RMSE and maximum error for both methods, error versus SOC range, inference time, flash and RAM. Discuss the limits honestly: one cell, a narrow temperature range, a short test period and a reference SOC that carries its own error. Compare with published SOC results only qualitatively, since conditions differ. Publish the firmware, trained weights, notebooks and raw logs together so that every number in the report can be reproduced by your examiners.

    Checkpoint: A results chapter with tables and plots that can be regenerated from the logged data.

Testing & validation

  • INA219 current checked against a multimeter at each load setting.
  • Integer C model bit-exact with the Python integer reference.
  • SOC RMSE and maximum error on unseen profiles reported for network and coulomb counting.
  • Inference time, flash and RAM measured on the target.

Expected results

An int8 neural SOC estimator running on the STM32F103 with measured accuracy, memory and timing, and a comparison with coulomb counting on the same cell and profiles.

Research angle for your M.Tech thesis

Research questions: what is the smallest network that meets a target SOC error on a cell without an FPU, and how much does int8 quantisation cost in accuracy? Does pre-training on a public dataset (NASA PCoE or CALCE) and fine-tuning on a few kit profiles beat training on kit data alone? Metrics: RMSE and maximum SOC error, error under unknown initial SOC, inference cycles, flash and RAM bytes. Baselines: coulomb counting, OCV lookup, and the float network. A strong thesis result is a documented accuracy versus resource curve on real hardware with a clear protocol for reference SOC.

Take it further

  • Add a recurrent or temporal convolution layer and compare accuracy against memory cost.
  • Combine the network with coulomb counting in a simple fusion filter.
  • Send SOC over CAN to a dashboard node.
  • Evaluate robustness to sensor offsets injected in firmware.

Viva and review questions

Why quantise to int8 instead of running the float model?

The Cortex-M3 has no FPU, so every float operation is a library call taking many cycles. Int8 weights also take a quarter of the flash, and our tests showed a small accuracy loss.

How accurate is your reference SOC?

It comes from coulomb counting with the INA219 and a measured capacity, so it carries current-sensor and capacity error. We started each test from full charge to limit drift and report that the network cannot be proven more accurate than this reference.

Why give the network moving averages of current?

Terminal voltage depends on recent current through polarisation, so a single sample is ambiguous. Short histories let a feed-forward network capture that dynamic without recurrence.

Related EV projects

All 100 EV projects