Chapter 22 — CAN Login Sequence

Before a node can participate on a CAN bus, it must allocate a unique 12-bit alias through the CAN login sequence defined in the OpenLCB CAN Frame Transfer Standard. This chapter details the CID frame construction, LFSR seed generation, alias extraction, timing requirements, and collision handling, as implemented in can_login_statemachine.c and can_login_message_handler.c.

22.1 Login Sequence Overview

The CAN login is a 10-state process driven by the CAN login state machine. Each call to CanLoginStateMachine_run() dispatches to the handler for the current run_state:

sequenceDiagram participant Node as Node participant Bus as CAN Bus participant Others as Other Nodes Note over Node: State: INIT Node->>Node: seed = Node ID Note over Node: State: GENERATE_ALIAS Node->>Node: alias = LFSR(seed) Node->>Node: Register alias in mapping table Note over Node: State: LOAD_CID07 Node->>Bus: CID7 (Node ID bits 47-36 + alias) Note over Node: State: LOAD_CID06 Node->>Bus: CID6 (Node ID bits 35-24 + alias) Note over Node: State: LOAD_CID05 Node->>Bus: CID5 (Node ID bits 23-12 + alias) Note over Node: State: LOAD_CID04 Node->>Bus: CID4 (Node ID bits 11-0 + alias) Node->>Node: Snapshot current_tick Note over Node: State: WAIT_200ms Node->>Node: Wait >= 200ms (actually >= 300ms) alt No objection Note over Node: State: LOAD_RID Node->>Bus: RID (Reserve ID) Note over Node: State: LOAD_AMD Node->>Bus: AMD (Alias Map Definition + Node ID in payload) Node->>Node: is_permitted = true else Objection received Others->>Bus: Frame with same alias Bus->>Node: Collision detected Node->>Node: Reset to GENERATE_SEED end

22.2 LFSR Seed Generation

The OpenLCB CAN Frame Transfer Standard specifies a Linear Feedback Shift Register (LFSR) algorithm for generating unpredictable aliases from Node IDs. The implementation splits the 48-bit seed into two 24-bit halves:

static uint64_t _generate_seed(uint64_t start_seed) {
    uint32_t lfsr2 = start_seed & 0xFFFFFF;         // lower 24 bits
    uint32_t lfsr1 = (start_seed >> 24) & 0xFFFFFF; // upper 24 bits

    uint32_t temp1 = ((lfsr1 << 9) | ((lfsr2 >> 15) & 0x1FF)) & 0xFFFFFF;
    uint32_t temp2 = (lfsr2 << 9) & 0xFFFFFF;

    lfsr1 = lfsr1 + temp1 + 0x1B0CA3L;
    lfsr2 = lfsr2 + temp2 + 0x7A4BA9L;

    lfsr1 = (lfsr1 & 0xFFFFFF) + ((lfsr2 & 0xFF000000) >> 24);
    lfsr2 = lfsr2 & 0xFFFFFF;

    return ((uint64_t)lfsr1 << 24) | lfsr2;
}

The magic constants 0x1B0CA3 and 0x7A4BA9 are specified by the standard (TN section 6.1.3). The shift-and-add pattern ensures that even closely related Node IDs produce different alias sequences.

22.3 Alias Extraction

A 12-bit alias is extracted from the 48-bit seed by XOR-folding:

static uint16_t _generate_alias(uint64_t seed) {
    uint32_t lfsr2 = seed & 0xFFFFFF;
    uint32_t lfsr1 = (seed >> 24) & 0xFFFFFF;

    return (lfsr1 ^ lfsr2 ^ (lfsr1 >> 12) ^ (lfsr2 >> 12)) & 0x0FFF;
}

Since alias 0x000 is invalid per spec, if the extraction produces zero, the seed is advanced and extraction is retried in a loop until a non-zero alias results.

22.4 CID Frame Construction

Four CID frames broadcast the 48-bit Node ID in 12-bit chunks embedded in the CAN identifier. Each frame has zero data bytes.

FrameCAN Identifier (29 bits)Node ID Bits
CID7 RESERVED_TOP_BIT | 0x07000000 | (node_id >> 24) & 0xFFF000 | alias Bits 47-36
CID6 RESERVED_TOP_BIT | 0x06000000 | (node_id >> 12) & 0xFFF000 | alias Bits 35-24
CID5 RESERVED_TOP_BIT | 0x05000000 | node_id & 0xFFF000 | alias Bits 23-12
CID4 RESERVED_TOP_BIT | 0x04000000 | (node_id << 12) & 0xFFF000 | alias Bits 11-0

