
我最近在设计一个亮度计,在做传感器选型的时候发现了一个性能不错的东西,在使用过程中做了一些仿真工作,现在分享出来:

没想到到吧?是它家的东西

东西很小:

性能也很好
VEML7700 = 光电二极管 + 低噪声放大器 + 积分型 ADC + I2C 数字接口
不是模拟输出,而是:
光强 → 模拟前端积分 → 16bit ADC → I2C 直接读 Lux
它的动态范围非常出色!
测量范围:0 ~ 140,000 lx
分辨率最低:0.0042 lx/bit
ADC 分辨率:16 bit
说明:16 bit = 65536 级
结合增益和积分时间可以覆盖:微光环境(暗屏调光),强光环境(阳光直射)

随便查了一下,最强也就 10w,咱们 14w 非常稳妥
注意到:

居然有工频干扰
那我问你,里面的 ADC 类型是什么?

结构为:
光电二极管
↓
低噪声放大器
↓
积分型 ADC
↓
数字滤波
↓
I2C接口
它是典型的 积分式 ADC 架构(类似 ΔΣ 前端思路)。
当然了,这个东西的寄存器也很简单,是讲 IIC 协议的绝佳好东西,日后课程就讲这个。
100 Hz(以及 120 Hz)闪烁抑制,本质来自 VEML7700 的 积分式测量(integrating ADC / integrating measurement),它天然等效于一个“矩形窗平均滤波器”,在频域上就是 sinc(梳状零点),只要积分时间选得合适,就能把 100/120 Hz 压到很低甚至理论为 0;芯片也明确写了支持 100 Hz 与 120 Hz flicker noise rejection。(因为这个内容可以迁移,就建模了)
LED/室内灯的亮度常见会有随电源产生的调制(很多驱动是整流/纹波),最典型频率就是:
50 Hz 市电 → 亮度纹波常在 100 Hz
60 Hz 市电 → 亮度纹波常在 120 Hz
把入射光照(或等效光电流)写成:
其中 可能是 100 Hz 或 120 Hz。
VEML7700 的 ALS 测量设置里有积分时间 ALS_IT(25/50/100/200/400/800 ms)。

