Skip to main content
emobility.academy
B.Tech / BEDiagnostics & cybersecurityAdvanced About 7 weeksKit + add-ons

UDS (ISO 14229) Diagnostic Services over CAN

You implement a subset of UDS (ISO 14229) diagnostic services on the kit's STM32 over CAN with ISO-TP transport, including diagnostic sessions, reading data by identifier, reading DTCs and routine control. A Python tester on the PC talks to the kit through a USB-CAN adapter.

Uses: CAN · UART

The problem

Every modern vehicle ECU, including EV BMS and motor controllers, is serviced through UDS. Engineers who understand services, sessions, negative responses and the transport layer can build and debug diagnostic stacks, a core skill in automotive firmware.

Objectives

  • Implement ISO-TP (ISO 15765-2) single and multi-frame transport with flow control.
  • Implement UDS services 0x10, 0x3E, 0x22, 0x2E, 0x19, 0x14, 0x31 and 0x11.
  • Expose live kit data such as cell voltage and temperature through data identifiers.
  • Test with a PC tester, including error cases and timing.

What you need

From the EV Embedded Development Kit

  • STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
  • CAN bus interface
  • 1S battery with connector and T-sense input
  • Analog potentiometer input
  • General output relay (NO / COM / NC)
  • SPI flash memory
  • 16×2 LCD
  • Serial UART interface

Software

  • STM32CubeIDE
  • Embedded C with HAL drivers
  • Python with python-can (and optionally the udsoncan and can-isotp packages for cross-checking)

Extra parts

  • A USB-CAN adapter — To act as the diagnostic tester node on the bus, since the kit provides one CAN node.

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

Key concepts

UDS service and sub-function
Each request starts with a service ID, such as 0x22 ReadDataByIdentifier. A positive response adds 0x40 to the service ID; a negative response is 0x7F, the service ID and a negative response code.
ISO-TP
A transport layer that splits messages longer than 7 bytes into a first frame and consecutive frames, with the receiver sending flow-control frames to set block size and separation time.
Diagnostic session
The default session allows basic services; an extended session unlocks more. A session times out back to default unless the tester sends TesterPresent (0x3E).
Negative response codes
Codes such as 0x11 service not supported, 0x13 incorrect message length, 0x31 request out of range and 0x7F service not supported in active session tell the tester why a request failed.

