CAPL编程基础
先弄清单位和状态,再增加事件
CAPL 的表达式和控制流与 C 相似,但运行环境、变量生命周期需要单独理解。本篇用一个按键控制的状态模型,把变量、函数、报文和定时器连接起来。这是独立练习,不是第 06 篇的车门协议。
使用 CANoe Simulation Setup 节点、CAN1、经典 CAN 标准 ID 0x210,不依赖 DBC。关闭相同 ID 的其他发送源,在隔离配置中练习。以下代码尚未在本站编译或运行;先处理安装版本编译器给出的诊断,再启动测量。
选择类型时同时写清单位
| 类型或数值 | 本例含义 | 容易犯的错误 |
|---|---|---|
byte |
无符号状态 0 或 1 | 在零值上减法后才检查边界 |
long |
有符号 32 位运算 | 认为所有整数类型范围相同 |
dword |
无符号 32 位数值 | 忽略长时间计数后的回绕 |
message CAN1.0x210 |
指定通道和 ID 的报文对象 | 误认为它自动保存最近收到的报文 |
msTimer |
以毫秒指定延时 | 声明成 timer 后仍按毫秒理解 |
timer |
以秒指定延时 | 此类型的 setTimer(t, 100) 是 100 秒 |
timeNow() |
单位为 10 微秒的测量时间计数 | 5,000 个计数为 50 ms;50,000 个为 500 ms |
单独的 on timer 事件需要引用的定时器,应声明在 variables 中。只在 on key 局部声明的定时器,对另一个事件过程不可见。
一个完整的状态与定时器练习
按 u 请求解锁状态 1,按 l 请求锁定状态 0。首次命令立即接受;之后直到 50 ms 定时器到期,忽略其他命令。被拒命令不会延长这一间隔。这是最小命令间隔限制,不是物理开关抖动模型。状态报文另按名义 3 秒周期请求发送。
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);
}
acceptCommand() 返回是否接受,按键事件执行状态变化,定时器事件只解除间隔限制。共用一个标志意味着解锁后很快到来的锁定也会被拒。如果需要对两种命令分别限频,应先定义要求,再修改状态变量。
setTimer 安排一次到期。只有报告事件重新设置自己,因此报告重复,间隔限制只到期一次。相对重设不能保证无漂移的绝对时间表,测量方法见周期定时器。
output(status) 只是请求发送,不确认接收成功。应在 Trace 和独立接收观察点检查通道、RTR、DLC 与数据。Write 日志因此明确写成 output requested。
局部作用域不等于每次调用重新初始化
CAPL 局部变量的值会在调用之间保留,声明时初始化不是每次调用都执行的清零操作。需要每次重新累加时,在函数中显式赋值:
long sumBytes(byte values[])
{
long total;
long i;
total = 0;
for (i = 0; i < elcount(values); i++)
{
total = total + values[i];
}
return total;
}
这个独立函数片段假定输入数组较小,总和不超出 long。相同数组调用两次应返回相同结果。可把删除 total = 0 作为反例:累加器可能保留上次值。不同节点中同名的变量也不会因此成为共享状态。
原始信号值与物理值
有 DBC 时,msg.Rpm 访问原始信号值;msg.Rpm.phys 表达物理值。假设某信号的系数为 0.25、偏移为 0,原始值 8000 才表示 2000 rpm。直接赋原始值 2000 表示 500 rpm。必须检查实际信号的系数、偏移、范围、编码与取整规则;这里是手算示例,没有提供对应数据库。
// Fragment: requires EngineData.Rpm with the stated DBC scaling.
message EngineData msg;
msg.Rpm.phys = 2000.0;
设置信号不能证明报文已经发出,读取缓存信号也不能证明数据新鲜。两者需要独立检查。
验收记录表
| 操作 | 待验证预期 | 区分的问题 |
|---|---|---|
| 开始测量 | 状态 0,请求一次初始报告 | 是否显式初始化 |
首次输入 u |
立即变成状态 1 | 没有初始 50 ms 禁入期 |
在接下来 50 ms 内明确注入 l |
保持状态 1 | 共用间隔限制生效 |
到期后输入 l |
状态 0 | 一次性定时器解除限制 |
| 不输入命令 | 名义每 3 秒报告 | 报告定时器重新设置 |
| 停止再启动 | 恢复状态 0 | 练习之间不携带状态 |
人工按键不适合精确的 49/51 ms 边界测试。应使用受控事件序列并保留时间戳;不要用恰好落在到期时刻的事件推断边界,因为事件处理顺序会影响结果。以上是待执行预期,不是通过记录。
单位与生命周期规则依据授权 Vector 参考中的 setTimer、timeNow、变量初始化和符号信号访问说明。间隔控制设计、缩放计算与验收表为本站分析。下一篇:与总线的核心交互。