CAPL Programming Basics
Learning Objectives
After completing this article, you will be able to:
- Quickly identify syntax similarities and differences between CAPL and C language
- Proficiently use CAPL-specific data types (message, timer, msTimer)
- Understand and use system functions and custom functions
- Grasp the basic concepts of event-driven programming model, laying the foundation for deeper learning in the next article
- Write simple CAPL programs to solve practical requirements
In the previous two articles, we've learned about CAPL concepts and created our first program. Now it's time to dive deep into CAPL's syntax world!
If you have a C language background, you'll be delighted to discover that CAPL's syntax is 80% the same as C! But it's precisely that 20% difference that constitutes the essence of CAPL. Let's explore together.
Quick Review of Basic Syntax
Similarities with C Language
First, let's confirm those familiar old friends:
/*
* EXAMPLE 1: Basic Syntax - Same as C Language
*/
void example1_basicSyntax()
{
int localCounter;
long engineSpeed;
byte localDoorStatus;
int i;
localCounter = 0;
engineSpeed = 1500;
localDoorStatus = 0;
localCounter++;
engineSpeed = engineSpeed * 2;
if (localDoorStatus == 1)
{
write("Door is open");
}
else
{
write("Door is closed");
}
for (i = 0; i < 10; i++)
{
write("Loop iteration: %d", i);
}
}
See? These look completely identical to C!
Data Type Comparison
Most data types from C language remain consistent in CAPL, but CAPL adds specialized types for automotive network characteristics. Let's look at a comparison:
Standard Data Types in CAPL:
| CAPL Type | Size | Range | C Language Equivalent |
|---|---|---|---|
char |
1 byte | -128 ~ +127 | char |
byte |
1 byte | 0 ~ 255 | unsigned char |
int |
2 bytes | -32768 ~ +32767 | int |
word |
2 bytes | 0 ~ 65535 | unsigned int |
long |
4 bytes | -2147483648 ~ +2147483647 | long |
dword |
4 bytes | 0 ~ 4294967295 | unsigned long |
double |
8 bytes | Same as C | double |
Why does CAPL distinguish between int and word?
In automotive networks, we often need to handle:
- Raw CAN data (usually unsigned values 0~255) → Use
byteorword - Signed numeric values (like temperature, pressure which can be negative) → Use
intorlong
CAPL Feature #1: Functions
System Functions (Built-in Functions)
CAPL provides a large number of predefined system functions - these are CAPL's "superpowers":
// Most commonly used system functions
output(message); // Send a message to CAN bus
write(char[]); // Write to Write window
setTimer(timer, time); // Start a timer
cancelTimer(timer); // Cancel a timer
getSignal(signal); // Get signal value (Signal type parameter)
setSignal(signal, value); // Set signal value (Signal type and double value)
Example: Using system functions to send CAN messages
on key 's'
{
// Create a CAN message with ID 0x100
message 0x100 msg;
// Set message data
msg.byte(0) = 0x01; // First byte = 1
msg.byte(1) = 0xFF; // Second byte = 255
// Send it to the bus
output(msg);
write("Message sent successfully!");
}

