CAPL 编程系列第二十五期-第三十期回顾答案与分析

第二十五期:常用内置函数之读取文本文件 (Reading Text Files)

  1. 函数与参数: openFileRead(char fileName[], int mode). fileName 是文件名(含路径),mode 指定模式(0=文本模式,1=二进制模式)。
  2. 检查成功: 检查 openFileRead 的返回值(文件句柄 dword)。如果返回值为 0,表示打开失败;非零值表示成功,该值是后续文件操作所需的句柄。
  3. fileGetString vs fileGetStringSZ: fileGetString 读取到换行符 \n 或文件尾 (EOF),但不包含换行符;fileGetStringSZ 功能类似,但会包含换行符(如果读取到且缓冲区够大),并且它保证了缓冲区始终以空字符 \0 结尾,更安全。
  4. EOF 检测: 使用函数 fileIsEOF(dword fileHandle)。在尝试读取前调用此函数,如果返回 1,则表示已到达文件末尾。
  5. 循环读取示例:
    Code snippet
    dword hnd;
    char line[256];
    hnd = openFileRead("data.txt", 0);
    if (hnd != 0) {
      while (fileIsEOF(hnd) == 0) {
        fileGetStringSZ(line, elcount(line), hnd);
        write("Read: %s", line); // line 可能包含换行符
      }
      fileClose(hnd);
    } else { write("Failed to open file."); }
    
  6. 返回 0: 表示文件打开失败,原因可能是文件不存在、路径错误或没有读取权限。
  7. fileClose 重要性: 释放文件句柄和相关系统资源,避免资源泄漏。确保操作系统知道文件不再被该程序使用。
  8. Mode 0 vs 1: Mode 0 (文本) 会根据操作系统处理行尾符(例如 \r\n 可能只读作 \n)。Mode 1 (二进制) 读取文件的原始字节流。读取 .txt 文件通常使用 Mode 0。
  9. fileGetString 内容: 缓冲区将包含 "Line1"fileGetString 将换行符视为分隔符,不会将其放入缓冲区。
  10. 逻辑推理 (Config File):
    • 使用 openFileRead("config.txt", 0) 打开文件,检查句柄。
    • 循环 while(fileIsEOF(handle) == 0)
    • 在循环内用 fileGetStringSZ(buffer, size, handle) 读取一行。
    • 使用 strstr(buffer, "Threshold=") 检查行内是否包含目标关键字。
    • 如果找到,定位到 '=' 后面,使用 atol()strtol() (第二十九期内容) 转换数值部分为整数。
    • 保存该整数值。
    • 读取完毕或找到所需值后,用 fileClose(handle) 关闭文件。

第二十六期:常用内置函数之写入文本文件 (Writing Text Files)

  1. 函数与模式: openFileWrite(char fileName[], int mode). Mode 0 = 覆盖写入(文件不存在则创建);Mode 2 = 追加写入(文件不存在则创建)。
  2. filePutString vs write: filePutString(string, handle) 将字符串写入指定文件句柄代表的文件;write(format, ...) 将格式化信息输出到 CANoe 的 Write 窗口。filePutString 不会自动添加换行符,需要手动在字符串中加入 \n
  3. 追加代码:
    Code snippet
    dword hnd;
    hnd = openFileWrite("log.txt", 2); // Mode 2 for append
    if (hnd != 0) {
      filePutString("Test complete.\n", hnd); // 加入 \n 换行
      fileClose(hnd);
    } else { write("Failed to open for append."); }
    
  4. 覆盖已存在文件: 文件原有内容将被清空。
  5. 追加不存在文件: 系统会创建一个新的空文件,然后开始写入。
  6. filePutString 返回值: 返回成功写入的字符数。可用于检查写入是否完整,但通常更关注文件是否成功打开。
  7. fileClose 重要性 (写入): 非常重要。确保所有内部缓冲区的数据被实际写入(刷新)到磁盘文件,否则可能导致数据丢失或文件不完整。
  8. 路径: 相对路径(如 "mylog.txt")是相对于 CANoe 当前配置的工作目录。绝对路径(如 "C:\\Logs\\mylog.txt")是完整的文件系统路径。
  9. 写入变量值: 需要先用 snprintf (第三十期) 将文本和变量值格式化成一个字符串,存入字符数组,然后用 filePutString 将该数组内容写入文件。
  10. 逻辑推理 (Event Log):
    • on key 's' 事件处理代码块中。
    • 获取当前时间戳(如使用 timeNow())。
    • 使用 snprintf 将时间戳和事件描述("Key 's' pressed")格式化到一个缓冲区 logBuffer 中,记得末尾加 \n
    • hnd = openFileWrite("event_log.txt", 2) 打开文件以追加模式,检查句柄 hnd
    • filePutString(logBuffer, hnd) 写入。
    • fileClose(hnd) 关闭。

