CAPL Script

CAPL编程基础

学习目标

完成本篇后,你将能够:

  • 快速识别 CAPL 与 C 语言的语法异同点
  • 熟练使用 CAPL 特有数据类型(message、timer、msTimer)
  • 理解并使用系统函数和自定义函数
  • 掌握事件驱动模型的基本概念,为下一篇深入学习打下基础
  • 编写简单的 CAPL 程序解决实际需求

在前两篇中,我们已经了解了 CAPL 的概念,并创建了第一个程序。现在,是时候深入 CAPL 的语法世界了!

如果你有 C 语言背景,你会惊喜地发现:CAPL 的语法 80% 与 C 相同!但正是那 20% 的差异,构成了 CAPL 的精髓。让我们一起探索。

基础语法快速回顾

与 C 语言的共同点

首先,让我们确认那些熟悉的老朋友:

/*
 * 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);
  }
}

看到了吗?这些看起来和 C 完全一样!

数据类型的对比

C 语言中的数据类型在 CAPL 中大部分保持一致,但 CAPL 针对汽车网络的特点,增加了专门的类型。让我们看一个对比:

CAPL 中的标准数据类型

CAPL 类型 大小 范围 C 语言对应
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 同 C double

为什么 CAPL 要区分 intword

在汽车网络中,我们经常需要处理:

  • 原始 CAN 数据(通常是 0~255 的无符号值)→ 用 byteword
  • 有符号数值(如温度、压力可能为负)→ 用 intlong

CAPL 特色之一:函数

系统函数(Built-in Functions)

CAPL 提供了大量预定义的系统函数,这些是 CAPL 的"超能力":

// 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)

例子:使用系统函数发送 CAN 消息

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

自定义函数

定义自定义函数的方式与 C 几乎相同,但有一个关键差异:CAPL 没有函数原型(header files)的概念!

// ❌ C 语言的写法(在 CAPL 中不需要)
int calculateChecksum(byte data[]);
int calculateChecksum(byte data[])
{
    // implementation
}

// ✅ CAPL 的写法
int calculateChecksum(byte data[])
{
    int sum = 0;
    for (int i = 0; i < elcount(data); i++)
    {
        sum += data[i];
    }
    return sum;
}

函数重载(Function Overloading)

CAPL 支持函数重载!同一函数名,不同参数列表:

// 这些函数可以共存
void sendMessage(message msg);
void sendMessage(message msg, int delay);
void sendMessage(dword id, byte data[]);

函数参数:引用传递

CAPL 支持 C++ 风格的引用参数:

// 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
}

注意:在 CAPL 中,函数参数默认是值传递。如果需要修改原始变量,需要使用引用参数(&)。但是请注意,引用参数在某些 CAPL 版本中可能有兼容性问题。在实际开发中,更常见的方式是直接修改变量或使用返回值。

示例:值传递(更常用的方式)

// 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 特色之二:特殊数据类型

message 类型:CAN 消息的载体

message 是 CAPL 中最重要的数据类型,代表 CAN 总线上的消息。

声明 message 变量

// 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};

访问 message 数据

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);

与 DBC 数据库集成后的强大功能

如果你的工程中有 DBC 数据库,可以直接使用信号名:

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 和 msTimer 类型:时间控制

定时器是 CAPL 编程的核心,用于实现周期性任务和延迟操作。

timer vs msTimer 的区别

  • timer:以秒为单位
  • msTimer:以毫秒为单位(更精确)

使用定时器的完整流程

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

一次性定时器 vs 重复定时器

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

定时器实战:模拟车门状态周期上报

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 特色之三:事件

CAPL 是事件驱动的语言,这是它与 C 最大的不同。在 C 中,程序从 main() 开始顺序执行;而在 CAPL 中,程序"等待"事件发生,然后执行相应的事件处理函数。

我们已经见过几个事件:

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

事件驱动的编程范式非常适合汽车网络:

  • 车载 ECU 大部分时间在"等待":等待 CAN 消息、等待传感器输入、等待定时器到期
  • 事件处理让程序逻辑更清晰:什么事件发生,就做什么事

在下一篇《核心交互——与总线世界对话》中,我们将深入探讨各种事件处理机制。

实践练习:综合运用

现在,让我们把学到的知识整合起来,编写一个更实用的程序。

练习目标:编写一个函数,模拟一个简化的车门控制模块,支持:

  1. 手动触发上锁/解锁
  2. 周期性上报车门状态
  3. 防抖处理(避免频繁操作)

完整代码

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

代码解读

  1. 函数封装sendDoorStatus() 抽象了状态发送逻辑,checkDebounce() 处理防抖
  2. 事件分工on start 负责初始化,on timer 负责周期任务,on key 负责手动控制
  3. 时间处理timeNow() 获取纳秒级时间戳,实现精确的防抖控制
  4. 状态管理doorState 变量维护模块的内部状态

试着运行这个程序:

  • l 键上锁
  • u 键解锁
  • 每 3 秒观察一次自动状态上报
  • 快速连续按键测试防抖功能

本篇总结

通过本篇的学习,我们掌握了:

  1. 语法基础:CAPL 与 C 语言的 80% 相似性,以及 20% 的关键差异
  2. 数据类型message 用于 CAN 消息,timer/msTimer 用于时间控制
  3. 函数特性:系统函数、自定义函数、函数重载、引用参数
  4. 事件驱动:CAPL 的核心编程范式,为下一篇深入学习做准备

CAPL 的语法既保持了 C 语言的简洁,又针对汽车网络的特点增加了专门的数据类型和函数。掌握这些基础知识,你已经具备了编写实用 CAPL 程序的能力!

课后练习

  1. 扩展练习 1:为上述车门控制模块添加一个新功能:按下 s 键,模拟一次车门传感器故障(发送错误状态值),观察系统的反应。

  2. 扩展练习 2:创建一个使用 timer 的程序,实现 PWM 信号模拟(高频切换两个不同的 CAN 消息)。

  3. 思考题:为什么 CAPL 中局部变量是静态分配的?这会带来什么影响?

  4. 预习任务:阅读下一篇《核心交互——与总线世界对话》的预告,了解 on message 事件的强大功能。