The problem
Batteries in EVs do not discharge at a constant current; they see bursts during acceleration, steady cruising and rest at stops. Testing a cell under a realistic profile shows voltage sag and recovery that a constant-current test misses. This project turns the kit's switchable loads into a small programmable load bank, the low-voltage cousin of the cyclers used in battery labs.
Objectives
- Combine the three switchable loads into distinct current levels.
- Define load profiles as sequences of steps and run them from a timer.
- Estimate current from measured cell voltage and calibrated load resistance, with a stated error.
- Stream timestamped data over UART and plot it on a PC.
- Stop the test automatically at a voltage or temperature limit.
What you need
From the EV Embedded Development Kit
- STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
- 1S battery with connector and T-sense input
- Load section: three switchable resistive loads with LEDs
- Charge/discharge control relay
- Serial UART interface
- 4×3 matrix keypad
- 16×2 LCD with contrast control
Software
- STM32CubeIDE
- Embedded C with STM32 HAL
- Python with pyserial, pandas and matplotlib
Extra parts
None. Everything this project needs is on the kit.
This project uses the kit's lithium cell. Respect polarity, never short the terminals, and don't leave the cell charging unattended.
Key concepts
- Load profile
- A list of (load combination, duration) steps that represents a usage pattern, such as a short city-driving cycle scaled down to the cell.
- Current estimated from load resistance
- With a known resistance R switched across the cell, current is about V / R. The error comes from resistance tolerance and heating, wiring and switch resistance, and the board's own consumption.
- Voltage sag and recovery
- When load is applied, voltage drops immediately through internal resistance, then more slowly through diffusion effects; when load is removed it recovers in the same two stages.
- Parallel combinations
- Switching loads in parallel lowers the total resistance, so three loads give up to seven non-zero current levels if their values differ.
Step-by-step guide
- 1
Characterise the loads
With the cell disconnected and the board unpowered, measure each load's resistance with a multimeter at the load section, including the switch path if you can reach it. Calculate the resistance and expected current at 3.7 V for every combination of the three loads. Note the power each resistor dissipates, and check it against any rating printed on the board; avoid long runs on the combination that dissipates the most if the resistors get hot.
Checkpoint: A table of all load combinations with resistance, expected current and power at 3.7 V.
- 2
Set up the board and load control
Reconnect the cell observing polarity. Enable DIP switches for the load section, battery sense, T-sense, relay, LCD, keypad and UART. From the kit pin map, find the enable line for each load and write a function load_set(mask) where bit 0, 1 and 2 control the three loads. The charge/discharge relay must be closed for current to flow; keep it under protection-logic control.
Checkpoint: Calling load_set with each of the eight masks lights the matching load LEDs and the cell voltage dips accordingly.
- 3
Configure timing and measurement
Configure the ADC with DMA, VREFINT correction and calibrated cell voltage, as in the voltage-monitor design. Set TIM2 to interrupt every 10 ms; this is the profile scheduler tick and the sampling clock. Configure a USART at 115200 baud with DMA transmit. A free-running timer at 1 MHz gives microsecond timestamps if you need finer detail on voltage steps. Check the real tick period once with a GPIO toggle and an oscilloscope or a logic analyser if one is available.
Checkpoint: Samples arrive at a verified 100 Hz and the UART streams without dropping lines at that rate.
- 4
Estimate current and state its error
For each sample compute I = V / R_active, where R_active is the parallel resistance of the loads switched on. Add the board's quiescent consumption as a fixed estimate if you have measured it with a multimeter in series once. Work out the error: a 5 per cent resistor tolerance is removed by your measurement, but resistance rises as resistors heat, and contact resistance adds. Report current with its uncertainty, for example ±3 per cent.
Checkpoint: A written error estimate for the current, and a spot check of one load's current with a multimeter in series showing agreement within that estimate.
- 5
Define the profile format
Represent a profile as an array of steps, each with a load mask and a duration in 10 ms ticks, plus a repeat count. Create at least three profiles: constant load, a pulse train (high load for 10 s, rest for 30 s) and a scaled drive cycle with acceleration bursts, cruise and stops. Store built-in profiles in flash and allow a new profile to be sent from the PC as a simple text line over UART.
Checkpoint: The firmware prints a parsed profile back over UART exactly as it was sent.
- 6
Write the profile runner
In the timer tick, decrement the current step's remaining ticks and move to the next step when it reaches zero, calling load_set at each boundary. Keep the runner as a state machine: IDLE, RUNNING, PAUSED, STOPPED_LIMIT, DONE. The keypad starts, pauses and stops; the LCD shows the profile name, step number, cell voltage and elapsed time. Keep the step table in RAM while running so a profile sent from the PC can be swapped in between tests without reflashing. Log every step change so the PC plot can mark step boundaries.
Checkpoint: A pulse profile run with a stopwatch switches loads at the right times within one tick.
- 7
Add cut-off and safety limits
Before each step and every sample, check cell voltage against the cut-off you chose above the cell's rated minimum, and temperature against a limit. On a limit, switch all loads off, open the relay and record the reason. Use a short debounce so the instantaneous sag at a high-load step does not end the test early, but a sustained low voltage does. Never leave a long test running unattended.
Checkpoint: A test with a deliberately high cut-off stops at that voltage with the reason logged and loads off.
- 8
Log and visualise on the PC
Stream CSV lines: time, step, load mask, voltage, estimated current, temperature. Write a Python script with pyserial that saves to a file, then use pandas and matplotlib to plot voltage and current against time on shared axes. Add a summary: total mAh and Wh delivered, minimum voltage, and voltage drop at each load step. Mark each load step on the plot with a vertical line so voltage changes can be matched to the load that caused them, and save every run with a file name that includes the profile name and date.
Checkpoint: A plot of a full pulse-profile test with voltage sag and recovery clearly visible at each step.
- 9
Compare profiles
Run the constant-load profile and a pulse profile with the same average current, starting from the same rested full charge. Compare delivered capacity to cut-off, minimum voltage and temperature rise. Repeat each test at least twice to check repeatability. Discuss why the pulsed profile may deliver a different capacity and how rest periods let voltage recover. Record the ambient temperature for each run, since a warmer room can raise delivered capacity and blur the comparison between profiles.
Checkpoint: A comparison table with two runs per profile and a short explanation of the differences.
- 10
Document the tester
Write up the load characterisation, current-estimation method and error, profile format, state machine, safety limits and results. Include instructions for writing a new profile and the Python scripts. State the limitations clearly: discrete current levels only, current estimated rather than measured, and one cell at room temperature. Add a short troubleshooting section covering the problems you met, such as a load that does not switch, a noisy reading or a profile line that fails to parse, and how you fixed each one.
Checkpoint: A report and repository that lets another team define and run their own profile.
Testing & validation
- Step timing against a stopwatch and the UART timestamps.
- Estimated current against a multimeter in series for each load combination.
- Automatic stop at the voltage and temperature limits.
- Repeatability of delivered mAh across two runs of the same profile.
Expected results
A programmable load tester that runs drive-cycle-like profiles on a real cell, with plots of voltage response and a comparison of how different profiles affect usable capacity.
Take it further
- Add an INA219 add-on to measure current and compare it with the V / R estimate.
- Use PWM on a load enable line, if the load hardware allows fast switching, to create intermediate average currents.
- Store test results in SPI flash for unattended logging without a PC.
- Use the recorded pulses to identify an equivalent-circuit model.
Viva and review questions
How do you know the current without a current sensor?
From Ohm's law: the measured cell voltage divided by the measured resistance of the loads switched on. The error comes from resistance drift with heat, contact resistance and the board's own consumption.
Why does voltage drop in two stages when a load is applied?
The immediate drop is across the cell's ohmic resistance; the slower drop comes from charge-transfer and diffusion effects, modelled as RC branches.
Why repeat each test?
To show the result is repeatable and to estimate the spread, so differences between profiles can be judged against test-to-test variation.