ZZBX / NOTES ← All notes
AutoSAR · Com · CAN

Three paths
for one CAN frame

I read through a PuHua AutoSAR CP build from Com_SendSignal down to FlexCAN_Ip_Send, and back up again. The thing worth writing down is not the function list — it is that sending, confirmation and reception are three independent paths driven by three different main functions. Confirmation is not nested inside the send call, and nearly every confusing symptom in this stack follows from that one fact.

2026-11-05 ◆ 13 min read ◆ Code reading, no runs ◆ PuHua AutoSAR CP
3independent paths
4layers crossed
2PduR flags
1runtime flag

Why read generated code at all

AutoSAR is configured, not written. You set ComIPduSignalProcessing to DEFERRED and a generator emits a different function body. That is the point of the standard, and it is also where debugging gets hard: the configuration is declarative and the behaviour is imperative, and the mapping between them lives only in the generated C.

So when a frame stops going out, the config tool shows you a checkbox that looks right, and the C code shows you a branch you did not ask for. Reading the chain once, end to end, converts that from a mystery into a lookup table.

Scope of this article

This is a code reading, not an experiment. Everything below comes from following one PuHua AutoSAR CP build (Com → PduR → CanIf → FlexCAN) and matching each call back to the ECUC container that produced it. The failure modes in sections 6 and 7 are derived from that chain, not observed on a bench — I mark them as such, because a derived claim presented as a measurement is the fastest way to waste a reader's afternoon.

One more thing to state up front: module names and container paths here are from one vendor build. The layering is standard, the exact function names are not — a different generator will name things differently, and you should read your own output rather than trusting my spelling.

The shape: three lanes

Here is the whole thing on one page. Read it left to right: the three columns are not steps in a sequence, they are three separate flows that happen to touch the same PDU.

The three independent paths in an AUTOSAR COM stack: send, transmit confirmation, receive
Send is driven by Com's main function, confirmation by the CAN driver's main function, reception by polling. Three drivers, one PDU.

Two properties of this picture carry most of the practical value. First, the left column is the only one you initiate — the middle and right columns are driven by the CAN driver's own main functions, on their own schedule. Second, the middle column starts at the CAN driver, not at Com. A confirmation travels upward; it is not a return value.

Send: why SendSignal does not send

The left column, in order. Reading it as a single call stack is the first mistake people make, because the top two entries are not part of the stack below them — they write data, and the main function is what sends it.

Com_SendSignal            ← app / RTE calls in; writes the signal
  Com_SetTxSignalBuff     ← into Com's own runtime buffer
    Com_Signal8BitRuntimeBuff[]

      ……〈returns here. Everything below is main-function driven〉……

Com_MainFunctionTx        ← cyclic task; decides which PDUs are due
  Com_MainFunction_SendPdu
    PduR_ComTransmit      ← into PduR, carrying Com's PDU handle
      PduR_Transmit
        PduR_UpModeTransmit        ← checks the current mode permits transmission
          ModuleIfTransmitApi      ← dispatch on PduRBswModuleType
            CanIf_Transmit
              CanIf_TransmitHandle
                CanWriteApi            ← dispatch on Can_DriverApiType
                  Can_Write
                    Can_43_FLEXCAN_Write
                      Can_43_FLEXCAN_Ipw_Write
                        FlexCAN_Ip_Send
                          FlexCAN_StartSendData

The split at the top is not an implementation detail, it is the ComIPduSignalProcessing parameter doing its job. On the same PDU the two settings behave like this:

Setting What SendSignal does When the frame leaves
DEFERRED Writes the buffer and returns Next Com_MainFunctionTx tick
IMMEDIATE Writes the buffer and triggers the send path Inside the caller's context

DEFERRED is the normal choice for cyclic frames, and the reason is bandwidth: five signals updated in five different places collapse into one bus access instead of five. The price is that the caller no longer controls when the frame goes out — which is exactly what a developer debugging a timing problem needs to control. That trade is the parameter's entire purpose, and it is worth knowing which side of it your frame is on before you start reading logs.

Where to look

The value comes from ComIPdu. In the build I read, a Tx PDU carries ComIPduDirection = SEND and ComIPduSignalProcessing = DEFERRED, with the period under ComTxIPdu/ComTxModeFalse/ComTxMode/ComTxModeMode = PERIODIC and ComTxModeTimePeriod = 0.02 — twenty milliseconds. Those four parameters together decide whether the frame is sent by the main function, how often, and starting when.

