第二十五期:常用内置函数之读取文本文件 (Reading Text Files)
- 函数与参数:
openFileRead(char fileName[], int mode).fileName是文件名(含路径),mode指定模式(0=文本模式,1=二进制模式)。 - 检查成功: 检查
openFileRead的返回值(文件句柄dword)。如果返回值为 0,表示打开失败;非零值表示成功,该值是后续文件操作所需的句柄。 fileGetStringvsfileGetStringSZ:fileGetString读取到换行符\n或文件尾 (EOF),但不包含换行符;fileGetStringSZ功能类似,但会包含换行符(如果读取到且缓冲区够大),并且它保证了缓冲区始终以空字符\0结尾,更安全。- EOF 检测: 使用函数
fileIsEOF(dword fileHandle)。在尝试读取前调用此函数,如果返回 1,则表示已到达文件末尾。 - 循环读取示例:
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."); } - 返回 0: 表示文件打开失败,原因可能是文件不存在、路径错误或没有读取权限。
fileClose重要性: 释放文件句柄和相关系统资源,避免资源泄漏。确保操作系统知道文件不再被该程序使用。- Mode 0 vs 1: Mode 0 (文本) 会根据操作系统处理行尾符(例如
\r\n可能只读作\n)。Mode 1 (二进制) 读取文件的原始字节流。读取.txt文件通常使用 Mode 0。 fileGetString内容: 缓冲区将包含"Line1"。fileGetString将换行符视为分隔符,不会将其放入缓冲区。- 逻辑推理 (Config File):
- 使用
openFileRead("config.txt", 0)打开文件,检查句柄。 - 循环
while(fileIsEOF(handle) == 0)。 - 在循环内用
fileGetStringSZ(buffer, size, handle)读取一行。 - 使用
strstr(buffer, "Threshold=")检查行内是否包含目标关键字。 - 如果找到,定位到 '=' 后面,使用
atol()或strtol()(第二十九期内容) 转换数值部分为整数。 - 保存该整数值。
- 读取完毕或找到所需值后,用
fileClose(handle)关闭文件。
- 使用
第二十六期:常用内置函数之写入文本文件 (Writing Text Files)
- 函数与模式:
openFileWrite(char fileName[], int mode). Mode 0 = 覆盖写入(文件不存在则创建);Mode 2 = 追加写入(文件不存在则创建)。 filePutStringvswrite:filePutString(string, handle)将字符串写入指定文件句柄代表的文件;write(format, ...)将格式化信息输出到 CANoe 的 Write 窗口。filePutString不会自动添加换行符,需要手动在字符串中加入\n。- 追加代码:
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."); } - 覆盖已存在文件: 文件原有内容将被清空。
- 追加不存在文件: 系统会创建一个新的空文件,然后开始写入。
filePutString返回值: 返回成功写入的字符数。可用于检查写入是否完整,但通常更关注文件是否成功打开。fileClose重要性 (写入): 非常重要。确保所有内部缓冲区的数据被实际写入(刷新)到磁盘文件,否则可能导致数据丢失或文件不完整。- 路径: 相对路径(如
"mylog.txt")是相对于 CANoe 当前配置的工作目录。绝对路径(如"C:\\Logs\\mylog.txt")是完整的文件系统路径。 - 写入变量值: 需要先用
snprintf(第三十期) 将文本和变量值格式化成一个字符串,存入字符数组,然后用filePutString将该数组内容写入文件。 - 逻辑推理 (Event Log):
- 在
on key 's'事件处理代码块中。 - 获取当前时间戳(如使用
timeNow())。 - 使用
snprintf将时间戳和事件描述("Key 's' pressed")格式化到一个缓冲区logBuffer中,记得末尾加\n。 hnd = openFileWrite("event_log.txt", 2)打开文件以追加模式,检查句柄hnd。filePutString(logBuffer, hnd)写入。fileClose(hnd)关闭。
- 在
第二十七期:常用内置函数之日志启停控制 (基础)
- 启动所有:
startLogging() - 停止所有:
stopLogging() - 配置位置: CANoe 的 "Measurement Setup" 窗口,在 "Logging" 分支下添加和配置 "Logging Block"。
- CAPL 控制模式: 设置为 "Triggered" 或 "Triggered (Single Trigger)"。
- 典型场景: 例如,只在检测到特定错误帧时才开始记录,或者在执行某个自动化测试序列的关键阶段进行记录,以减小日志文件大小。
- 重复启动: 如果对应 Logging Block 已在记录状态,再次调用
startLogging()通常没有额外效果。 - 两个块,一次调用: 如果两个块都设置为 "Triggered (Single Trigger)",调用一次
startLogging()会同时触发这两个块开始记录。 - 视觉确认: 在 Measurement Setup 中,活动(正在记录)的 Logging Block 通常会有一个状态图标(如红点)或背景色变化。
- 控制范围: 无参数的
startLogging()和stopLogging()控制所有配置为 "Triggered" 或 "Triggered (Single Trigger)" 模式的 Logging Block。 - 逻辑推理 (Event + Time): 使用基础函数,需在 Logging Block 配置中预设 Pre-trigger 和 Post-trigger 时间(例如,Pre=1000ms, Post=5000ms),并将模式设为 "Triggered (Single Trigger)"。在
on key 'x'中只需调用startLogging()即可。CANoe 会根据配置的时间自动处理触发点前后的数据记录。
第二十八期:常用内置函数之高级日志控制
- 启动特定:
startLogging("SensorLog");(如果需要预触发,则用startLogging("SensorLog", preTriggerTimeMs);) - 停止特定:
stopLogging("SensorLog");(如果需要后触发,则用stopLogging("SensorLog", postTriggerTimeMs);) preTriggerTimeMs: 预触发时间(毫秒)。指定在调用startLogging之前 多长时间的数据(来自 CANoe 的内部环形缓冲区)应该被包含在日志文件中。postTriggerTimeMs: 后触发时间(毫秒)。指定在调用stopLogging之后 还要继续记录多长时间的数据,然后才真正停止。- 环形缓冲区与预触发: CANoe 在内存中维护一个环形缓冲区,持续存储最近的总线活动。调用带预触发时间的
startLogging时,CANoe 会从这个缓冲区中提取触发点前相应时间段的数据写入日志文件,然后继续记录实时数据。 - 带预触发启动:
startLogging("HighSpeedData", 2000); - 带后触发停止:
stopLogging("HighSpeedData", 500); - 时间范围: 日志将包含
startLogging调用前 1000ms 的数据,startLogging和stopLogging调用之间的所有数据,以及stopLogging调用后 2000ms 的数据。 - 无效名称: 函数调用通常会失败(可能在 Write 窗口输出错误信息),但不会影响其他 Logging Block 或导致 CAPL 脚本崩溃。
- 逻辑推理 (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 秒后立即停止(无额外后触发) }
第二十九期:常用内置函数之数据类型转换
ltoa作用与参数:long val,char buffer[],int base。将长整型val转换为指定base(进制)的字符串,并存入buffer。ltoa进制:base=10 表示十进制,base=16 表示十六进制。ltoa(255, buf, 10)结果是 "255";ltoa(255, buf, 16)结果是 "ff" (小写)。- 字符串转 Long:
atol(char str[])。能自动识别 "0x" 或 "0X" 前缀来处理十六进制字符串。 - 字符串转 Double:
atodbl(char str[]). atol示例:atol("1A")通常返回 0 或未定义,因为它默认解析十进制。atol("0x1A")返回 26。atodbl示例: 返回-123.45。它会解析到第一个非有效浮点数字符('x')为止。atolvsstrtol:atol简单,仅返回转换结果(或0/错误)。strtol(str, &endptr, base)更强大:明确指定进制base,通过endptr返回未解析部分的指针,便于错误检查和连续解析。strtod对应atodbl也有类似增强版。- 解析多个值: 使用
strtol/strtod。第一次调用后,检查endptr是否指向有效字符,然后以endptr作为下一次调用的起始字符串指针,重复此过程。 - 整数转字符串:
char buffer[10]; ltoa(100, buffer, 10); - 逻辑推理 (Parsing Line):
- 用
strstr(line, "Timestamp=")找到时间戳标签。将指针移到 '=' 之后。 - 对该指针调用
atol()或strtol(ptr, &endptr, 10)提取时间戳长整型。 - 用
strstr(line or endptr, "Value=")找到值标签。将指针移到 '=' 之后。 - 对该指针调用
atodbl()或strtod(ptr, NULL)提取值浮点型。
- 用
第三十期:常用内置函数之字符串处理
strlenvsmbslen:strlen("你好")返回字节数(如 UTF-8 下是 6)。mbslen("你好")返回字符数(是 2)。mbslen给出字符数量。strncat作用与安全: 将源字符串追加到目标字符串。第三个参数n(应为目标缓冲区剩余空间大小,如elcount(dest) - strlen(dest) - 1) 用于限制最多追加多少字符,防止溢出目标缓冲区。- 安全追加:
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); } snprintf: 类似sprintf,用于格式化字符串,但它接受目标缓冲区的大小作为参数,保证不会写入超过缓冲区边界(包括末尾的\0),从而防止缓冲区溢出。snprintf代码:Code snippetint id = 123; float voltage = 4.8; char output[50]; snprintf(output, elcount(output), "ID: %d, Voltage: %.1fV", id, voltage);snprintf溢出: 输出会被截断以适应缓冲区大小(保证\0结尾)。函数返回值是 假如缓冲区足够大,本应写入的字符数(不含\0)。如果返回值大于等于缓冲区大小参数,说明发生了截断。strlen与\0: 返回 4。strlen计算字符串长度时遇到第一个空字符\0就停止。strncat(..., elcount(dest)): 不安全。elcount(dest)是总大小,不是剩余空间。如果dest已有内容,追加elcount(dest)个字符几乎肯定会导致溢出。正确做法是计算剩余空间。snprintf返回值: 返回欲写入的字符数(不含末尾\0)。若此返回值 >= 提供的缓冲区大小参数,表示输出被截断了。- 逻辑推理 (Dynamic Filename):
- 获取年(
y), 月(m), 日(d), 时(h), 分(mi), 秒(s) 整数值。 - 定义缓冲区:
char filename[100]; - 使用
snprintf格式化:Code snippetsnprintf(filename, elcount(filename), "LogFile_%04d%02d%02d_%02d%02d%02d.txt", y, m, d, h, mi, s); // %04d, %02d 用于补零确保固定宽度 filename中即为所需的文件名字符串。
- 获取年(