UDS Library¶
Overview¶
The UDS (Unified Diagnostic Services) library provides a high-level, event-driven API for implementing ISO 14229-1 compliant diagnostic services. Built on top of the ISO14229 Library, it simplifies UDS server development by allowing you to register event handlers that respond to diagnostic requests.
Key Features¶
Event-Driven Architecture: Register handlers for specific UDS services using callbacks
Static and Dynamic Registration: Define handlers at compile-time via macros or register them at runtime
Flexible Handler System: Each handler includes
checkandactionfunctions for fine-grained controlPre-built Default Handlers: Common operations like memory access and ECU reset are provided out-of-the-box
Comprehensive Service Support: Implements most UDS services defined in ISO 14229-1
Supported UDS Services¶
The following diagnostic services are currently implemented:
Service |
SID |
|---|---|
Diagnostic Session Control |
|
ECU Reset |
|
Clear Diagnostic Information |
|
Read DTC Information |
|
Read Data By Identifier |
|
Read Memory By Address |
|
Security Access |
|
Communication Control |
|
Authentication |
|
Dynamically Define Data Identifier |
|
Write Data By Identifier |
|
Input Output Control By Identifier |
|
Routine Control |
|
Request Download |
|
Request Upload |
|
Transfer Data |
|
Request Transfer Exit |
|
Request File Transfer |
|
Write Memory By Address |
|
Tester Present |
|
Control DTC Settings |
|
Link Control |
|
Getting Started¶
Core Concepts¶
The UDS library operates on an event-driven model where diagnostic requests trigger events that are handled by registered event handlers.
Event Handlers¶
An event handler consists of:
Check Function (
uds_check_fn): Validates whether the current ECU state allows handling the event with its provided argumentsReturns
UDS_OKif the event can be processedReturns a negative response code (
UDS_NRC_*) if conditions aren’t metSets
apply_actiontotrueto proceed with the action function
Action Function (
uds_action_fn): Performs the actual diagnostic operationExecutes only if the check function approved
Returns
UDS_PositiveResponseon success orUDS_NRC_*on errorSets
consume_eventtotrueto stop event propagation orfalseto allow other handlers to process it
Associated Data: Context-specific information (e.g., data identifiers, memory addresses, user context)
Registration¶
Event handlers are registered using:
Static Registration: Use macros (e.g.,
UDS_REGISTER_*) to define handlers at compile-timeDynamic Registration: Call
instance.register_event_handler()at runtime (requiresCONFIG_UDS_USE_DYNAMIC_REGISTRATION)
Event Processing Flow¶
When a diagnostic request arrives:
The underlying ISO14229 Library generates a UDS event
The UDS library iterates through registered event handlers (static first, then dynamic)
For each handler:
The
checkfunction is calledIf approved, the
actionfunction executesIf
consume_eventistrue, iteration stops
If no handler processes the event, a negative response is sent to the client
Example¶
The following example demonstrates a minimal setup of an UDS server that registers a single Data Identifier handler.
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(uds_sample, LOG_LEVEL_DBG);
#include <zephyr/drivers/can.h>
#include <ardep/iso14229.h>
#include <ardep/uds.h>
static const struct device *can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus));
struct uds_instance_t instance;
// Define a data identifier and its associated data
const uint16_t primitive_type_id = 0x50;
uint16_t primitive_type = 5;
UDSErr_t read_data_by_id_check(const struct uds_context *const context,
bool *apply_action) {
*apply_action = true;
return UDS_OK;
}
UDSErr_t read_data_by_id_action(struct uds_context *const context,
bool *consume_event) {
UDSRDBIArgs_t *args = context->arg;
*consume_event = true;
// Convert to big-endian for network transmission
uint16_t t = sys_cpu_to_be16(*(uint16_t *)context->registration->data_identifier.data);
return args->copy(context->server, t, sizeof(t));
}
UDS_REGISTER_DATA_BY_IDENTIFIER_HANDLER(
&instance,
primitive_type_id,
&primitive_type,
read_data_by_id_check,
read_data_by_id_action,
NULL,
NULL,
NULL,
NULL,
NULL);
int main(void) {
int err = 0;
// Configure ISO-TP addressing
UDSISOTpCConfig_t cfg = {
.source_addr = 0x7E8,
.target_addr = 0x7E0,
.source_addr_func = 0x7DF,
.target_addr_func = UDS_TP_NOOP_ADDR,
};
uds_init(&instance, &cfg, can_dev, NULL);
if (!device_is_ready(can_dev)) {
LOG_INF("CAN device not ready");
return -ENODEV;
}
err = can_set_mode(can_dev, CAN_MODE_NORMAL);
if (err) {
LOG_ERR("Failed to set CAN mode: %d", err);
return err;
}
err = can_start(can_dev);
if (err) {
LOG_ERR("Failed to start CAN device: %d", err);
return err;
}
instance.iso14229.thread_start(&instance.iso14229);
}
For more examples on how to setup the different UDS services with macros, see the code of the UDS Sample and the documentation on the Macros in uds_macro.h.
The UDSISOTpCConfig_t allows to describe the addressing scheme used on the CAN bus:
source_addr: The physical address the ECU listens to for “physical” requests (the address of the ECU).0x7E8in the example above.target_addr: The physical address the ECU uses in “physical” responses (the address of the Tester). Used for ECU → tester responses under physical addressing.0x7E0in the example above.source_addr_func: The functional/group request ID the ECU listens to for “functional” requests.0x7DFin the example above.target_addr_func: The ECU uses this address in “functional” responses. Usually set toUDS_TP_NOOP_ADDRas functional requests do not expect a response.
API Reference¶
Event Handler Registration¶
This section describes the macros and functions available for registering handlers for each UDS service.
Read DTC Information (0x19)¶
Events: UDS_EVT_ReadDTCInformation
The Read DTC Information service supports multiple subfunctions. You can register handlers with different levels of granularity:
Macros:
UDS_REGISTER_READ_DTC_INFO_HANDLER(_instance, _check, _act, _subfunc_id, _user_context)Register a handler for a specific subfunction (e.g.,
UDS_READ_DTC_INFO_SUBFUNC__DTC_BY_STATUS_MASK)UDS_REGISTER_READ_DTC_INFO_HANDLER_MANY(_instance, _check, _act, _user_context, ...)Register a handler for multiple subfunctions by providing subfunction IDs as variadic arguments
UDS_REGISTER_READ_DTC_INFO_HANDLER_ALL(_instance, _check, _act, _user_context)Register a handler for all subfunctions
Example:
UDSErr_t my_check_dtc(const struct uds_context *ctx, bool *apply_action) {
*apply_action = true;
return UDS_OK;
}
UDSErr_t my_read_dtc(struct uds_context *ctx, bool *consume_event) {
// Read and return DTC data
*consume_event = true;
return UDS_PositiveResponse;
}
UDS_REGISTER_READ_DTC_INFO_HANDLER(
&instance,
my_check_dtc,
my_read_dtc,
UDS_READ_DTC_INFO_SUBFUNC__DTC_BY_STATUS_MASK,
NULL
);
Memory Operations (0x23, 0x3D)¶
Events: UDS_EVT_ReadMemByAddr, UDS_EVT_WriteMemByAddr
Macros:
UDS_REGISTER_MEMORY_HANDLER(_instance, _read_check, _read, _write_check, _write, _user_context)Register custom handlers for memory read and write operations
UDS_REGISTER_MEMORY_DEFAULT_HANDLER(_instance)Register default handlers that support reading/writing RAM and Flash memory
Default Handler Behavior:
Validates memory addresses are within RAM or Flash regions
Performs bounds checking
Uses safe memory access functions
Example:
// Use default handler for standard memory access
UDS_REGISTER_MEMORY_DEFAULT_HANDLER(&instance);
// Or register custom handler for specialized behavior
UDS_REGISTER_MEMORY_HANDLER(
&instance,
my_mem_check_read,
my_mem_action_read,
my_mem_check_write,
my_mem_action_write,
&my_context
);
ECU Reset (0x11)¶
Events: UDS_EVT_EcuReset, UDS_EVT_DoScheduledReset
Macros:
UDS_REGISTER_ECU_RESET_HANDLER(_instance, _reset_type, _ecu_reset_check, _ecu_reset, _do_scheduled_reset_check, _do_scheduled_reset, _user_context)Register a custom handler for a specific reset type (e.g.,
ECU_RESET__HARD)UDS_REGISTER_ECU_DEFAULT_HARD_RESET_HANDLER(_instance)Register the default handler for hard reset operations
Example:
UDS_REGISTER_ECU_DEFAULT_HARD_RESET_HANDLER(&instance);
Data Identifier Operations (0x22, 0x2E, 0x2F)¶
Events: UDS_EVT_ReadDataByIdent, UDS_EVT_WriteDataByIdent, UDS_EVT_IOControl
These three services share the same data identifier space, so they use a common registration mechanism.
Macros:
UDS_REGISTER_DATA_BY_IDENTIFIER_HANDLER(_instance, _data_id, _data_ptr, _read_check, _read, _write_check, _write, _io_control_check, _io_control, _user_context)
Parameters:
_instance: Pointer to the UDS server instance_data_id: The 16-bit data identifier (DID)_data_ptr: Pointer to the data buffer_read_check,_read: Check and action functions for read operations_write_check,_write: Check and action functions for write operations (set toNULLif not supported)_io_control_check,_io_control: Check and action functions for IO control operations (set toNULLif not supported)_user_context: Optional user-defined context
Example:
static uint16_t vehicle_speed = 0;
UDSErr_t read_speed_check(const struct uds_context *ctx, bool *apply) {
*apply = true;
return UDS_OK;
}
UDSErr_t read_speed_action(struct uds_context *ctx, bool *consume) {
// Data is automatically copied from vehicle_speed
*consume = true;
return UDS_PositiveResponse;
}
UDS_REGISTER_DATA_BY_IDENTIFIER_HANDLER(
&instance, // Instance
0xF123, // Custom DID
&vehicle_speed, // Data pointer
read_speed_check, // Read check
read_speed_action, // Read action
NULL, NULL, // No write support
NULL, NULL, // No IO control support
NULL // User context
);
Diagnostic Session Control (0x10)¶
Events: UDS_EVT_DiagSessCtrl, UDS_EVT_SessionTimeout
Macros:
UDS_REGISTER_DIAG_SESSION_CTRL_HANDLER(_instance, _diag_session_ctrl_check, _diag_session_ctrl, _session_timeout_check, _session_timeout, _user_context)
Note: This handler is optional. Session change requests succeed even without a custom handler. The library automatically manages session state.
Accessing Current Session:
struct uds_instance_t instance;
uint8_t current_session = instance.iso14229.server.sessionType;
Warning
If you use UDS_REGISTER_LINK_CONTROL_DEFAULT_HANDLER(), it registers its own session handler. Ensure your custom handler doesn’t consume session events if both are used.
Clear Diagnostic Information (0x14)¶
Events: UDS_EVT_ClearDiagnosticInfo
Macros:
UDS_REGISTER_CLEAR_DIAG_INFO_HANDLER(_instance, _check, _act, _user_context)
Routine Control (0x31)¶
Events: UDS_EVT_RoutineCtrl
Macros:
UDS_REGISTER_ROUTINE_CONTROL_HANDLER(_instance, _routine_id, _check, _act, _user_context)
The action function must handle all subfunctions:
UDS_ROUTINE_CONTROL__START_ROUTINE(0x01)UDS_ROUTINE_CONTROL__STOP_ROUTINE(0x02)UDS_ROUTINE_CONTROL__REQUEST_ROUTINE_RESULTS(0x03)
Security Access (0x27)¶
Events: UDS_EVT_SecAccessRequestSeed, UDS_EVT_SecAccessValidateKey
Macros:
UDS_REGISTER_SECURITY_ACCESS_HANDLER(_instance, _request_seed_check, _request_seed_act, _validate_key_check, _validate_key_act, _user_context)
Accessing Current Security Level:
struct uds_instance_t instance;
uint8_t security_level = instance.iso14229.server.securityLevel;
Communication Control (0x28)¶
Events: UDS_EVT_CommCtrl
Macros:
UDS_REGISTER_COMMUNICATION_CONTROL_HANDLER(_instance, _check, _act, _user_context)
Control DTC Setting (0x85)¶
Events: UDS_EVT_ControlDTCSetting
Macros:
UDS_REGISTER_CONTROL_DTC_SETTING_HANDLER(_instance, _check, _act, _user_context)
Dynamically Define Data Identifier (0x2C)¶
Events: UDS_EVT_DynamicDefineDataId
Macros:
UDS_REGISTER_DYNAMICALLY_DEFINE_DATA_IDS_HANDLER(_instance, _check, _act, _user_context)Register a custom handler
UDS_REGISTER_DYNAMICALLY_DEFINE_DATA_IDS_DEFAULT_HANDLER(_instance)Register the default handler (recommended)
Important
Requires CONFIG_UDS_USE_DYNAMIC_REGISTRATION=y in your prj.conf
Warning
Implementing this service requires managing internal UDS library structures. Use the default handler unless you have specific requirements.
Authentication (0x29)¶
Events: UDS_EVT_Auth, UDS_EVT_AuthTimeout
Macros:
UDS_REGISTER_AUTHENTICATION_HANDLER(_instance, _auth_check, _auth_act, _timeout_check, _timeout_act, _user_context)
Managing Authentication State:
Unlike session and security level, authentication state is not stored internally. You must manage it in your application.
Recommended Approach: Store authentication data in a user context:
struct my_auth_context {
bool authenticated;
uint8_t auth_level;
// Additional authentication data
};
struct my_auth_context auth_ctx = { .authenticated = false };
struct uds_instance_t instance;
uds_init(&instance, &iso_tp_config, &can_dev, &auth_ctx);
UDSErr_t my_auth_check(const struct uds_context *ctx, bool *apply_action) {
struct my_auth_context *auth =
(struct my_auth_context *)ctx->instance->user_context;
// Check authentication state
if (auth->authenticated) {
*apply_action = true;
return UDS_OK;
}
return UDS_NRC_SECURITY_ACCESS_DENIED;
}
Tester Present (0x3E)¶
Events: UDS_EVT_TesterPresent
This service is handled automatically by the library. No custom event handlers are needed.
Link Control (0x87)¶
Events: UDS_EVT_LinkControl
Macros:
UDS_REGISTER_LINK_CONTROL_HANDLER(_instance, _check, _act, _user_context)Register a custom handler
UDS_REGISTER_LINK_CONTROL_DEFAULT_HANDLER(_instance)Register the default handler (recommended)
Configuration:
# In prj.conf
CONFIG_UDS_USE_LINK_CONTROL=y
CONFIG_UDS_DEFAULT_CAN_BITRATE=500000
Important
Requires
CONFIG_UDS_USE_LINK_CONTROL=yMust set
CONFIG_UDS_DEFAULT_CAN_BITRATEto your CAN interface’s default baudrate because the default bitrate cannot be queried in code.
Default Handler Behavior:
Handles baudrate transitions
Automatically restores default baudrate on session timeout
Registers its own session control handler
Warning
The default link control handler registers a session event handler. If you use both this and a custom session handler, ensure your handler does not consume session events.
Data Transfer Services (0x34, 0x35, 0x36, 0x37, 0x38)¶
Events: UDS_EVT_RequestDownload, UDS_EVT_RequestUpload, UDS_EVT_TransferData, UDS_EVT_RequestTransferExit, UDS_EVT_RequestFileTransfer
These services are handled internally by the library and do not support custom handlers.
These services read from and write to flash memory or the file system. Note that they do not perform flash erase operations; any required erasure must be done beforehand (for example, via a routine).
Configuration:
# In prj.conf
CONFIG_UDS_FILE_TRANSFER=y # Required for file transfer (0x38)
Dynamic Registration¶
For scenarios where handlers need to be registered at runtime (e.g., based on configuration files, runtime conditions), the library supports dynamic registration.
Prerequisites:
# In prj.conf
CONFIG_UDS_USE_DYNAMIC_REGISTRATION=y
Registration Process:
Create a
struct uds_registration_tobject with the desired configurationCall
instance.register_event_handler()Store the returned
dynamic_idfor later unregistration
Example:
struct uds_instance_t instance;
// Create registration
struct uds_registration_t reg = {
.instance = &instance,
.type = UDS_REGISTRATION_TYPE__DATA_IDENTIFIER,
.data_identifier = {
.data_id = 0xF190,
.data = &my_vin_data,
.read = {
.check = my_read_check,
.action = my_read_action
},
.user_context = NULL
}
};
// Register dynamically
uint32_t dynamic_id;
struct uds_registration_t *reg_ptr;
int ret = instance.register_event_handler(&instance, reg, &dynamic_id, ®_ptr);
if (ret == 0) {
// Registration successful
}
// Later, unregister
instance.unregister_event_handler(&instance, dynamic_id);
Notes:
Examine the static registration macros in
ardep/uds_macro.hfor guidance on structuring registration objectsDynamic handlers are checked after static handlers during event processing
Returns
-ENOSPCif all dynamic IDs are exhausted (1 to UINT32_MAX)
Advanced Topics¶
Internals and Architecture¶
The UDS library uses Zephyr’s Iterable Sections for static event handlers and a singly-linked list for dynamic handlers.
Event Processing Order:
Static handlers (defined via macros) - stored in iterable sections
Dynamic handlers (registered at runtime) - stored in linked list
Default negative response - if no handler processed the event
Performance Considerations:
Static handlers have O(n) lookup time but zero memory allocation overhead
Dynamic handlers also have O(n) lookup but require heap allocation
Handler Interaction¶
Multiple handlers can be registered for the same event type. The library processes them in order until one consumes the event.
Event Consumption:
Set
consume_event = trueto stop further processingSet
consume_event = falseto allow subsequent handlers to process the event
Use Cases for Non-Consuming Handlers:
Logging/monitoring without affecting normal processing
Best Practices¶
Use Default Handlers When Possible
The library provides tested implementations for common operations (memory access, ECU reset, link control). Use them unless you have specific requirements.
Always Set consume_event
Explicitly set the
consume_eventflag in your action functions. Don’t rely on default values.Validate in Check Functions
Perform all validation in the
checkfunction. Theactionfunction should assume preconditions are met.Return Appropriate NRCs
Use ISO 14229-1 defined negative response codes (
UDS_NRC_*) to provide meaningful feedback to diagnostic clients.Manage State Carefully
For services like Authentication that don’t store internal state, use the
user_contextto maintain necessary information.Test Session and Security Interactions
Many UDS services require specific diagnostic sessions or security levels. Ensure your handlers check these preconditions.
Troubleshooting¶
Handler Not Being Called
Verify the handler is registered (check return value of dynamic registration)
Ensure
checkfunction returnsUDS_OKand setsapply_action = trueCheck if an earlier handler consumed the event
For static handlers, verify the macro is at file scope (not in a function)
Wrong NRC Returned
Check that your
checkfunction returns the correct NRCVerify your
actionfunction doesn’t override the NRC on error pathsEnsure you’re not consuming events that should be handled by later handlers
Link Control Issues
Verify
CONFIG_UDS_DEFAULT_CAN_BITRATEmatches your hardware configurationCheck CAN driver supports runtime baudrate changes
Ensure session handler doesn’t consume events when using default link control
Dynamic Registration Fails
Confirm
CONFIG_UDS_USE_DYNAMIC_REGISTRATION=yCheck available heap memory
Verify you’re not exceeding UINT32_MAX registrations
Further Reading¶
ISO14229 Library - Underlying ISO-TP and UDS protocol implementation
ISO 14229-1:2020 - UDS specification
Zephyr Iterable Sections - Understanding static registration