Bosch dishwasher board SWS4118: what the firmware does and what use it is (part 3)
Contents 8
The third part about the Bosch dishwasher board 702168823.01GA / SWS4118 Rev.4. Part one covers what is on the board and the EEPROM copy, part two how we read the STM32 firmware. This part is about what is in that firmware:
- what this board is in the dishwasher and who it talks to;
- what the firmware consists of: a bootloader and the main program;
- which parts of the microcontroller it switches on, read from the live board, read only;
- two three-phase inverters, overcurrent protection, temperature sensor tables, a self-test;
- how the board talks to the panel: the D-Bus;
- what use all this is to us and what we do next.
draft, a first analysis, still being extended
⚠ Everything here was read without writing anything. The firmware was analysed on a computer from the copy, and the peripheral settings were read from registers while the ST-Link powered the board from USB (3.3 V, no mains).
What this board is
The hn/bsh-home-appliances project describes Bosch dishwasher power modules of the EPG700xx series: the same STM32F301VCT6 microcontroller in a 100-pin package, on some boards the same FSB50550AB inverter, an X20 debug connector. Our board looks like one of that family. (The die itself identifies as an STM32F302/F303, see part two; judging by the blocks the firmware uses, TIM8, six comparators and four op-amps, it is most likely an STM32F303xC: the F302 has no TIM8.)
According to that description, this is the washing control unit (address 1 on the bus): it runs the wash program and drives the heater, pumps and valves. The button panel is a separate node (address 2) that only sends it commands: which program is selected, which options. They talk over the internal D-Bus, described below.
This matters to us: the "run the pump" command does not come from the panel, it is generated inside this firmware while it runs a wash program.
How we analysed it
| What | With | What for |
|---|---|---|
| the firmware copy (256 KB) | the Capstone disassembler (a Python library), for a first look | to see the structure, tables and peripheral accesses |
| the microcontroller peripheral settings | STM32_Programmer_CLI with -r32: reading registers on the live board | to find out what the firmware actually switched on |
| deeper analysis (next) | Ghidra: ARM Cortex, little endian, base 0x08000000 | command handlers, program logic |
We read the registers like this (every command only reads; the output is hexadecimal words). GPIO was read for every port with a 0x400 step; one is shown here, GPIOE:
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG freq=100 -r32 0x40021000 0x20
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG freq=100 -r32 0x48001000 0x28
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG freq=100 -r32 0x40012C00 0x50
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG freq=100 -r32 0x40013400 0x50
STM32_Programmer_CLI -c port=SWD mode=HOTPLUG freq=100 -r32 0x40013800 0x14
| Address | What is there |
|---|---|
0x40021000 | RCC: clocks |
0x48000000…0x48001400 | GPIOA…GPIOF: the mode of every pin (step 0x400) |
0x40012C00, 0x40013400 | timers TIM1 and TIM8: the inverters' PWM |
0x40013800 | USART1: only the configuration registers, up to 0x10 |
0x4001001C…0x40010044 | comparators and op-amps |
0x40007400 | DAC |
⚠ We do not read the serial ports' data registers (from 0x24 in USART1): reading them clears flags and can lose a byte the board is receiving at that moment.
What the firmware consists of
| Flash area | Size | What is there |
|---|---|---|
0x08000000–0x08000433 | ≈ 1 KB | the bootloader (it takes the first 2 KB page) |
0x08000800–≈0x0801E000 | ≈ 118 KB | the main program: interrupt table, code, tables |
0x0802C000–0x08034000 | 32 KB | data: settings, including a factory copy of EEPROM block 1 |
from 0x08038000 | ≈ 200 bytes | a little more data |
| the rest | ≈ 100 KB | empty (FF) |
The bootloader
Right at the start sits the string EDS_ST_STM32F3_02_210313, which looks like a build name and date (13 March 2021). What the bootloader does at power-on:
- It starts the crystal oscillator and a ×3 frequency multiplier. With an 8 MHz crystal (not yet checked on the board) that is 24 MHz.
- It sets up the USART1 serial port on PE0/PE1 (pins 97 and 98): 9600 baud, 8 bits with parity (at 24 MHz a divider of 2500 gives exactly 9600, one more argument for an 8 MHz crystal).
- It sends the byte
0x54and waits ≈ 30 ms for a reply (up to 1 s if the previous start asked for longer). - If there is no reply, it leaves a
55 AAmarker in RAM and restarts the chip with the watchdog. The next start sees the marker and hands over straight to the main program. - If there is a reply, it accepts commands:
| Command byte | What it does (from our analysis) |
|---|---|
0x0A | link check, answers 0x8A |
0x5B | returns the version string (EDS_ST_…, 32 bytes) |
0x77 | sets an address (most likely where to receive data) |
0x04 | changes the port speed |
0x1A | runs code from the given address |
The bootloader itself does not write to flash: updating apparently works by receiving a flashing program into RAM, which then rewrites the main program. In other words, the firmware can be updated over the same serial port the board uses to talk to the panel (only the format differs: the bootloader uses parity, 8E1, the main program does not, 8N1). We are not doing that, but it explains why the bootloader is there.
The main program
The interrupt table starts at 0x08000800. Apart from the standard ones, only seven handlers are in use, and they show straight away what the program is about:
| Interrupt | Most likely |
|---|---|
| ADC1_2 | ADC results ready: phase currents and bus voltage, the core of motor control |
| TIM1_UP | every PWM period of the first inverter |
| TIM1_BRK, TIM8_BRK | emergency shutdown of the inverters |
| USART1 | receiving and sending on the D-Bus |
| UART5 | another serial port (see below) |
| EXTI9_5 | an event on one of pins 5–9 of some port; which one is still unknown |
The program can write to its own flash (it contains the flash unlock sequence), most likely to keep its settings in the 0x0802C000 area.
What the firmware switches on in the chip
This was read from the live board's registers with the program already running (the board on 3.3 V from the ST-Link, no motors connected).
| Peripheral | What we read | What it means |
|---|---|---|
| clocks | crystal × 9, APB2 bus undivided, APB1 /2 | with an 8 MHz crystal that is 72 MHz, this chip's top speed |
| USART1 (PE0, PE1) | divider 7500, 8 bits without parity, interrupt-driven receive | 9600 baud, 8N1: exactly the D-Bus |
| TIM1 (PE8–PE13) | three complementary output pairs, centre-aligned PWM, period 1562, 2 µs dead time | a three-phase inverter, PWM ≈ 23 kHz |
| TIM8 (PC6–PC8, PC10–PC12) | the same, period 3446 | a second three-phase inverter, PWM ≈ 10.4 kHz |
| outputs of both timers | main output enable off | the motors are stopped, as they should be without mains |
| comparators COMP1–COMP6 | all six on, outputs routed to the timers' break inputs | overcurrent protection: three per inverter, one per phase |
| op-amps OPAMP1–OPAMP4 | on | they amplify the current-shunt signals for the ADC |
| DAC, both channels | 1806 of 4095 (≈ 1.46 V) | most likely the comparator threshold |
| ADC 1–4 | ≈ 25 pins in analogue mode | currents, voltages, temperatures, levels |
| TIM2 (PA9, PA10), TIM3 (PB5), TIM4 (PD13, PD15) | pins in timer mode | pulse or frequency measurement (sensors) or PWM; not yet known |
| UART5 | transmit only, ≈ 900 baud; not routed to any pin | purpose unknown |
| watchdogs | independent and window | if the program hangs, the chip restarts |
| CRC unit | computes a checksum of flash areas while running | a self-test (see below) |
Pin numbers in the 100-pin package:
| Signals | Microcontroller pins |
|---|---|
| TIM1: PE8–PE13 | 39–44 |
| TIM8: PC6, PC7, PC8 | 63, 64, 65 |
| TIM8: PC10, PC11, PC12 | 78, 79, 80 |
| USART1 (D-Bus): PE0, PE1 | 97, 98 |
Two inverters, one power module
The firmware is ready to drive two three-phase motors: both timers are set up the same way, each with its own protection. Our board carries one FSB50550AB power module. Either the second motor is driven some other way (on another board or another version of this one), or the firmware is shared between several board versions. Which timer feeds the FSB50550AB we will find with a continuity tester: its inputs should ring through either to pins 39–44 (TIM1) or to 63–65 and 78–80 (TIM8).
In Bosch dishwashers both the circulation pump and the drain pump can be three-phase brushless motors; according to hn, on some boards the FSB50550AB drives the drying fan. What ours drives we will find with the same continuity check and from the connector its outputs go to.
Field-oriented motor control
The program contains a 256-entry sine table (−32767 to 32767). Together with complementary PWM outputs, current measurement through op-amps and the ADC, and protection comparators, that is the usual kit for field-oriented control (FOC) of a brushless motor without position sensors, the same way ST's motor control library is built. The motor turns smoothly and quietly, and the firmware most likely works out the rotor position from the currents.
Temperature sensors
The firmware has two tables that convert a 12-bit ADC reading (0–4095) into degrees. Both run from 0 to 125 °C: these are thermistors (NTC), most likely in the water and on the heater or the power module.
| °C | Table 1, ADC | Table 2, ADC |
|---|---|---|
| 0 | 3147 | 3492 |
| 10 | 2810 | 3244 |
| 20 | 2439 | 2948 |
| 30 | 2062 | 2616 |
| 40 | 1702 | 2268 |
| 50 | 1378 | 1923 |
| 60 | 1101 | 1600 |
| 70 | 875 | 1317 |
| 82 | 661 | 1029 |
| 90 | 548 | 870 |
| 100 | 435 | 703 |
| 125 | 249 | 416 |
The hotter it gets, the lower the number: the thermistor is in the lower leg of the divider. Between the points the firmware most likely interpolates linearly. For our water heater this is a ready calibration table: measure the voltage at the ADC input (with the same divider) and you have the temperature. The 82 °C point instead of 80 is as it appears in the firmware table.
Flash self-test
While running, the program uses the hardware CRC unit to checksum areas of its own flash, following a table of ranges. Household appliances commonly do this so that corrupted memory never ends up driving the heater and the motor.
The consequence for us: you cannot simply patch even one byte of the original firmware, because the self-test will notice. So there are two options: the original firmware unchanged, or entirely our own.
How the board talks to the panel: the D-Bus
What hn describes, and what matches our analysis:
| What | hn's description | In our firmware |
|---|---|---|
| wires | three: GND, DATA, VBUS; in dishwashers VBUS ≈ 13.5 V, DATA is 5 V logic | USART1 on PE0/PE1; where it goes is still to be found |
| speed and format | 9600 baud, 8N1 | yes: divider 7500 at 72 MHz, no parity |
| frame | length, address and subsystem, command (2 bytes), data, CRC16 (XMODEM) | still being analysed |
| acknowledgement | one byte right after the frame | still being analysed |
Node addresses according to hn: 1 the washing control unit (our board), 2 the control panel, 0x6 the floor time projector, 0xA/0xB internet modules. Known commands: 17.10-10 xx selects wash program xx, 17.10-00 the options, 24.20-06 the rinse aid and salt indicators, 25.20-08 the remaining time.
According to hn, some nodes can return their memory contents straight over the D-Bus. We have not tried that, and no longer need to: we have the copy.
⚠ All of this is described for hn's boards; for ours we still need to record the real exchange between the panel and the board.
What use this is to us
| What we learned | What it gives us |
|---|---|
| no protection, copy saved | the board can always be returned to its factory state, so we can experiment freely |
| a bootloader on the serial port | the firmware can be updated without taking anything apart, over the same three wires |
| D-Bus: 9600 8N1, known frame format | an ESP32 can listen and send commands without changing the firmware |
| PWM pins, frequency, dead time, protection | everything our own firmware needs is already known from the original |
| NTC tables | a ready temperature conversion for the water heater |
| CRC self-test | patching the original firmware is a bad idea: either the original unchanged or our own |
| two inverters in the firmware | we need to find out which one feeds our FSB50550AB |
Our plan now:
- First, the original firmware and the D-Bus. We have the panel: we connect the board and the panel, listen to the DATA line with a logic analyser or an ESP32 (hn's project has a ready ESPHome component) and record what the panel sends when we start a program. Then we try sending those commands ourselves. Nothing gets erased.
- The catch with this route: the original firmware runs the whole wash program, so it will expect the level and water sensors, valves and the door lock. Without them it will most likely stop with an error. A service test might help; we will look for it in the exchange and in the firmware too.
- If the original firmware will not simply run the pump, our own firmware. The hardware is now known down to the pin, and the factory copy sits alongside, so we can go back at any time.
Next
- Trace the FSB50550AB inputs: TIM1 (pins 39–44) or TIM8 (63–65, 78–80).
- Find which connector PE0/PE1 (pins 97, 98) go to: that is the D-Bus line; measure VBUS on it.
- Find the crystal and check it is 8 MHz: all the frequencies above depend on it.
- Record the panel ↔ board exchange.
- Ghidra: the USART1 handler (
0x0800BA3D) and the D-Bus command table. - Find out what is on the TIM2–TIM4 timer pins and on the EXTI9_5 interrupt: flow, level or motor sensors.
🔒 Identifiers and original copies: hidden, only the author can see this.
Article changelog
- 2026-11-10 — first draft: firmware layout, the bootloader, chip peripherals from the live board, two inverters, NTC tables, the self-test, the D-Bus, what use it is and the plan.