Below CanIf the chain becomes mechanical: PduR has one transmit entry per lower module (CAN, LIN, FlexRay), and the dispatch through ModuleIfTransmitApi is a table lookup keyed by PduRBswModuleType. Nothing smart happens down there. Which is useful: if a frame dies between CanIf and the driver, the problem is almost always the CAN ID / handle mapping, not the logic.

Confirm: one layer later

This is the column people get wrong, so it gets its own section. Confirmation starts at the CAN driver's main function, travels up through CanIf and PduR, and ends by clearing a flag in Com:

Can_MainFunction_Write    ← the CAN driver's own cyclic task (e.g. Periods_0)
  Can_MainFunction_MultipleWrite
    Can_43_FLEXCAN_Ipw_MainFunction_Write   ← polls whether the TX mailbox really completed
      CanIf_TxConfirmation
        CanIfTxPduUserTxConfirmationName    ← callback name from config, resolved back by name
          PduR_CanIfTxConfirmation
            PduR_IfTxConfirmation
              ModuleTxConfirmationApi       ← another dispatch, by module type
                Com_TxConfirmation
                  Com_TxIPduRunTimeState[comTxPduId].Transmiting = FALSE;

The last line is the whole point of this path. Com needs one bit of per-PDU state — Transmiting — because it has no idea when the hardware finishes. Set on send, cleared on confirmation. Without that bit, Com could not answer the only question it needs answered: is this PDU free to send again.

Note the two dispatches in this chain — CanIfTxPduUserTxConfirmationName resolves a configured callback name, then ModuleTxConfirmationApi dispatches again by module type. Both are generated code doing configuration lookups at runtime. That is why a wrong callback name in the config produces no compiler error and no link error: the function simply is not called.

Receive: polling, not interrupt

Reception mirrors sending but runs in the opposite direction, and it too is polled rather than interrupt-driven at this level:

Can_MainFunction_Read     ← CAN driver cyclic task
  Can_Ipw_MainFunction_Read
    Can_Ipw_ParseData     ← parses the frame payload
      CanIf_RxIndication
        CanIf_RxIndicationHandle
          CanIfRxPduUserRxIndicationName   ← resolves the upper-layer callback by name
            PduR_CanIfRxIndication
              PduR_IfRxIndication
                ModuleIfRxIndicationApi
                  Com_RxIndication
                    Com_CopyPduDataToRxBuffer   ← lands in the receive buffer first
                      Com_RxIPduRuntimeBuff

  ……〈main function takes over〉……

Com_MainFunctionRx
  Com_IndicationProcess   ← splits the PDU into signals
    Com_SignalRxIndication
      Com_RxSignalHandle  ← per-signal handling (filtering, RTE notify)

The same two-step split as sending, for the same reason: data lands in a buffer first, and a main function does the interpretation later. Com_CopyPduDataToRxBuffer moves bytes; Com_MainFunctionRx decides what they mean. If you are hunting a signal that arrives with the wrong value, the buffer copy is where bytes get placed and the main function is where bit positions get applied — two very different bugs that look identical from the application side.

Deadline monitoring hangs off the same main function. Com_MainFunctionRx calls Com_RxSignalTimeOutHandle, which fires ComTimeoutNotification and ends up in an RTE callback:

Com_MainFunctionRx
  Com_RxSignalTimeOutHandle
    ComTimeoutNotification
      Rte_COMCbkRxTOut_<Signal>_<IPdu>
Monitoring is switchable at runtime

Timeout supervision is not always on, and that is worth knowing before you conclude the monitor is broken. Com_ReceptionDMControl is driven from BswM via BSWM_DEADLINE_MONITOR_CONTROL, so a mode change can disable supervision for a set of PDUs. When a timeout callback never fires, check whether monitoring was switched off, before you check the timeout value.

Two flags that decide closure

Now the part I would hand to anyone debugging this stack. The confirmation path only exists if PduR was configured to carry it. Two separate parameters, both under the PduRRoutingPath, both must be true:

Parameter Where What it gates
PduRSrcPduUpTxConf PduRSrcPdu Whether the source module (Com) is told about completion
PduRTransmissionConfirmation PduRDestPdu Whether the destination side reports completion at all

Follow the consequence. If either is false, PduR_IfTxConfirmation has nothing to pass upward, Com_TxConfirmation is never called, and Transmiting stays TRUE. On a periodic PDU that means the first frame goes out and every frame after it is silently dropped — no DET, no error counter, nothing in the log. The bus is quiet and every layer claims success.

Derived, not observed

