Most explanations of a battery management system, including our own BMS overview, describe it stage by stage:
here’s how coulomb counting works, here’s how a contactor gets driven, here’s what isolation resistance means
This is useful, but it leaves out something engineers actually have to design: the order these pieces run in, and what gets passed between them. A BMS isn’t a bag of independent algorithms: it’s a pipeline, and the shape of that pipeline is itself a design decision with real consequences for latency, safety case structure, and where you’re allowed to put a software boundary.
The diagram below is one way to draw that pipeline: five stages, raw analog signals going in on the left, a CAN frame and a contactor command coming out on the right, and two signals that cut against the main flow rather than following it: one feeding a derating calculation back into state estimation, the other carrying safety-relevant requests into the safety state machine from outside the pipeline entirely.

Stage 1. Cell & pack sensing is where software starts trusting hardware
Everything downstream depends on four raw inputs:
- individual cell voltages (Vi)
- temperatures at various points in the pack (Ti)
- total pack current (I)
- isolation resistance (Riso) between the high-voltage system and chassis ground
These arrive over what the diagram calls the analog data loop, from what’s commonly called the CSC (the Cell Supervision Circuit), the per-module board carrying the cell-monitoring IC, thermistor interface, and (usually) the passive balancing FETs, talking to the master over an isolated serial link (daisy-chained SPI is common), plus a shunt or Hall-effect sensor for current and an insulation monitoring circuit for Riso.
This is worth calling out as its own stage because it’s the only place in the pipeline where the software is reading the physical world directly rather than another piece of software’s output. Every error downstream (a bad SoC estimate, a nuisance trip, a missed real fault) usually traces back to something that went wrong here: a noisy ADC reading, a thermistor with a bad calibration table, a current sensor with drift the software didn’t account for. We’ve covered isolation resistance monitoring itself on the isolation resistance page, and cell/pack sensing hardware choices deserve their own treatment separately from what follows here, which is entirely about what the software does once that data arrives.
Stage 2. Signal filtering and diagnostics: cleaning up before anything acts on it
Raw ADC samples are not something you want a safety decision or a Kalman filter working from directly. The AFE (analog front end) firmware layer sits between the sensing hardware and everything else, and its job is to make the rest of the software’s life easier and safer:
Noise filtering removes sampling artifacts and switching noise before a voltage or current reading is used for anything. Error plausibility checking cross-references readings against physical limits and against each other: a cell voltage that’s physically impossible, or a temperature reading that jumped 40°C in one sample period, gets flagged as a sensor fault rather than treated as real data. Passive cell balancing control also typically lives at this layer, since it’s a local, per-cell decision (bleed current through a resistor on cells that are ahead of the pack) that doesn’t need the full state-estimation picture to execute, just the filtered voltage readings.
The output the diagram labels “filtered signals” is the pipeline’s first real contract: everything after this stage is entitled to assume the numbers it’s working with have already been sanity-checked.
Stage 3. State estimation: turning filtered signals into numbers nobody can measure directly
This is the layer most people mean when they say “BMS algorithms.” State of Charge, State of Health, and State of Power are not physical quantities a sensor can read; they’re inferred, using models built from the filtered voltage, current, and temperature data. We’ve written about each of these in detail elsewhere on the site: SoC estimation by coulomb counting, State of Charge more broadly, State of Health, and State of Power, so this post won’t re-derive coulomb counting or Kalman filtering here. What matters for the architecture view is what this stage produces and consumes: it takes clean sensor data as input and produces the numbers every other consumer of the BMS (the safety layer, the vehicle or system controller, the driver’s dashboard) actually cares about.
The BMS master controller boundary
The dashed line running through the diagram marks a real architectural split, not just a drawing convenience, and it sits further left than you might expect. The CSC is the only thing outside it. Everything else (AFE-level filtering and diagnostics, state estimation, the safety state machine, and the CAN interface) runs on what the diagram labels the BMS master controller. That matches how distributed BMS architectures are usually described elsewhere: the CSC (or CSCs, plural, in a multi-module pack) handles module-level monitoring, while the master/BMU “runs sophisticated algorithms” for state estimation, executes control commands, and manages system-wide communication (evlithium’s BMS architecture guide describes the split this way). We’ve covered master-slave pack architecture more generally on its own page.
One boundary the diagram can’t settle on its own: whether the safety state machine (specifically contactor-control authority) genuinely sits inside “BMS master” or with a higher-level vehicle or system supervisory controller is a real design choice, not a fixed rule. Different safety architectures draw that line differently, and which one applies depends on how the overall safety case is structured, not on anything intrinsic to the BMS itself. Treat the safety state machine’s placement inside the master’s box here as one common answer, not the only one.
Stage 4. Safety state machine and operational limits, where estimates become actions
This is the layer with consequences you can hear: a contactor closing or opening. Three functions live here: contactor control, pre-charge sequencing, and OV/UV/OC protection.
- Contactor control is implemented as an HV state machine, moving the pack through discrete states (open, pre-charging, closed, fault) rather than treating contactor position as a simple on/off flag. We’ve covered contactor selection and control on its own page.
- Pre-charge sequencing is why that state machine needs a “pre-charging” state in the first place: closing contactors directly onto a discharged DC-link capacitor produces an inrush current that can weld contactor contacts or blow fuses. A resistor (or, less commonly, a controlled switching device) limits that inrush while the link capacitor charges, before the main contactor takes over, a step we’ve covered separately on the pre-charge resistor page.
- Over-voltage, under-voltage, and over-current protection is the layer that takes the SoC/SoH/SoP outputs from stage 3 and the limits from the pack’s design envelope and decides, in real time, whether the pack is being asked to do something it shouldn’t. This is also typically where the functional-safety case (ISO 26262 ASIL requirements, for automotive packs) concentrates its evidence, because it’s the layer with direct authority over the contactors.
Stage 5. External communication: what the rest of the vehicle or system actually sees
None of the preceding four stages matter to anything outside the BMS unless their results get communicated. The CAN bus interface broadcasts SoC, SoP, and pack status on a defined schedule so the vehicle control unit, charger, or energy management system can read pack state without polling, and it carries the contactor control signals that let an external controller request contactor state changes (subject to the safety state machine’s arbitration; external requests don’t bypass the protection logic). CAN is the dominant choice here for reasons that are mostly historical and practical: it’s already load-bearing infrastructure in nearly every vehicle and industrial system a pack gets integrated into, though the specific message layout is rarely standardized across manufacturers: most packs implement their own DBC.
The cross-cutting signals: voltage-limit derating and external safety requests
The forward pipeline (sense, filter, estimate, protect, communicate) is the easy part to draw. The two signals that don’t follow it are more interesting, and they’re doing genuinely different jobs, which is why they’re drawn as two separate arrows rather than one.
Voltage-limit derating runs from the safety state machine back into state estimation. Near either end of a cell’s usable range, two things are true at once: SoC estimation gets less trustworthy (the OCV-vs-SoC curve flattens at the extremes, so a given voltage error maps to a much larger SoC error), and the thing that actually has to be respected (the cell’s hard upper or lower voltage limit) doesn’t care how confident the SoC estimate is. So rather than deriving the discharge/charge current limit from SoC, the pack falls back to voltage: it estimates open-circuit voltage from the measured terminal voltage, current, and the cell’s DC internal resistance (OCV = Vterminal ± I×DCIR, correcting for the resistive drop under load), and uses that OCV, together with the safety layer’s configured voltage limits, to derate the current or power limit directly. This is the same logic behind the equivalent-circuit-model approach to State of Power estimation more generally: one worked example in the literature models terminal voltage as Vb = OCV − Vp − Ib·R0 (open-circuit voltage minus polarization voltage minus the ohmic drop) and derives the power limit by working backward from the voltage constraint through that same relationship (Zhang et al., Sustainability 2021). We’ve covered current-limit estimation more broadly on its own page. The arrow is labeled by what triggers it (proximity to a voltage limit) rather than “OCV derating,” since nothing about the OCV estimate itself gets altered; only the current/power limit downstream of it does.
External safety request runs from external communication into the safety state machine, and it’s worth keeping conceptually separate from a fault the BMS detects on its own. Not every reason to open contactors originates inside the BMS: a vehicle system controller can issue a user-initiated shutdown request, and a crash sensor can signal an impact; both cases where something else in the system has decided the pack needs to disconnect, and the BMS’s job is to act on that request rather than to have independently diagnosed a problem. Our own emergency shutdown page lists this explicitly among the shutdown triggers: alongside internally-detected conditions like cell temperature, cell voltage, and over-current, it names “an external request from the VSC or a direct input to the BMS from the crash sensor” and a plain “User Request… issued through the vehicle system controller (VSC).” That second phrasing is worth noting carefully: a crash sensor doesn’t always arrive via CAN through the external communication block the way a VSC request does; it’s sometimes wired directly into the BMS as a hardware input, bypassing this whole path. The diagram shows the CAN-borne case; a direct hardware interlock is a separate wire, not a software signal, so it doesn’t appear as an arrow here.
There’s a third source worth naming even though it isn’t drawn as a separate arrow: state estimation can itself flag a condition serious enough to warrant the safety state machine’s attention: an SoH that’s dropped below a safe threshold, or an SoC estimate that’s diverged enough to be untrustworthy. That doesn’t need its own backward arrow, because it already has a path forward: it rides along on the existing SoC/SoH/SoP arrow into the safety state machine, alongside the normal state estimate. The safety state machine ends up arbitrating three distinct kinds of input (its own OV/UV/OC checks, estimation-side confidence flags arriving with the normal forward data, and external requests it didn’t generate), which is a more accurate picture than a single generic “fault” signal.
This post has been built based on the support of and sponsorship from: illumion, Custom Power, Quarto Technical Services, Eatron Technologies and About:Energy
A second axis: low-level versus high-level software
Everything above describes the diagram as drawn: five stages left to right, plus two feedback paths running the other way. But there’s a question the diagram doesn’t answer on its own: within any one of those boxes, how much of the code is talking directly to hardware, and how much of it doesn’t know or care what hardware it’s running on? That split (low-level driver code versus high-level application code) is a different axis from the signal flow, and it cuts through several of the boxes rather than lining up with any of the boundaries already drawn.
Low-level software is register- and interrupt-level: the SPI driver for a daisy-chained cell-monitoring IC, the GPIO or PWM output that actually energizes a contactor coil, the CAN controller driver moving bits onto the wire. High-level software is hardware-independent application logic sitting on top of it: code that would survive a change of MCU or a different vendor’s cell-monitoring IC largely unmodified. The automotive world has a formal name for this layering: in AUTOSAR terms it’s the MCAL and basic software layer underneath, a runtime environment in the middle, and application software components on top, and a well-architected BMS follows roughly the same split whether or not it’s built on AUTOSAR specifically; one industry architecture writeup describes essentially this structure, with an MCAL layer, a safety application layer bundling the SOX (state estimation) components with contactor and protection logic, and a separate communication layer for CAN/Modbus/Ethernet (eInfochips).
Where that split lands varies a lot by stage, which is what makes it worth drawing separately rather than folding into the main diagram.
- Stage 1 is pure hardware, so it sits below the split entirely.
- Stage 2, the AFE firmware, is mostly low-level by nature: noise filtering and balancing-FET control are close to the sensing hardware, with only something like cross-cell plausibility checking starting to look like hardware-independent logic.
- Stage 3 flips that almost completely: state estimation is about as pure an example of high-level software as a BMS has, since coulomb counting, OCV lookup, and a Kalman filter all operate on the “filtered signals” contract without needing to know what produced them.
- Stages 4 and 5 are where it stops being clean: the safety state machine’s transition logic and OV/UV/OC threshold checks are high-level, but the code that actually drives the contactor coil or reads an auxiliary contact is low-level; the CAN interface has the same split between deciding what a message contains and the driver that puts it on the bus.

