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.
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.

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.
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>
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.
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.