Step-by-step guide

  1. 1

    Plan the service set and identifiers

    Choose services: 0x10 DiagnosticSessionControl, 0x3E TesterPresent, 0x22 ReadDataByIdentifier, 0x2E WriteDataByIdentifier, 0x19 ReadDTCInformation (sub-functions 0x01 and 0x02), 0x14 ClearDiagnosticInformation, 0x31 RoutineControl and 0x11 ECUReset. Define data identifiers such as 0xF190 VIN-style serial (writable), and project DIDs for cell voltage, temperature and potentiometer value. Define one routine, such as a relay self-test. Keep this specification as the single reference for both the firmware and the Python tester, and update it whenever a byte layout changes during development.

    Checkpoint: A service and DID specification with request and response byte layouts and session rules.

  2. 2

    Configure CAN

    Turn on the DIP switches for CAN, battery, potentiometer, relay, LCD and UART blocks. Configure bxCAN at 500 kbit/s and set a filter so the controller receives the physical request ID (for example 0x7E0) and functional ID 0x7DF. Use 0x7E8 for responses. Connect the USB-CAN adapter with proper 120 ohm termination at both ends. Check that the functional ID reaches the same dispatcher as physical requests.

    Checkpoint: Frames sent from the PC to 0x7E0 are received and echoed back on 0x7E8.

  3. 3

    Implement the ISO-TP layer

    Write receive and transmit state machines for single frames, first frames, consecutive frames and flow control, with sequence numbers and timeouts (N_Bs, N_Cr). Use a buffer large enough for your longest message. Respect the tester's separation time when transmitting multi-frame responses, using a timer rather than blocking delays. Test with different block sizes and separation times requested by the tester, including block size zero, which means the sender may send all frames without waiting.

    Checkpoint: A 50-byte message transfers correctly in both directions, and a missing consecutive frame causes a timeout and a clean reset.

  4. 4

    Build the UDS dispatcher

    Write a dispatcher that reads the service ID and calls a handler, checking message length and session permission first. Return negative responses with the correct codes for unsupported services, wrong length or wrong session. Implement suppress-positive-response for sub-functions where the bit is set. Keep a table of services with their allowed sessions and minimum lengths, so permission checks are data rather than scattered if-statements. This makes adding services later much safer.

    Checkpoint: Unsupported and malformed requests receive the correct negative response codes.

  5. 5

    Implement session and timing services

    Implement 0x10 with default and extended sessions, returning P2 and P2* timing values in the response. Start an S3 timer in the extended session that returns to default after about five seconds without requests, and reset it on any request including 0x3E. Show the current session on the LCD. Also handle a session change request while a multi-frame transfer is in progress, and document the behaviour you chose, since the standard leaves some choices to the implementer.

    Checkpoint: The session falls back to default after the timeout unless TesterPresent is sent.

  6. 6

    Implement data and DTC services

    Implement 0x22 for one or several DIDs per request, reading live ADC values in scaled units. Implement 0x2E for the writable serial DID only in the extended session, storing it in SPI flash. Implement 0x19 and 0x14 with a small DTC table such as over-temperature and throttle out of range, using UDS status bits. Return 0x31 for unknown DIDs and 0x7F for writes attempted in the default session.

    Checkpoint: Live values read correctly, the written serial persists through power cycling, and DTCs read and clear.

  7. 7

    Implement routine control and reset

    Implement 0x31 start and request-results for the relay self-test: toggle the general output relay and report success. Implement 0x11 hard reset by sending the positive response and then calling NVIC_SystemReset after the frame is transmitted. For the routine, check that a request-results call before the routine has started returns the negative response 0x24, request sequence error. For reset, confirm that session state, security state and any partly received ISO-TP message are cleared after the restart.

    Checkpoint: The routine runs and reports results, and the ECU resets and comes back in the default session.

  8. 8

    Build and run the PC tester

    Write a Python tester using python-can, and cross-check with the udsoncan and can-isotp packages if available. Script a test suite covering every service, every negative response code you support and the session timeouts. Log all frames with timestamps. Make every test check the exact response bytes, not just whether a reply arrived. Run the full suite after each firmware change so regressions are caught at once, and save a summary that lists each service with a pass or fail result.

    Checkpoint: The full test suite passes, with logs saved.

  9. 9

    Measure timing

    Measure response times from request to response on the PC log and check they are within the P2 value you declared. Measure multi-frame throughput at different block sizes and separation times. Check that diagnostics do not disturb the kit's other tasks. Run a background load on the kit, such as LCD updates and ADC sampling, during timing tests so the figures represent a busy ECU. Repeat each measurement many times and report the worst case as well as the average.

    Checkpoint: A timing table showing all responses within P2.

  10. 10

    Document the stack

    Write up the architecture of transport, dispatcher and services, the service specification, the test suite and timing results. State which parts of ISO 14229 you implemented and which you did not, such as security access and flash programming. Include message sequence charts for a single-frame read, a multi-frame read and a session timeout. Add the full test suite results and a table listing each supported service, its sub-functions, the sessions where it is allowed and its negative responses.

    Checkpoint: A report and a live demo with the Python tester.

Testing & validation

  • Multi-frame ISO-TP transfers with flow control in both directions.
  • Correct negative response codes for each error case.
  • Session timeout and TesterPresent behaviour verified.
  • Response times within the declared P2 limit.

Expected results

A working UDS server subset with ISO-TP on the STM32, a scripted Python tester and logged evidence that services, sessions, negative responses and timing behave as specified.

Take it further

  • Add 0x27 SecurityAccess with a seed and key.
  • Add 0x34, 0x36 and 0x37 for firmware download to SPI flash.
  • Add 0x2F InputOutputControl for the RGB LEDs.
  • Support extended 29-bit addressing.

Viva and review questions

What is the positive response to 0x22?

0x62, which is the service ID plus 0x40, followed by the DID and its data.

Why does ISO-TP need flow control?

A receiver may have limited buffers or processing time. The flow-control frame tells the sender how many frames to send in a block and the minimum gap between them.

What happens if the tester stops sending requests in the extended session?

The S3 timer expires after about five seconds and the ECU returns to the default session, which locks the extended-session services again.

Related EV projects

All 100 EV projects