The 29-bit CAN identifier is structured as: bit 28 = reserved (set to 1), bits 27-24 = CID frame number (7,6,5,4), bits 23-12 = Node ID chunk, bits 11-0 = alias.

22.5 Timing: The 200ms Wait

After transmitting CID4, the node waits at least 200ms for potential collision reports. The implementation uses the global 100ms tick counter:

void CanLoginMessageHandler_state_wait_200ms(can_statemachine_info_t *info) {
    uint8_t elapsed = (uint8_t)(info->current_tick
            - (uint8_t)info->openlcb_node->timerticks);

    if (elapsed > 2) {
        info->openlcb_node->state.run_state = RUNSTATE_LOAD_RESERVE_ID;
    }
}
Actual Wait Time: 200-300ms

The 100ms timer tick granularity means the actual wait is between 200ms and 300ms. The comparison elapsed > 2 requires at least 3 tick increments (300ms worst case). The snapshot is taken when CID4 is loaded. This satisfies the spec's 200ms minimum requirement.

22.6 RID and AMD Frame Construction

FrameCAN IdentifierPayloadEffect
RID RESERVED_TOP_BIT | 0x00700000 | alias 0 bytes Claims the alias -- other nodes must stop using it
AMD RESERVED_TOP_BIT | 0x00701000 | alias 6 bytes: Node ID (big-endian) Defines the alias-to-Node-ID mapping for all observers. Sets is_permitted = true.

22.7 State Machine Dispatch

The CanLoginStateMachine_run() function dispatches to the correct handler based on the node's run_state:

run_stateValueHandlerDescription
RUNSTATE_INIT0state_initSeed = Node ID, skip to GENERATE_ALIAS
RUNSTATE_GENERATE_SEED1state_generate_seedAdvance LFSR one step (conflict retry only)
RUNSTATE_GENERATE_ALIAS2state_generate_aliasExtract 12-bit alias, register in table
RUNSTATE_LOAD_CHECK_ID_073state_load_cid07Load CID7 frame
RUNSTATE_LOAD_CHECK_ID_064state_load_cid06Load CID6 frame
RUNSTATE_LOAD_CHECK_ID_055state_load_cid05Load CID5 frame
RUNSTATE_LOAD_CHECK_ID_046state_load_cid04Load CID4 frame, snapshot tick
RUNSTATE_WAIT_200ms7state_wait_200msWait for collisions
RUNSTATE_LOAD_RESERVE_ID8state_load_ridLoad RID frame
RUNSTATE_LOAD_ALIAS_MAP_DEFINITION9state_load_amdLoad AMD frame, mark permitted
First Login vs. Conflict Retry

On the very first login, RUNSTATE_INIT sets the seed to the Node ID and jumps directly to RUNSTATE_GENERATE_ALIAS, skipping RUNSTATE_GENERATE_SEED. The GENERATE_SEED state is only entered on alias conflict retry, where the seed must be advanced to produce a different alias.

22.8 Collision Detection and Re-seed Flow

If another node's frame arrives using the same alias during the CID or wait phases, the RX handler sets the is_duplicate flag in the alias mapping table. The CAN main state machine detects this and restarts the login:

flowchart TD A[Main loop checks has_duplicate_alias] -->|true| B[Scan table for is_duplicate entries] B --> C[Found node with conflicting alias] C --> D[Unregister old alias] D --> E[Set run_state = RUNSTATE_GENERATE_SEED] E --> F[Clear has_duplicate_alias flag] F --> G[Next main loop iteration] G --> H[GENERATE_SEED: advance LFSR] H --> I[GENERATE_ALIAS: extract new alias] I --> J[Resume CID sequence with new alias] A -->|false| K[No conflict, continue normally]

22.9 The on_alias_change Callback

When a new alias is generated and registered, the on_alias_change optional callback (from can_config_t) is invoked. This allows the application to be notified of alias changes, which may be useful for debugging or for updating external mapping caches.

22.10 Source Files

FilePurpose
drivers/canbus/can_login_statemachine.cState machine dispatcher -- maps run_state to handler function
drivers/canbus/can_login_message_handler.cHandler implementations: LFSR, CID/RID/AMD frame construction, timing
drivers/canbus/can_config.cWiring -- builds the interface structs that connect dispatcher to handlers
← Prev: Ch 21 — Multi-Node Routing Next: Ch 23 — Wiring It Together →