TT Lab
开始
学习 学习路径 课程

电子电路入门 — 传感器输入验证

再接一级RC就能解决吗?

在 TT Lab 中继续学习

目标

从阶数、负载、频率和时间响应几个方面,比较用于温室传感器的滤波器,并制作保留真实 ngspice 资料的验证 CLI。

为什么重要

请在掌握前面的混叠单元,以及 Python 函数、文件、复数、list、dict、异常之后再进行。本实验预计需要 120 分钟,所以请在默认的 60 分钟结束之前,用“+时间”延长。最多 180 分钟,会话结束时文件会消失。请另行保存需要的代码和报告。不会涉及真实设备或物理电压。

环境与文件

产出物是 /root/filter-response/analyze.py。镜像中带有 Python3 和 ngspice42,不需要外部下载或额外权限。把 /opt/lab/fixtures/filter_response 加入 sys.path,就可以 import simulator。提供的 capture(cfg,新文件夹) 会运行真实的电路,并返回输出文件夹的 Path。其中会留下 input.cir、config.json、solver.log、ac.tsv、pass.tsv、stop.tsv、step.tsv。不会覆盖已有的文件夹,父文件夹必须已存在。

电路与理论中的四种结构完全相同。AC 输入是 ac_v 幅度、23 度相位。阶跃输入在 0 到 1ms 为 0V,随后在 1µs 内上升到 1V,并保持到 50ms。理想缓冲器是带宽无限、输出不受限制的受控电压源,不是真实的运算放大器。

配置契约

config 只接受恰好包含下面 5 个键的 dict。数值只接受 int、float,bool、字符串、NaN、无穷大、超出 float 转换范围,都以 ValueError 拒绝。数值以 float 返回,topology 是 str,没有 Q 的情形保持 None。

键 允许的值
topology rc1、rc2-loaded、rc2-buffered、sk2 之一
f0_hz 220 或 300
q sk2 时为 0.5、1/math.sqrt(2)、1.4 之一;其他电路只能是 None
dt_s 5e-6 或 2.5e-6
ac_v 0.5、1、2 之一

f0_hz 是频率尺度,并不意味着所有电路的 −3dB 频率。R=1000Ω,C=1/(2πRf0_hz)。只有 sk2 使用 C1=2qC、C2=C/(2q),其余按理论使用相同的 R·C。q=1/sqrt(2) 在 JSON 中记录为 0.7071067811865475。

AC 文件与 Bode 契约

read_ac 的 kind 是 sweep、pass、stop 之一。文件是普通文件、最大 1MiB,表头是以空白分隔的 frequency v(in) v(in) v(out) v(out)。忽略空行。每个数据行是 5 个有限 float,电压分量绝对值≤1e6,复数输入的幅度≥1e-12。频率必须为正且严格递增。内容错误为 ValueError,操作系统的文件错误可以原样传递。

第一个频率必须与指定值相差在 1e-8Hz 以内,最后一个必须在 1e-7Hz 以内。所有内部频率,必须与指定网格在相对 1e-10 或绝对 1e-9 以内相同。不要只检查起点和终点。返回的是由 5 个 float 组成的 tuple 的 list。

bode 接收已验证的 rows,并且不修改原始数据。H=complex(输出实部,输出虚部)/complex(输入实部,输入虚部)。每个返回 dict 的键是 frequency_hz、h_re、h_im、gain=abs(H)、db=20log10(gain)、phase_deg=degrees(atan2(H.imag,H.real))。如果 gain=0,则 db 和 phase_deg 为 None。保持列表的顺序。

模型与阶跃观测契约

transfer 用 config 验证配置,并接收 0≤freq≤1e6 的有限数值。令 p=1j*freq/f0_hz,则以 complex 返回:rc1:1/(1+p),rc2-loaded:1/(1+3p+p²),rc2-buffered:1/(1+p)²,sk2:1/(1+p/q+p²)。