在此
积分测量可以抽象成:在一个采样周期里把信号做平均(或积分再归一化):
把 代进去:
常数项积分就是 。关键是余弦项:
整理成幅度形式,会得到一个与起始相位有关的项乘上一个“频率响应包络”。对“抑制能力”最关键的是幅度上界(相位最坏情况)满足:
残留
也就是经典的 sinc:
结论:积分时间 越长,且如果 ,该频率分量会被“理论完全抑制(零点)”。
sinc 的零点条件是:
也就是:
现在看 VEML7700 的积分时间选项:25/50/100/200/400/800 ms。
50 ms = 5×10 ms,同时也是 6×8.333 ms→ 对 100 Hz、120 Hz 都是“整数周期积分”,理论零点。
100/200/400/800 ms 同理:
100 ms = 10×10 ms = 12×8.333 ms
200 ms = 20×10 ms = 24×8.333 ms
400 ms = 40×10 ms = 48×8.333 ms
800 ms = 80×10 ms = 96×8.333 ms→ 对 100/120 Hz 都会落在 sinc 零点上
所以 只要 ALS_IT 选 50 ms 或更长这些“整倍数”选项,100/120 Hz 闪烁分量在理想矩形积分模型下会被强力压制(理论为 0)。
25 ms = 2.5×10 ms(对 100 Hz 不是整数周期),所以 100 Hz 不会是严格零点,但 sinc 仍会给出衰减:
对应约 -17.9 dB 的幅度衰减(功率约 -35.8 dB);而对 120 Hz:120×25 ms = 3(整数),则是零点;因此会看到:短积分时间也有抑制,但最佳 notch 来自“整周期积分”。
要最强的 100/120 Hz 抑制,优先 ALS_IT = 50 / 100 / 200 / 400 / 800 ms(这些对 100/120 都是整周期积分);如果要更快响应又想有一定抑制:25 ms 也能压,但对 100 Hz 不是“完全消除”;这也和 datasheet 给出的 ALS_IT 可选值以及“100/120 Hz flicker noise rejection”特性相一致。
我继续把这个 sinc 推导扩展成“完整离散采样链”版本:包含 积分窗口 + 刷新周期(refresh time) 对频谱的影响(会出现更复杂的梳状谱),并给出在不同 ALS_IT 下对 100/120 Hz 的理论衰减量对比表;100/120 Hz 抑制主要由“积分窗口”决定(sinc 零点),而“刷新周期 Ts(refresh time)”主要决定残余闪烁在输出序列里会变成什么“表观频率”(alias 到低频/直流),但不会把已经被积分削弱的幅度再放大。
思路是:推导 → 离散化 → 刷新采样/混叠 → 仿真验证
把光照(或光电流)写成“直流 + 闪烁”:
传感器对每次测量做“积分平均”(积分型 ADC/积分测量等效):
其中 。
把余弦项积分出来(核心一步):
用三角恒等式整理(把相位相关的那部分提出来)后,可得到余弦分量的幅度被一个频率响应系数乘上:
这就是矩形窗平均的 sinc 频响(零点在 )。
零点条件:
对 100 Hz:周期 10 ms,所以只要 是 10 ms 的整数倍就能“理论全消”
对 120 Hz:周期 8.333… ms,只要 是 8.333… ms 的整数倍就能“理论全消”
VEML7700 给的 ALS_IT 选项里:50/100/200/400/800 ms 都同时满足:
100 Hz: 个周期
120 Hz: 个周期 因此 100/120 Hz 都落在 sinc 零点上(理想模型下)。
而 25 ms:
对 120 Hz:(零点,理论全消)
对 100 Hz:(不是整数周期,只能衰减) 其幅度衰减:
真实输出不是连续的 ,而是每隔 refresh time 输出一次:
这相当于先用积分窗口滤波(sinc 衰减 100/120),再以 采样输出
对单一正弦闪烁分量:
幅度:仍然是 ,与 Ts 无关(Ts 只改变相位/采样点)
表观频率:采样会把 混叠到
所以 100 Hz 很可能在输出序列里变成“超低频漂移”甚至“接近直流”;这解释了在低刷新率(power saving)下可能看到:即使灯在 100 Hz 闪,输出里也只表现为慢慢抖/慢漂(alias),而不是 100 Hz 的快速抖动。
先用闭式积分公式直接算 (不靠高采样离散近似),对 datasheet power saving 表中的多个(ALS_IT, PSM mode, refresh time)组合,分别对 100/120 Hz:
计算理论衰减
仿真得到输出 AC 的 RMS(去均值后的 rms)
计算 alias 后的表观频率

VEML7700 100/120Hz flicker rejection (integration + refresh sampling)
太长了,放最后。

图 1
随 ALS_IT 的变化(100/120 Hz)