It’s also worth being explicit that this is independent of the master/CSC boundary covered above. That boundary is about which physical node executes a piece of code; the low-level/high-level split is about which layer of the stack it runs in. The dashed line carried over into the diagram below, at the same position as the main diagram’s master/CSC boundary, is there for visual continuity only; its placement isn’t derived from anything about the low-level/high-level split, and on this axis it doesn’t really tell you much beyond “the CSC has no software stack to split.” In the architecture this diagram describes, the CSC stays on the hardware side of both boundaries at once: it carries the balancing FETs and the raw sensing silicon, but not the balancing decision logic, which is why it shows up here as pure hardware rather than getting its own stack. A more distributed design could push some high-level logic (a local balancing decision, say) down onto a CSC with its own small controller; nothing about the low-level/high-level distinction itself rules that out, it’s a separate question from where any given piece of logic happens to run.
Why the layering matters beyond the diagram
None of these five stages need to run on the same processor, at the same rate, or even in the same codebase: the CSC’s own sampling can run at a fast, local rate largely decoupled from the master, while CAN broadcast runs at whatever cadence the vehicle bus schedule demands. But keeping the stages as clean, well-defined layers, each with a stable input/output contract, is what makes it possible to change one implementation detail (swap a Kalman filter for a new SoC model, move balancing from passive to active, upgrade a sensing IC) without having to re-validate the whole system end to end. That’s arguably the real point of drawing an architecture diagram like this one: not to document what exists, but to make the boundaries explicit enough that they can be trusted.
Looking ahead: cloud-connected calibration over the same channel
Everything above describes the pipeline as a closed loop running entirely onboard, in real time, inside one pack. The external communication stage is also, increasingly, where a second and much slower loop closes: fleet or site data uploaded over that same CAN-to-telematics/cloud path lets SoH (and potentially SoC and SoP) models get recalibrated centrally, using degradation data aggregated across many packs that no single BMS can see on its own, with updated parameters or model coefficients pushed back down to the pack over that same communication stage. This isn’t specific to EV packs; the same architecture applies to stationary storage (BESS) and, at a smaller scale, e-bikes: any BMS whose external communication stage reaches a network is a candidate for having its state-estimation algorithms tuned by data it didn’t generate itself. That’s a real shift for stage 3, from a fixed local algorithm to something with a remote, periodically-updated component, and it’s a big enough topic (data pipelines, validation before pushing a new model to a safety-relevant estimate, which of SoC/SoH/SoP it’s safe to touch this way) that it deserves its own post rather than a paragraph here.
References
External sources used to check specific technical claims in this piece, beyond our own site’s coverage linked inline above:
- Zhang, T., Guo, N., Sun, X., Fan, J., Yang, N., Song, J. & Zou, Y., “A Systematic Framework for State of Charge, State of Health and State of Power Co-Estimation of Lithium-Ion Battery in Electric Vehicles,” Sustainability, 13(9), 5166, 2021: mdpi.com/2071-1050/13/9/5166. Source for the equivalent-circuit-model relationship between terminal voltage, OCV, and internal resistance used in the voltage-limit-derating explanation.
- eInfochips, “Technical Deep Dive into Battery Management System Architecture”: einfochips.com. Source for the MCAL / safety-application-layer / communication-layer structure referenced in the low-level/high-level section.
- CSEE, “Battery Management Systems Explained Visually”: cselectricalandelectronics.com. General corroboration for the sense → estimate → protect → communicate pipeline shape and its feedback loops.
- MathWorks, “Model Battery Management System with Stateflow”: mathworks.com/help/stateflow. Reference example of multiple fault sources (voltage, temperature, over-current, contactor) converging on a single fault state.
- evlithium, “Battery Management System (BMS): Core Functions, Architecture and Hardware Design Guide”: evlithium.com. Source for the CSC / master (BMU) division of responsibilities referenced in the master-controller-boundary section.
