CAPL Script

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 byte or word
  • Signed numeric values (like temperature, pressure which can be negative) → Use int or long

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!");
}

CAPL Script: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 seconds
  • msTimer: 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:

  1. Manual lock/unlock triggering
  2. Periodic door status reporting
  3. 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:

  1. Function encapsulation: sendDoorStatus() abstracts the status sending logic, checkDebounce() handles debouncing
  2. Event division of labor: on start handles initialization, on timer handles periodic tasks, on key handles manual control
  3. Time handling: timeNow() gets nanosecond-level timestamps for precise debounce control
  4. State management: The doorState variable maintains the module's internal state

Try running this program:

  • Press l key to lock
  • Press u key 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:

  1. Syntax basics: The 80% similarity between CAPL and C language, and the key 20% differences
  2. Data types: message for CAN messages, timer/msTimer for time control
  3. Function features: System functions, custom functions, function overloading, reference parameters
  4. 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

  1. Extension Exercise 1: Add a new feature to the above door control module: when the s key is pressed, simulate a door sensor fault (send an error status value) and observe the system's response.

  2. Extension Exercise 2: Create a program using timer to implement PWM signal simulation (high-frequency switching between two different CAN messages).

  3. Thought Question: Why are local variables in CAPL statically allocated? What impact does this have?

  4. Preview Task: Read the preview for the next article "Core Interaction - Talking with the Bus World" to understand the powerful capabilities of on message events.