图二
(ALS_IT=100ms, Ts=4100ms)输出 AC 几乎为 0(因为 100/120 都在零点上,只剩数值误差量级);这与“积分整周期会把 100/120 卡到零点”的推导完全一致。
[{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 4.440892098500626e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 1,
'Refresh Ts (ms)': 600,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.0292132110016965e-12,
'Sample rate Fs (Hz)': 1.6666666666666667,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 3.3306690738754696e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1100,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.5765560160124284e-12,
'Sample rate Fs (Hz)': 0.9090909090909091,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 5.551115123125783e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2100,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 3.38815681875831e-12,
'Sample rate Fs (Hz)': 0.47619047619047616,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 9.159339953157541e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4100,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 7.003769644029207e-12,
'Sample rate Fs (Hz)': 0.24390243902439027,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 2.220446049250313e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 1,
'Refresh Ts (ms)': 700,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 6.631849509652089e-13,
'Sample rate Fs (Hz)': 1.4285714285714286,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 4.440892098500626e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1200,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 2.7962493131201986e-13,
'Sample rate Fs (Hz)': 0.8333333333333334,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 3.3306690738754696e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2200,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.796050142148529e-12,
'Sample rate Fs (Hz)': 0.45454545454545453,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 5.551115123125783e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4200,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 3.4456600945190602e-12,
'Sample rate Fs (Hz)': 0.23809523809523808,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 4.440892098500626e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 1,
'Refresh Ts (ms)': 900,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.4840777570411563e-13,
'Sample rate Fs (Hz)': 1.1111111111111112,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 2.220446049250313e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1400,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.2013635062764906e-13,
'Sample rate Fs (Hz)': 0.7142857142857143,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 4.440892098500626e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2400,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.0690675190600384e-12,
'Sample rate Fs (Hz)': 0.4166666666666667,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 3.3306690738754696e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4400,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.916262794144013e-12,
'Sample rate Fs (Hz)': 0.22727272727272727,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 8.881784197001252e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 1,
'Refresh Ts (ms)': 1300,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 1.211411191229325e-13,
'Sample rate Fs (Hz)': 0.7692307692307692,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 4.440892098500626e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1800,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 9.305829788260955e-14,
'Sample rate Fs (Hz)': 0.5555555555555556,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 2.220446049250313e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2800,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 6.744487182985767e-13,
'Sample rate Fs (Hz)': 0.35714285714285715,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 4.440892098500626e-15,
'Flicker f (Hz)': 100.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4800,
'Residual RMS (A*|H|/sqrt2)': 2.7564237370048413e-17,
'Residual RMS (sim)': 9.854802457162338e-14,
'Sample rate Fs (Hz)': 0.20833333333333334,
'|H_int(f)| (theory)': 3.898171832519376e-17},
{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 5.329070518200751e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 1,
'Refresh Ts (ms)': 600,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 2.345822114388084e-13,
'Sample rate Fs (Hz)': 1.6666666666666667,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 3.9968028886505635e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1100,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 1.7921556589833297e-12,
'Sample rate Fs (Hz)': 0.9090909090909091,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 6.661338147750939e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2100,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 2.6547579978994212e-12,
'Sample rate Fs (Hz)': 0.47619047619047616,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 100,
'Aliased f_out (Hz)': 1.099120794378905e-14,
'Flicker f (Hz)': 120.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4100,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 5.895750347530821e-12,
'Sample rate Fs (Hz)': 0.24390243902439027,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 2.6645352591003757e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 1,
'Refresh Ts (ms)': 700,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 5.450190209624567e-13,
'Sample rate Fs (Hz)': 1.4285714285714286,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 5.329070518200751e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1200,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 1.0645791361968601e-12,
'Sample rate Fs (Hz)': 0.8333333333333334,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 3.9968028886505635e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2200,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 1.6661758038683603e-12,
'Sample rate Fs (Hz)': 0.45454545454545453,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 200,
'Aliased f_out (Hz)': 6.661338147750939e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4200,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 3.7249748066843815e-12,
'Sample rate Fs (Hz)': 0.23809523809523808,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 5.329070518200751e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 1,
'Refresh Ts (ms)': 900,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 2.0013185800332077e-13,
'Sample rate Fs (Hz)': 1.1111111111111112,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 2.6645352591003757e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1400,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 5.01898682545424e-13,
'Sample rate Fs (Hz)': 0.7142857142857143,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 5.329070518200751e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2400,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 3.286798044931687e-13,
'Sample rate Fs (Hz)': 0.4166666666666667,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 400,
'Aliased f_out (Hz)': 3.9968028886505635e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4400,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 3.5579182447460383e-13,
'Sample rate Fs (Hz)': 0.22727272727272727,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 1.0658141036401503e-14,
'Flicker f (Hz)': 120.0,
'PSM mode': 1,
'Refresh Ts (ms)': 1300,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 1.916018930536057e-13,
'Sample rate Fs (Hz)': 0.7692307692307692,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 5.329070518200751e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 2,
'Refresh Ts (ms)': 1800,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 3.7895292157526576e-13,
'Sample rate Fs (Hz)': 0.5555555555555556,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 2.6645352591003757e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 3,
'Refresh Ts (ms)': 2800,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 6.30280040835984e-13,
'Sample rate Fs (Hz)': 0.35714285714285715,
'|H_int(f)| (theory)': 3.8981718325193755e-17},
{'ALS_IT (ms)': 800,
'Aliased f_out (Hz)': 5.329070518200751e-15,
'Flicker f (Hz)': 120.0,
'PSM mode': 4,
'Refresh Ts (ms)': 4800,
'Residual RMS (A*|H|/sqrt2)': 2.756423737004841e-17,
'Residual RMS (sim)': 1.6358238563673158e-13,
'Sample rate Fs (Hz)': 0.20833333333333334,
'|H_int(f)| (theory)': 3.8981718325193755e-17}]
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# -----------------------------
# VEML7700 flicker rejection simulation
# Model: y[n] = (1/Tint) * ∫_{n*Ts}^{n*Ts+Tint} x(t) dt
# where x(t) = E0 + A*cos(2π f t + φ0)
# This is an "integrate-and-dump" (rectangular window average) sampled every Ts (refresh time).
#
# Theoretical magnitude attenuation at flicker frequency f:
# |H_int(f)| = |sin(π f Tint) / (π f Tint)| = |sinc(f*Tint)| (normalized sinc with π inside)
#
# Sampling maps the residual sinusoid to a discrete-time (possibly aliased) frequency,
# but does NOT change its magnitude (only its apparent frequency in the output sequence).
# -----------------------------
def sinc_pi(x):
"""sinc with π inside: sin(πx)/(πx), with sinc(0)=1"""
x = np.asarray(x, dtype=float)
y = np.ones_like(x)
nz = np.abs(x) > 1e-15
y[nz] = np.sin(np.pi * x[nz]) / (np.pi * x[nz])
return y
def integrate_average_over_window(f, Tint, t0, phi0, E0=1.0, A=1.0):
"""
Closed-form window average of E0 + A*cos(2π f t + phi0)
over [t0, t0+Tint], divided by Tint.
"""
# Average of constant:
y = E0
# Average of cosine:
# (A/Tint) * ∫ cos(2π f t + phi0) dt = (A/(2π f Tint)) * [sin(2π f t + phi0)]_{t0}^{t0+Tint}
if f == 0:
y += A * np.cos(phi0) # degenerate
else:
y += (A / (2 * np.pi * f * Tint)) * (
np.sin(2 * np.pi * f * (t0 + Tint) + phi0) - np.sin(2 * np.pi * f * t0 + phi0)
)
return y
def simulate_sequence(f, Tint, Ts, N=5000, phi0=None, E0=1.0, A=1.0):
"""
Simulate y[n] for N samples with window average, using closed-form integration.
"""
if phi0 is None:
phi0 = np.random.uniform(0, 2*np.pi)
t0 = np.arange(N) * Ts
y = np.array([integrate_average_over_window(f, Tint, t, phi0, E0=E0, A=A) for t in t0])
return y, phi0
def alias_frequency(f, Fs):
"""
Map analog frequency f (Hz) to aliased frequency in [0, Fs/2] for real-valued sampling.
"""
if Fs <= 0:
return np.nan
# Wrap to [0, Fs)
f_mod = np.mod(f, Fs)
# Fold to [0, Fs/2]
return min(f_mod, Fs - f_mod)
# -----------------------------
# Use refresh-time table from the VEML7700 datasheet (for ALS_GAIN = x2, power-saving enabled):
# ALS_IT: 100/200/400/800 ms, PSM Mode 1..4 -> refresh time as listed.
# -----------------------------
refresh_table_ms = {
100: {1: 600, 2: 1100, 3: 2100, 4: 4100},
200: {1: 700, 2: 1200, 3: 2200, 4: 4200},
400: {1: 900, 2: 1400, 3: 2400, 4: 4400},
800: {1: 1300, 2: 1800, 3: 2800, 4: 4800},
}
# Flicker frequencies of interest
f_list = [100.0, 120.0]
# Build a comparison table
rows = []
np.random.seed(0)
for Tint_ms, modes in refresh_table_ms.items():
Tint = Tint_ms / 1000.0
for mode, Ts_ms in modes.items():
Ts = Ts_ms / 1000.0
Fs = 1.0 / Ts
for f in f_list:
# Theoretical attenuation magnitude for the integrate window
att = abs(sinc_pi(f * Tint)) # |sin(pi f Tint)/(pi f Tint)|
# Simulate and measure residual RMS (after removing mean)
y, phi0 = simulate_sequence(f, Tint, Ts, N=4000, phi0=None, E0=10.0, A=1.0)
y_ac = y - np.mean(y)
rms = np.sqrt(np.mean(y_ac**2))
# Expected RMS for a sinusoid of amplitude (A*att) is (A*att)/sqrt(2)
expected_rms = (1.0 * att) / np.sqrt(2)
rows.append({
"ALS_IT (ms)": Tint_ms,
"PSM mode": mode,
"Refresh Ts (ms)": Ts_ms,
"Sample rate Fs (Hz)": Fs,
"Flicker f (Hz)": f,
"Aliased f_out (Hz)": alias_frequency(f, Fs),
"|H_int(f)| (theory)": att,
"Residual RMS (sim)": rms,
"Residual RMS (A*|H|/sqrt2)": expected_rms,
})
df = pd.DataFrame(rows)
# Sort for readability
df = df.sort_values(["Flicker f (Hz)", "ALS_IT (ms)", "PSM mode"]).reset_index(drop=True)
# Show the table
# from caas_jupyter_tools import display_dataframe_to_user
# display_dataframe_to_user("VEML7700 100/120Hz flicker rejection (integration + refresh sampling)", df)
import pprint
pprint.pprint(df.to_dict(orient='records'))
# -----------------------------
# Plot 1: Theoretical attenuation |H_int(f)| vs integration time for 100 and 120 Hz
# -----------------------------
Tint_ms_grid = np.array([25, 50, 100, 200, 400, 800])
Tint_grid = Tint_ms_grid / 1000.0
plt.figure()
for f in f_list:
att_grid = abs(sinc_pi(f * Tint_grid))
plt.plot(Tint_ms_grid, att_grid, marker='o', label=f"f = {int(f)} Hz")
plt.xlabel("Integration time ALS_IT (ms)")
plt.ylabel("|H_int(f)| = |sin(π f T)/(π f T)|")
plt.title("Theoretical flicker attenuation from window integration (rectangular average)")
plt.grid(True)
plt.legend()
plt.show()
# -----------------------------
# Plot 2: Example time-domain output showing aliasing frequency (same magnitude, different apparent frequency)
# Choose a configuration where Fs is low enough that 100Hz aliases to a small frequency.
# Example: ALS_IT=100ms, Mode 4 -> Ts=4100ms -> Fs≈0.244Hz, so 100Hz aliases near ~0.0..0.122Hz.
# -----------------------------
Tint_ms = 100
mode = 4
Ts_ms = refresh_table_ms[Tint_ms][mode]
Tint = Tint_ms/1000.0
Ts = Ts_ms/1000.0
Fs = 1.0/Ts
plt.figure()
for f in f_list:
y, phi0 = simulate_sequence(f, Tint, Ts, N=250, phi0=0.3, E0=10.0, A=1.0)
y_ac = y - np.mean(y)
plt.plot(y_ac, label=f"Input flicker {int(f)} Hz, alias {alias_frequency(f, Fs):.4f} Hz")
plt.xlabel("Sample index n (one sample per refresh)")
plt.ylabel("Output AC component (counts, normalized)")
plt.title(f"Example output AC after integration (ALS_IT={Tint_ms}ms) and sampling (Ts={Ts_ms}ms)")
plt.grid(True)
plt.legend()
plt.show()