第二十七期:常用内置函数之日志启停控制 (基础)

  1. 启动所有: startLogging()
  2. 停止所有: stopLogging()
  3. 配置位置: CANoe 的 "Measurement Setup" 窗口,在 "Logging" 分支下添加和配置 "Logging Block"。
  4. CAPL 控制模式: 设置为 "Triggered" 或 "Triggered (Single Trigger)"。
  5. 典型场景: 例如,只在检测到特定错误帧时才开始记录,或者在执行某个自动化测试序列的关键阶段进行记录,以减小日志文件大小。
  6. 重复启动: 如果对应 Logging Block 已在记录状态,再次调用 startLogging() 通常没有额外效果。
  7. 两个块,一次调用: 如果两个块都设置为 "Triggered (Single Trigger)",调用一次 startLogging() 会同时触发这两个块开始记录。
  8. 视觉确认: 在 Measurement Setup 中,活动(正在记录)的 Logging Block 通常会有一个状态图标(如红点)或背景色变化。
  9. 控制范围: 无参数的 startLogging()stopLogging() 控制所有配置为 "Triggered" 或 "Triggered (Single Trigger)" 模式的 Logging Block。
  10. 逻辑推理 (Event + Time): 使用基础函数,需在 Logging Block 配置中预设 Pre-trigger 和 Post-trigger 时间(例如,Pre=1000ms, Post=5000ms),并将模式设为 "Triggered (Single Trigger)"。在 on key 'x' 中只需调用 startLogging() 即可。CANoe 会根据配置的时间自动处理触发点前后的数据记录。

第二十八期:常用内置函数之高级日志控制

  1. 启动特定: startLogging("SensorLog"); (如果需要预触发,则用 startLogging("SensorLog", preTriggerTimeMs);)
  2. 停止特定: stopLogging("SensorLog"); (如果需要后触发,则用 stopLogging("SensorLog", postTriggerTimeMs);)
  3. preTriggerTimeMs: 预触发时间(毫秒)。指定在调用 startLogging 之前 多长时间的数据(来自 CANoe 的内部环形缓冲区)应该被包含在日志文件中。
  4. postTriggerTimeMs: 后触发时间(毫秒)。指定在调用 stopLogging 之后 还要继续记录多长时间的数据,然后才真正停止。
  5. 环形缓冲区与预触发: CANoe 在内存中维护一个环形缓冲区,持续存储最近的总线活动。调用带预触发时间的 startLogging 时,CANoe 会从这个缓冲区中提取触发点前相应时间段的数据写入日志文件,然后继续记录实时数据。
  6. 带预触发启动: startLogging("HighSpeedData", 2000);
  7. 带后触发停止: stopLogging("HighSpeedData", 500);
  8. 时间范围: 日志将包含 startLogging 调用前 1000ms 的数据,startLoggingstopLogging 调用之间的所有数据,以及 stopLogging 调用后 2000ms 的数据。
  9. 无效名称: 函数调用通常会失败(可能在 Write 窗口输出错误信息),但不会影响其他 Logging Block 或导致 CAPL 脚本崩溃。
  10. 逻辑推理 (Engine Logging):
    Code snippet
    on sysvar EngineState {
      if (this == 1) { // 假设 1 表示启动
        // 检查是否是从 0 变为 1 的转换(需要额外变量跟踪先前状态)
        // 假设已确认是启动事件
        startLogging("EngineStartLog", 1000); // 记录启动前 1 秒
        setTimer(EngineStopLogTimer, 5); // 设置 5 秒后停止的计时器
      }
    }
    
    on timer EngineStopLogTimer {
      stopLogging("EngineStartLog", 0); // 5 秒后立即停止(无额外后触发)
    }
    