Custom Functions
Defining custom functions is almost the same as in C, but there's one key difference: CAPL doesn't have the concept of function prototypes (header files)!
// ❌ C language style (not needed in CAPL)
int calculateChecksum(byte data[]);
int calculateChecksum(byte data[])
{
// implementation
}
// ✅ CAPL style
int calculateChecksum(byte data[])
{
int sum = 0;
for (int i = 0; i < elcount(data); i++)
{
sum += data[i];
}
return sum;
}
Function Overloading
CAPL supports function overloading! Same function name, different parameter lists:
// These functions can coexist
void sendMessage(message msg);
void sendMessage(message msg, int delay);
void sendMessage(dword id, byte data[]);
Function Parameters: Pass-by-Reference
CAPL supports C++-style reference parameters:
// Using reference parameter
void incrementValue(int& value)
{
value++; // Modifies the original variable
}
on key 'i'
{
int counter = 5;
write("Before: %d", counter); // Output: Before: 5
incrementValue(counter);
write("After: %d", counter); // Output: After: 6
}
Note: In CAPL, function parameters are pass-by-value by default. If you need to modify the original variable, you need to use reference parameters (&). However, please note that reference parameters may have compatibility issues in some CAPL versions. In actual development, the more common approach is to directly modify the variable or use return values.
Example: Pass-by-value (more commonly used approach)
// Using pass-by-value
void incrementValue(int value)
{
value = value + 1;
write("Value inside function: %d", value);
}
on key 'i'
{
int testCounter = 5;
write("Before: %d", testCounter);
incrementValue(testCounter);
write("After: %d (unchanged - pass by value)", testCounter);
}
CAPL Feature #2: Special Data Types
message Type: CAN Message Container
message is the most important data type in CAPL, representing messages on the CAN bus.
Declaring message variables:
// Method 1: By ID
message 0x200 lockCmd; // Message with ID 0x200
message 512 doorStatus; // Message with ID 512 (decimal)
// Method 2: From database
message EngineData engineMsg; // From DBC file
// Method 3: With initialization
message 0x100 msg = {dlc = 4, word(0) = 0x1234};
Accessing message data:
message 0x100 msg;
// Set DLC (Data Length Code)
msg.dlc = 8;
// Set individual bytes
msg.byte(0) = 0x01;
msg.byte(1) = 0x02;
msg.byte(2) = 0x03;
// Or use word/dword for multi-byte access
msg.word(0) = 0x1234; // bytes 0-1
msg.dword(0) = 0x12345678; // bytes 0-3
// Send the message
output(msg);
Powerful features when integrated with DBC databases:
If your project has a DBC database, you can directly use signal names:
message EngineData msg;
// Set signal values directly (physical values)
msg.Rpm = 2500.0; // RPM signal
msg.Temp = 85.5; // Temperature signal
// CAPL will automatically handle encoding!
output(msg);
timer and msTimer Types: Time Control
Timers are at the core of CAPL programming, used to implement periodic tasks and delayed operations.
Difference between timer vs msTimer:
timer: Unit is secondsmsTimer: Unit is milliseconds (more precise)
Complete workflow for using timers:
variables
{
msTimer sendTimer; // Declare timer variable
int counter = 0;
}
on start
{
// Start timer when measurement begins
setTimer(sendTimer, 1000); // Trigger every 1000ms (1 second)
}
on timer sendTimer
{
// This runs every time the timer expires
counter++;
write("Timer triggered %d times", counter);
// Restart the timer for next cycle
setTimer(sendTimer, 1000);
}
on key 'x'
{
// Cancel timer
cancelTimer(sendTimer);
write("Timer cancelled");
}
One-shot timer vs repeating timer:
on key 'a'
{
msTimer oneShotTimer;
// One-shot timer: only triggers once
setTimer(oneShotTimer, 500); // Triggers after 500ms, then stops
write("Timer started, will trigger once in 500ms");
}
on timer oneShotTimer
{
write("One-shot timer executed!");
}
Timer in action: Simulating periodic door status reporting
variables
{
msTimer statusTimer;
byte doorState = 0; // 0=closed, 1=open
}
on start
{
setTimer(statusTimer, 2000); // Report every 2 seconds
}
on timer statusTimer
{
message 0x200 doorStatus;
doorStatus.dlc = 1;
doorStatus.byte(0) = doorState;
output(doorStatus);
write("Door status sent: %s", (doorState == 1) ? "OPEN" : "CLOSED");
// Toggle state for next cycle
doorState = (doorState == 1) ? 0 : 1;
// Reset timer
setTimer(statusTimer, 2000);
}
CAPL Feature #3: Events
CAPL is an event-driven language, which is its biggest difference from C. In C, programs start from main() and execute sequentially; in CAPL, programs "wait" for events to occur, then execute the corresponding event handler functions.
We've already seen several events:
on start // Measurement starts
on stopMeasurement // Measurement stops
on key 'a' // Keyboard key 'a' pressed
on timer t1 // Timer t1 expired
on message 0x100 // Message with ID 0x100 received
The event-driven programming paradigm is very suitable for automotive networks:
- Automotive ECUs spend most of their time "waiting": waiting for CAN messages, waiting for sensor input, waiting for timers to expire
- Event handling makes program logic clearer: when an event occurs, do what needs to be done
In the next article "Core Interaction - Talking with the Bus World", we'll dive deep into various event handling mechanisms.
Practical Exercise: Comprehensive Application
Now, let's integrate what we've learned and write a more practical program.
Exercise Goal: Write a function that simulates a simplified door control module, supporting:
- Manual lock/unlock triggering
- Periodic door status reporting
- Debounce handling (avoiding frequent operations)
Complete Code:
variables
{
msTimer reportTimer;
msTimer debounceTimer;
// Door state: 0=locked, 1=unlocked
byte doorState = 0;
// Debounce flag
dword lastCommandTime = 0;
}
// Function: Send door status to CAN bus
void sendDoorStatus()
{
message 0x200 status;
status.dlc = 1;
status.byte(0) = doorState;
output(status);
write("Door status sent: %s", (doorState == 1) ? "UNLOCKED" : "LOCKED");
}
// Function: Debounce control
int checkDebounce()
{
dword currentTime;
currentTime = timeNow(); // Get current time
// If less than 50ms since last command, ignore
if ((currentTime - lastCommandTime) < 50000) // 50ms debounce time
{
write("Command ignored - debounce active");
return 0; // Rejected
}
lastCommandTime = currentTime;
return 1; // Accepted
}
// Event: Measurement started
on start
{
// Start periodic status reporting
setTimer(reportTimer, 3000); // Every 3 seconds
write("Door control module initialized");
sendDoorStatus(); // Send initial status
}
// Event: Periodic status report
on timer reportTimer
{
sendDoorStatus();
setTimer(reportTimer, 3000); // Reset timer
}
// Event: Lock command (press 'l')
on key 'l'
{
if (checkDebounce() == 1)
{
doorState = 0; // Lock
write("Door locked manually");
sendDoorStatus();
}
}
// Event: Unlock command (press 'u')
on key 'u'
{
if (checkDebounce() == 1)
{
doorState = 1; // Unlock
write("Door unlocked manually");
sendDoorStatus();
}
}
// Event: Stop measurement
on stopMeasurement
{
write("Door control module stopped");
}
Code Breakdown:
- Function encapsulation:
sendDoorStatus()abstracts the status sending logic,checkDebounce()handles debouncing - Event division of labor:
on starthandles initialization,on timerhandles periodic tasks,on keyhandles manual control - Time handling:
timeNow()gets nanosecond-level timestamps for precise debounce control - State management: The
doorStatevariable maintains the module's internal state
Try running this program:
- Press
lkey to lock - Press
ukey to unlock - Observe automatic status reporting every 3 seconds
- Test debounce function by pressing keys rapidly
Article Summary
Through this article's learning, we've mastered:
- Syntax basics: The 80% similarity between CAPL and C language, and the key 20% differences
- Data types:
messagefor CAN messages,timer/msTimerfor time control - Function features: System functions, custom functions, function overloading, reference parameters
- Event-driven programming: CAPL's core programming paradigm, preparing for deeper learning in the next article
CAPL's syntax maintains C language's conciseness while adding specialized data types and functions for automotive network characteristics. With these basics mastered, you now have the ability to write practical CAPL programs!
Exercises
-
Extension Exercise 1: Add a new feature to the above door control module: when the
skey is pressed, simulate a door sensor fault (send an error status value) and observe the system's response. -
Extension Exercise 2: Create a program using
timerto implement PWM signal simulation (high-frequency switching between two different CAN messages). -
Thought Question: Why are local variables in CAPL statically allocated? What impact does this have?
-
Preview Task: Read the preview for the next article "Core Interaction - Talking with the Bus World" to understand the powerful capabilities of
on messageevents.