提供的 simulator.load_step(path) 会验证表头为 time v(in) v(out)、最大 8MiB、数据 2 到 100000 行,并返回 3 列 tuple 列表。所有值都是有限的,电压绝对值≤1e6,时间不小于 0 且严格递增。第一个时间≤1µs,最后一个必须与 50ms 在 1e-12 秒以内。内容错误为 ValueError。学员不需要重新实现这个加载器。

measure_step 使用该加载器,在 t=0.001001+i1e-5(i=0..4899)处对输出电压做线性插值。恰好吻合的行直接读取,外推为 ValueError。返回的键有 samples_v(4900 个电压)、overshoot_v=max(0,max(samples_v)−1)、settled_at_s、grid_step_s=1e-5、observation_end_s=0.001001+48991e-5。

settled_at_s 是以 1.001ms 上升完成为基准的相对时间。返回满足 |电压−1|>.02 的最后一个采样点的下一个索引*1e-5。如果最后一个采样点也在带外,则为 None,如果全部在带内,则为 0。这是在观测网格和有限观测区间内的判定,并不是连续时间最大值,也不是无限时间稳定性的证明。

判定与分析契约

assess 接收正常的配置、bode 的 sweep 列表、125Hz 的 wanted dict、875Hz 的 nuisance dict,以及 measure_step 的 step dict。前提是传入的中间报告遵循上面的契约,所以不需要重复验证整个结构。返回的键如下。

analyze(value,folder) 把 config、read_ac、bode、measure_step、assess 连起来。返回的是 schema=1、config、sweep、wanted、nuisance、step、assessment。ac.tsv 按 sweep 读取,pass.tsv、stop.tsv 分别按 pass、stop 读取,并把第一项用作 wanted、nuisance。不要修改原始文件和原始配置,错误要传递出去。

条件比较与 CLI 契约

campaign(values,runner) 先验证由 2 到 6 个配置组成的严格 list 的全部内容。输入的 dt_s 必须全部是 5e-6。对每个条件,先执行 runner(基本配置副本)→analyze,接着用只把 dt_s 改为 2.5e-6 的新配置执行 runner→analyze。一个条件结束之后,再运行下一个条件。runner 的异常要传递,并且不修改原始输入。

返回的是 schema=1、runs、candidate_indices、scope='ideal-linear-filters-and-finite-step-observation'。runs 按输入顺序,包含 coarse、fine(各自的 analyze 报告)、difference_v(相同 4900 个时刻的采样点的最大绝对差)、converged(差值≤1e-5V)。candidate_indices 是已收敛、且两侧 assessment.all_pass 都是 True 的条件的从 0 开始的索引列表。空的候选是正常结果,要与运行错误区分开。

main(argv) 接收设置列表的 JSON 文件,以及尚不存在的输出文件夹路径。确认 JSON 中所有配置和默认 dt 之后,再创建文件夹。父文件夹必须已存在。按 runner 的调用顺序,在 run-00、run-01、…… 这些新子文件夹中调用提供的 capture。把结果以一行 JSON 输出到 stdout,并返回 0。参数、配置、文件、运行的 Exception,要输出一行 {"error":"analysis_failed"} 并返回 2,不要输出 traceback。已经生成的新证据,即使失败也不要删除。import 时不运行 CLI,只在直接运行时,才以 main 的返回值退出。

提供的配置是 /opt/lab/fixtures/filter_response/cases.json。在最后一步,请像下面这样用新名称运行,并连同报告一起确认原始文件。重定向也要选择不会覆盖之前报告的路径。

python3 /root/filter-response/analyze.py /opt/lab/fixtures/filter_response/cases.json /root/filter-response/trial-01 > /root/filter-response/report-01.json