第二十九期:常用内置函数之数据类型转换

  1. ltoa 作用与参数: long val, char buffer[], int base。将长整型 val 转换为指定 base(进制)的字符串,并存入 buffer
  2. ltoa 进制: base=10 表示十进制,base=16 表示十六进制。ltoa(255, buf, 10) 结果是 "255";ltoa(255, buf, 16) 结果是 "ff" (小写)。
  3. 字符串转 Long: atol(char str[])。能自动识别 "0x" 或 "0X" 前缀来处理十六进制字符串。
  4. 字符串转 Double: atodbl(char str[]).
  5. atol 示例: atol("1A") 通常返回 0 或未定义,因为它默认解析十进制。atol("0x1A") 返回 26。
  6. atodbl 示例: 返回 -123.45。它会解析到第一个非有效浮点数字符('x')为止。
  7. atol vs strtol: atol 简单,仅返回转换结果(或0/错误)。strtol(str, &endptr, base) 更强大:明确指定进制 base,通过 endptr 返回未解析部分的指针,便于错误检查和连续解析。strtod 对应 atodbl 也有类似增强版。
  8. 解析多个值: 使用 strtol/strtod。第一次调用后,检查 endptr 是否指向有效字符,然后以 endptr 作为下一次调用的起始字符串指针,重复此过程。
  9. 整数转字符串: char buffer[10]; ltoa(100, buffer, 10);
  10. 逻辑推理 (Parsing Line):
    • strstr(line, "Timestamp=") 找到时间戳标签。将指针移到 '=' 之后。
    • 对该指针调用 atol()strtol(ptr, &endptr, 10) 提取时间戳长整型。
    • strstr(line or endptr, "Value=") 找到值标签。将指针移到 '=' 之后。
    • 对该指针调用 atodbl()strtod(ptr, NULL) 提取值浮点型。

第三十期:常用内置函数之字符串处理

  1. strlen vs mbslen: strlen("你好") 返回字节数(如 UTF-8 下是 6)。mbslen("你好") 返回字符数(是 2)。mbslen 给出字符数量。
  2. strncat 作用与安全: 将源字符串追加到目标字符串。第三个参数 n (应为目标缓冲区剩余空间大小,如 elcount(dest) - strlen(dest) - 1) 用于限制最多追加多少字符,防止溢出目标缓冲区。
  3. 安全追加:
    Code snippet
    char buffer[50] = "Result: ";
    char suffix[] = "Pass";
    int remaining_space = elcount(buffer) - strlen(buffer) - 1;
    if (remaining_space > 0) {
      strncat(buffer, suffix, remaining_space);
    }
    
  4. snprintf: 类似 sprintf,用于格式化字符串,但它接受目标缓冲区的大小作为参数,保证不会写入超过缓冲区边界(包括末尾的 \0),从而防止缓冲区溢出。
  5. snprintf 代码:
    Code snippet
    int id = 123; float voltage = 4.8;
    char output[50];
    snprintf(output, elcount(output), "ID: %d, Voltage: %.1fV", id, voltage);
    
  6. snprintf 溢出: 输出会被截断以适应缓冲区大小(保证 \0 结尾)。函数返回值是 假如缓冲区足够大,本应写入的字符数(不含 \0)。如果返回值大于等于缓冲区大小参数,说明发生了截断。
  7. strlen\0: 返回 4。strlen 计算字符串长度时遇到第一个空字符 \0 就停止。
  8. strncat(..., elcount(dest)): 不安全elcount(dest) 是总大小,不是剩余空间。如果 dest 已有内容,追加 elcount(dest) 个字符几乎肯定会导致溢出。正确做法是计算剩余空间。
  9. snprintf 返回值: 返回欲写入的字符数(不含末尾 \0)。若此返回值 >= 提供的缓冲区大小参数,表示输出被截断了。
  10. 逻辑推理 (Dynamic Filename):
    • 获取年(y), 月(m), 日(d), 时(h), 分(mi), 秒(s) 整数值。
    • 定义缓冲区: char filename[100];
    • 使用 snprintf 格式化:
      Code snippet
      snprintf(filename, elcount(filename), "LogFile_%04d%02d%02d_%02d%02d%02d.txt", y, m, d, h, mi, s);
      // %04d, %02d 用于补零确保固定宽度
      
    • filename 中即为所需的文件名字符串。