I want to be explicit: I did not build this failure on a bench. It is what the code path implies — the flag has exactly one clearer, Com_TxConfirmation, and that function has exactly one caller in this chain. A claim with that shape is safe to act on, but it is still a reading, and the first thing to do with it is confirm it in your own build rather than taking my word.

This is also the most useful diagnostic in the whole article. A frame that sends once and then stops is a very specific signature: it means the send path works end to end and the confirm path is broken somewhere. Compare it with a frame that never sends at all — that is a different failure, up in the mode or the timing, not down in the confirmation plumbing.

Where to look when a frame stops

A short decision order, built from the three-lane shape rather than from guesswork. Each step narrows which column the fault lives in.

Symptom First place to look Why
Never sends at all Com mode / IPduGroup / transmission mode PduR_UpModeTransmit gates the whole send path
Sends once, then stops The two PduR confirmation flags Transmiting never cleared
Sends, but the value is wrong Bit position / endianness, before the driver The byte copy and the bit extraction are different steps
Reception silent, timeout fires Filter / CanIf Rx PDU mapping The timeout proves the main function runs
No timeout either Com_ReceptionDMControl / BswM Monitoring may simply be switched off

Notice that four of the five rows point at configuration rather than code. That is the nature of this stack: the code is generated and correct, and the behaviour you are fighting lives in a checkbox three containers away from where the symptom appears.

Three traps worth naming

1. Com_TriggerTransmit is not for CAN

It appears in PduR next to the CAN entries and looks like the way to send immediately. It is not: it is the interface LIN and FlexRay use, where the lower layer pulls the data at its own schedule. On CAN, immediate transmission is a Com-side parameter (IMMEDIATE), not this API. Reaching for the wrong one produces code that links and does nothing.

2. Two dispatches are table lookups, not logic

PduRBswModuleType selects which module to call; Can_DriverApiType selects which driver. Both are generated tables. When a chain stops mid-way, a table lookup that resolved to the wrong entry looks exactly like a function that was never called — because from the symptom's side, both are silence.

3. The timeout value is not the monitor

ComTimeout is the deadline. Whether the deadline is being watched at all is a separate, runtime-switchable thing controlled through BswM. Tuning the timeout while the monitor is disabled is a very quiet way to spend a day.

The common thread: in a configured stack, the interesting information is almost never in the code. The code tells you what can happen; the configuration tells you what does.

Common questions

Why does Com_SendSignal not send the frame directly?

Because it only writes Com's own transmit buffer; pushing down to the CAN driver is Com_MainFunctionTx's job. The split is controlled by ComIPduSignalProcessing: with DEFERRED, SendSignal is decoupled from transmission and several signals can accumulate into one PDU and leave together; with IMMEDIATE it triggers the send path on the spot. DEFERRED compresses N signal updates into one bus access, at the cost of the caller no longer deciding when the frame leaves.

Why is TxConfirmation not part of the send call chain?

Because confirmation comes from hardware, and hardware finishes later than Can_Write returns. Can_Write only places the frame in a transmit mailbox; the completion callback fires after the CAN driver's own main function (Can_MainFunction_Write) polls the mailbox state, then travels up through CanIf_TxConfirmation and PduR_IfTxConfirmation to Com_TxConfirmation. The three paths are parallel, not nested.

How does Com know the previous frame has finished?

Through Com_TxIPduRunTimeState[comTxPduId].Transmiting. Set when sending, cleared inside Com_TxConfirmation. If confirmation never arrives the flag stays TRUE and that PDU never sends again — which surfaces as the first frame arriving normally and every frame after it disappearing.

Which PduR setting, if missed, loses the transmit confirmation?

Two, and both must be true: PduRSrcPduUpTxConf under PduRSrcPdu (whether the source module is told about completion) and PduRTransmissionConfirmation under PduRDestPdu (whether the destination reports completion). If either is false, PduR_IfTxConfirmation has nothing to pass up, Com_TxConfirmation is never called, and Transmiting stays TRUE.

Harry Zhao
AUTOMOTIVE EMBEDDED · AUTOSAR BSW / PLATFORM TOOLING

12 years in automotive embedded software, from ECU low-level drivers up to AutoSAR CP platform architecture: build flows, test scripting, Git servers, Jenkins CI/CD, and chip-level functional safety on Infineon TC397 and NXP S32K314. Reads generated code for a living, because that is where configuration goes to become behaviour.

Hand-written HTML / CSS · Cloudflare Workers ← All notes me.tech-harry.top