CAPL Programming Basics
Learn units and state before adding more events
CAPL resembles C in expressions and control flow, but its execution context and variable lifetime deserve separate attention. This lesson uses one keyboard-controlled state model to connect variables, functions, messages and timers. It is an independent exercise, not the door protocol from lesson 06.
Use a CANoe Simulation Setup node, CAN1 and classical CAN standard ID 0x210. There is no DBC dependency. Disable other senders of this ID and use an isolated configuration. The source below has not been compiled or executed here; resolve installed-version compiler diagnostics before running it.
Choose a type and name its unit
| Type or value | Meaning in this exercise | Common mistake |
|---|---|---|
byte |
Unsigned state 0 or 1 | Using subtraction at zero without a guard |
long |
Signed 32-bit arithmetic | Assuming every integer type has the same range |
dword |
Unsigned 32-bit value | Ignoring wraparound in a long-running counter |
message CAN1.0x210 |
A message object for an explicit channel and ID | Treating it as the last frame received |
msTimer |
Timer delay expressed in milliseconds | Declaring timer and assuming milliseconds |
timer |
Timer delay expressed in seconds | setTimer(t, 100) means 100 seconds for this type |
timeNow() |
Measurement time in ticks of 10 microseconds | 5,000 ticks = 50 ms; 50,000 ticks = 500 ms |
Declare a timer in the variables section when a separate on timer procedure needs to reference it. A timer declared only inside an on key block is not visible to that separate event procedure.
A complete state and timer exercise
Press u to request unlocked state (1), or l for locked state (0). The first command is accepted immediately. After acceptance, other commands are ignored until a 50 ms timer expires; rejected commands do not extend that interval. This is a minimum-spacing gate, not a physical switch-bounce model. Periodic state reports are requested every nominal 3 seconds.
variables
{
msTimer reportTimer;
msTimer debounceTimer;
byte unlocked;
byte debounceActive;
message CAN1.0x210 status;
}
void reportState()
{
status.RTR = 0;
status.DLC = 1;
status.byte(0) = unlocked;
output(status);
write("State output requested: unlocked=%d", unlocked);
}
int acceptCommand()
{
if (debounceActive != 0) return 0;
debounceActive = 1;
setTimer(debounceTimer, 50);
return 1;
}
on start
{
cancelTimer(reportTimer);
cancelTimer(debounceTimer);
unlocked = 0;
debounceActive = 0;
reportState();
setTimer(reportTimer, 3000);
}
on key 'u'
{
if (acceptCommand() == 0)
{
write("Unlock ignored: 50 ms gate active");
return;
}
unlocked = 1;
reportState();
}
on key 'l'
{
if (acceptCommand() == 0)
{
write("Lock ignored: 50 ms gate active");
return;
}
unlocked = 0;
reportState();
}
on timer debounceTimer
{
debounceActive = 0;
}
on timer reportTimer
{
reportState();
setTimer(reportTimer, 3000);
}
The function acceptCommand() returns a decision; the key event performs the state change. The timer event only opens the gate again. Sharing one gate means a lock command shortly after an unlock is also rejected. If you need separate limits per command, define that requirement before changing the state variables.
setTimer schedules a one-shot expiration. Only the report event rearms itself, so reporting repeats while the gate expires once. Relative rearming does not guarantee a drift-free absolute schedule. See periodic timers for measuring the difference.
output(status) requests transmission and does not confirm reception. Check channel, RTR, DLC and data in Trace and in a separate receiving observer. The Write line deliberately describes a request.
Local scope is different from variable lifetime
CAPL local variables retain their values across calls; declaration initialization is not a per-call reset. For a fresh accumulation, assign explicitly inside the function:
long sumBytes(byte values[])
{
long total;
long i;
total = 0;
for (i = 0; i < elcount(values); i++)
{
total = total + values[i];
}
return total;
}
This standalone function fragment assumes a small array whose sum fits long. Calling it twice with the same array should return the same result. Removing total = 0 is a deliberate negative exercise: the accumulator can carry state across calls. Separate nodes do not share variables merely because those variables have the same name.
Raw signal values and physical values
With a DBC, msg.Rpm accesses the raw signal value; msg.Rpm.phys requests physical interpretation. For a hypothetical signal with factor 0.25 and offset 0, raw 8000 represents 2000 rpm. Assigning raw 2000 represents 500 rpm, not 2000 rpm. Check the actual signal's factor, offset, range, encoding and rounding; these numbers are a worked example, not a supplied database.
// Fragment: requires EngineData.Rpm with the stated DBC scaling.
message EngineData msg;
msg.Rpm.phys = 2000.0;
Setting a signal does not establish that the message was sent, and reading a cached signal does not establish freshness. Keep those checks separate.
Acceptance worksheet
| Experiment | Expected result | What it distinguishes |
|---|---|---|
| Start measurement | State 0; one initial report requested | Explicit initialization |
Issue the first u command |
State 1 immediately | No initial 50 ms lockout |
Inject l clearly inside the next 50 ms |
State remains 1 | Shared gate blocks a second command |
Issue l after gate expiration |
State 0 | One-shot timer reopened the gate |
| Observe without input | Nominal 3-second reports | Rearmed reporting timer |
| Stop and restart | State resets to 0 | State does not leak between exercises |
Human key presses are unsuitable for a precise 49/51 ms boundary test. Use a controlled event sequence and preserve timestamps; avoid drawing a timing conclusion from events exactly at the expiration boundary, where event ordering matters. These are expected results to verify, not a recorded passing run.
The unit and lifetime rules follow the authorized Vector reference for setTimer, timeNow, variable initialization and symbolic signal access. The gate design, scaling calculation and worksheet are this site's analysis. Continue with bus interaction.