步骤

  1. 先验证电路条件:在 /root/filter-response/analyze.py 中实现 config(value)。验证下面 5 个键的配置,并返回把数值规范化为 float 的新 dict。错误的值为 ValueError,并且不修改原始输入。
  2. 读取复数电压的五列:添加 read_ac(path,kind="sweep")。检查下面的文件与网格契约之后,返回由频率、输入实部、输入虚部、输出实部、输出虚部组成的 tuple 的 list。
  3. 去除输入幅度与相位:添加 bode(rows)。对每个已验证的行求出复数输出/输入,并按下面的格式返回频率、复数增益、幅度、dB、相位的列表。
  4. 计算包含负载的传递函数:添加 transfer(cfg,freq)。验证配置和频率,并把理论中四个电路的传递函数以 complex 返回。
  5. 找出最后一次离开容差带:添加 measure_step(path)。用提供的 load_step 读取原始波形,并在指定的 4900 个时刻对输出电压做线性插值。返回过冲电压、建立时刻、观测网格和采样点。
  6. 同时判定频率与时间要求:添加 assess(cfg,sweep,wanted,nuisance,step)。分别计算所有 AC 点的复数模型误差、两个所要求频率的增益、阶跃过冲与建立要求,并返回最终的 AND。
  7. 根据文件生成验证报告:添加 analyze(value,folder)。读取配置、3 个 AC 文件和 1 个阶跃文件,把整体分析与判定连起来。错误不要变成成功报告,而要传递出去。
  8. 把两种计算精度与真实 CLI 连起来:添加 campaign(values,runner)、main(argv) 和 CLI 入口。验证全部配置之后,把每个电路分别以 5µs 和 2.5µs 运行,只选择满足收敛以及两侧设计判定的条件。

参考

评分把对临时文件的纯函数检查,与真实 ngspice 和单独 CLI 的运行区分开来进行。不会修改学员的代码和文件夹。如果把数值表硬编码,在其他输入幅度、频率和负载条件下就会失败。这次的选择,是理想线性模型之内的候选。真实放大器的电源、带宽、压摆率、噪声、元件公差和温度可靠性,需要另行验证。

先验证电路条件

在 /root/filter-response/analyze.py 中实现 config(value)。验证下面 5 个键的配置,并返回把数值规范化为 float 的新 dict。错误的值为 ValueError,并且不修改原始输入。

每种电路类型中 Q 的含义不同。不要把字符串或 bool 当作数值接收。

读取复数电压的五列

添加 read_ac(path,kind="sweep")。检查下面的文件与网格契约之后,返回由频率、输入实部、输入虚部、输出实部、输出虚部组成的 tuple 的 list。

同一个表头出现两次,是因为复数。即使起点和终点吻合,中间的网格也可能是错的。

去除输入幅度与相位

添加 bode(rows)。对每个已验证的行求出复数输出/输入,并按下面的格式返回频率、复数增益、幅度、dB、相位的列表。

请确认 20log10 与 atan2 的象限。不要把输入相位 23 度残留在滤波器的相位中。

计算包含负载的传递函数

添加 transfer(cfg,freq)。验证配置和频率,并把理论中四个电路的传递函数以 complex 返回。

直接连接时,分母中 p 的系数是 3。请与用缓冲器隔开时的 2 区分开来。

找出最后一次离开容差带

添加 measure_step(path)。用提供的 load_step 读取原始波形,并在指定的 4900 个时刻对输出电压做线性插值。返回过冲电压、建立时刻、观测网格和采样点。

重要的是,在第一次进入容差带的时刻之后,是否又走出去过。如果直到最后都在带外,则为 null。

同时判定频率与时间要求

添加 assess(cfg,sweep,wanted,nuisance,step)。分别计算所有 AC 点的复数模型误差、两个所要求频率的增益、阶跃过冲与建立要求,并返回最终的 AND。

幅度相同而相位错误的响应,也属于模型不一致。如果 AC 两个点通过就省略时间,这种实现是错的。

根据文件生成验证报告

添加 analyze(value,folder)。读取配置、3 个 AC 文件和 1 个阶跃文件,把整体分析与判定连起来。错误不要变成成功报告,而要传递出去。

保留原始波形,并把中间结果也留在报告中,以便追踪计算。

把两种计算精度与真实 CLI 连起来

添加 campaign(values,runner)、main(argv) 和 CLI 入口。验证全部配置之后,把每个电路分别以 5µs 和 2.5µs 运行,只选择满足收敛以及两侧设计判定的条件。

要区分运行失败与空的候选。不要覆盖已有的证据文件夹,并同时检查真实的调用和保存的报告。