1. 为什么叫”脚本解释器”?
CSV 文件不是简单的”命令列表”,而是带有控制流的脚本:
| 能力 | 说明 |
|---|---|
| 顺序执行 | 按行顺序执行 I2C 命令 |
| 条件跳过 | # 注释行不执行 |
| 延时 | DELAY:N 暂停 N 秒 |
| 循环 | LOOP BEGIN:N / LOOP END 重复执行 N 次 |
| 单命令重复 | REPEAT:N 将当前行命令执行 N 次 |
这与解释型脚本语言(如 Shell、Python)的”读取→解析→执行”模式一致,因此称为类脚本解释器。
2. 精髓一:CSV 即 DSL(领域特定语言)
2.1 CSV 作为脚本载体
选择 CSV 而非 JSON/YAML 的原因:
- 表格化:每行一条指令,列对应参数
- Excel 可编辑:测试人员可手动修改
- 版本管理友好:diff 清晰
- 注释简单:
#开头即注释
2.2 第一列:控制语句
1 | DELAY:5 → 延时 5 秒 |
解析逻辑(i2c_command.py):
1 | if row[0].startswith('#'): |
精髓:第一列既可以是”控制关键字”,也可以为空(表示普通命令),与后续列(cmd_type, slave_addr, reg_addr…)组合成一条完整指令。
3. 精髓二:执行引擎与状态机
3.1 执行上下文
1 | self.execution_context = { |
- current_index:指令指针,相当于 PC
- loop_stack:支持嵌套循环
- running:支持用户点击”停止”中断
3.2 事件驱动调度(避免栈溢出)
问题:若用递归实现 process_next_command() → execute → callback → process_next_command(),深层循环会导致栈溢出。
解决:用 QTimer.singleShot(0, self.process_next_command) 替代直接递归:
1 | def process_next_command(self): |
singleShot(0, fn)表示”下一帧”执行fn- 每次调用
process_next_command都在新的栈帧中,不会累积 - 支持任意深度的 LOOP 嵌套和命令数量
3.3 循环实现
LOOP BEGIN:
1 | # 首次进入:入栈 (start_idx, total_count, 1) |
LOOP END:
1 | start_idx, total_count, current_count = self.execution_context['loop_stack'][-1] |
精髓:通过修改 current_index 实现”跳转”,用栈保存循环状态,实现结构化控制流。
4. 精髓三:异步回调与串行化
4.1 命令执行是异步的
I2C 命令通过串口发送,需等待 STM32 响应。若同步阻塞,会卡住 UI。
设计:execute_command(command, callback) 接收回调:
1 | def execute_command(self, command, callback=None): |
4.2 回调中调度下一条
1 | def on_command_complete(success): |
精髓:每条命令完成后,通过回调 + QTimer 驱动下一条,形成串行执行链,既保证顺序,又不阻塞 UI。
5. 精髓四:I2CCommand 的统一抽象
5.1 一行 CSV → 一个对象
1 |
|
- 一条 CSV 行可同时携带控制信息(ctrl_type, repeat_count)和I2C 参数
- 例如
LOOP BEGIN:2, 2, 0x58, 0xF1, 0x3, ,表示:循环开始,且该行本身是一条读命令
5.2 命令编码
1 | def get_command_header(self): |
上位机只需调用 get_command_header() 和 get_command_data(),即可生成符合 STM32 协议的字节流。
6. 精髓五:EIS 扫频 = CSV 脚本驱动
6.1 多频率 = 多配置组
PyEisAdcCreator 根据 FREQ_INDEX: 0~0xF 生成 16 组配置,每组对应一个频率点。
6.2 CSV 中的”程序”
1 | 配置组1: 写寄存器 → DELAY:10 → 验证17个ADC点 |
本质:CSV 描述了一个隐式循环——对每个频率点执行”配置→等待→验证”。
循环由行的顺序和配置组分隔符体现,无需显式 LOOP,因为组数在生成时已固定。
6.3 可扩展性
若需”每个频率测 3 次取平均”,只需在每组外加:
1 | LOOP BEGIN:3 |
脚本解释器已支持,无需改代码。
7. 总结:脚本解释器的五层设计
| 层 | 内容 |
|---|---|
| 词法 | CSV 行分割、列解析 |
| 语法 | 第一列控制关键字 + 后续列参数 |
| 语义 | I2CCommand 对象(cmd_type, addr, data…) |
| 执行 | process_next_command + 回调 + QTimer |
| 控制流 | DELAY / LOOP / REPEAT / 顺序 |
精髓归纳:
- CSV 即脚本:用表格形式表达”程序”
- 控制流入数据:第一列混入控制关键字
- 事件驱动执行:QTimer 替代递归,避免栈溢出
- 回调串行化:异步 I/O 通过回调驱动下一条
- 生成与解释分离:PyEisAdcCreator 生成,PyComSender 解释,各司其职
这种设计使 EIS 验证从”手工点寄存器”升级为”跑脚本”,实现流片前高质量自动化压测。
6. 为什么测试的是”范围”而非精确值?
6.1 物理信号的本质:ADC 采样值
测试的核心不是寄存器,而是物理信号:
1 | DAC (已知数字正弦) → 运放 → 电池 → ADC 采样 V(t) |
- DAC 输出是确定的(已知 DAC 查表序列)
- 电池是被测对象(阻抗 Z(ω) 未知,但可假设为标称值)
- ADC 采样值 ≈ V(t) = Z(ω) × I(t)
- 期望 ADC 值由 DAC 序列和标称阻抗计算得出
6.2 为什么需要范围比较?
- 电芯一致性差异:不同电芯阻抗有 ±5% 偏差
- 温度漂移:电芯阻抗随温度变化
- ADC 量化误差:ADC 本身有 LSB 误差
- 无法精确预测:电池是模拟器件,无法给出精确的期望值
因此:
- 精确比较(类型3):要求 ADC 值与期望值完全一致,实际不存在
- 范围比较(类型4):要求 ADC 值在
[低界, 高界]范围内,允许测量误差
6.3 读命令用于什么场景?
当 ADC 值接近 0 时:
- 范围比较无法判断正负(正 0.01 和负 0.01 都在 0 附近)
- 此时用读命令(类型2)直接读回 ADC 值,由工程师判断是否合理
6.4 完整测试的物理闭环
1 | PyEisAdcCreator (已知 DAC 序列 + 标称阻抗 → 计算期望范围) |
一句话总结:测试验证的是 FPGA 芯片中 ADC 在电池两端实际采样到的电压信号 是否在预期范围内,从而判断整个 EIS 测量信号链路是否工作正常。
系列目录
01. EIS原理与电池阻抗谱
02. FPGA ADC激励与电池阻抗扫描原理
03. 上位机架构:PyQt与CSV流程
04. STM32命令转发与协议
05. CSV脚本解释器实现精髓(本文)