背景
随着智能设备对电池续航、充电管理和温控的需求不断增加,电池管理系统(BMS)中的 I2C 通信成为了一个核心技术。然而,I2C 作为一种基于总线的通信协议,随着设备数量的增加,容易出现总线竞争、超时、阻塞等问题,尤其在充电管理、电流/电压监控等高频场景中,I2C 总线的竞争会导致性能瓶颈和系统卡顿,最终影响用户体验和系统稳定性。
本文将分析一种通过 属性缓存 和 自适应刷新机制 来优化 I2C 访问的设计方案,结合噪声门限、窗口振幅等自适应算法来提升系统的鲁棒性和实时性。
设计的目标
减少 I2C 访问冲突
I2C 作为共享总线,当多个设备同时请求访问时,会导致总线的争用和锁竞争,尤其是在需要频繁访问的属性(如电流、电压、温度等)上,I2C 请求可能会阻塞其他关键操作,甚至导致超时和死锁。
降低 I2C 请求频率
频繁的 I2C 请求不仅增加了总线的负担,还导致了大量不必要的通信。在传统的方案中,很多属性在短时间内不会发生变化,但仍会频繁请求访问,造成资源浪费。
上层的所有数据请求,均变成读取缓存中的数据,但也仍然兼容实时读取的接口(强实时性)
保持系统稳定性和实时性
在电池管理系统中,某些属性(如电压、电流、温度等)对系统的稳定性至关重要。为确保系统在任何时刻都能及时响应变化,必须保证数据的一致性和实时性。通过自适应刷新机制,可以根据属性的重要性和变化频率动态调整刷新频率,避免频繁更新导致的性能下降。
方案原理
属性缓存机制
传统的 I2C 访问模式中,每次读取设备的状态时,都会直接发起 I2C 请求并读取数据,这可能导致频繁的总线竞争和性能下降。为了解决这个问题,我们引入了 属性缓存 机制:
缓存层:将每个 I2C 属性的值保存在本地缓存中,并且缓存值有一个有效期(TTL)。只有当缓存过期时,才会重新发起 I2C 请求。通过减少重复的 I2C 请求,显著降低了总线的负担。
缓存失效和刷新机制:当属性的缓存过期或收到请求刷新时,会通过后台任务(如 workqueue)异步刷新缓存,而不是在主线程中阻塞等待 I2C 请求完成。
这种机制的优点是能够减少 I2C 请求频率,并确保关键属性能在需要时及时刷新,同时避免了 I2C 总线的竞争和冲突。
自适应刷新机制
为了提高系统的实时性和鲁棒性,我们设计了一套自适应刷新机制,具体原理如下:
按时间敏感度分类
根据属性的变化频率和对系统的影响程度,将所有的 I2C 属性分为 FAST、MED、SLOW 和 STATIC 四类:
FAST 类:快速变化的属性,如电流、电压、状态等,需要较高的刷新频率,TTL 范围较小。
MED 类:中等变化频率的属性,如温度、SOC 等,TTL 范围较大。
SLOW 类:变化较慢的属性,如充电周期、电池健康等,TTL 范围更大。
STATIC 类:基本不变化的属性,如设备型号、设计容量等,TTL 非常大,甚至可以忽略刷新。
TTL的动态调整
TTL(Time-To-Live)是属性缓存的有效期,决定了缓存多久后过期。TTL 的值不仅与属性的时间敏感度相关,还会根据 变化频率 和 稳定性 动态调整:
显著变化:当属性的变化大于某个设定的阈值时,TTL 会缩短,强制进行快速刷新。
稳定状态:当属性变化较小时,TTL 会延长,减少不必要的刷新。
噪声抑制:通过 噪声门限 和 窗口振幅,避免因为测量噪声或微小波动导致过度频繁的刷新。
噪声门限与窗口振幅
噪声门限(Noise Threshold)和窗口振幅(Window Amplitude)是自适应刷新机制中的关键算法。它们的作用是过滤掉不重要的噪声变化,只对 显著变化 做出反应。
噪声门限:通过计算属性值的变化幅度,设定一个最小变化阈值,只有当变化大于该阈值时才认为是有效变化。例如,电流传感器可能会因为测量误差或 ADC 抖动导致微小的波动,但这些波动不足以影响系统决策,因此需要设置噪声门限来过滤掉这些微小的变化。
窗口振幅:通过滑动窗口计算一段时间内的 最大变化范围,如果窗口内的变化超过设定的阈值,则认为有显著变化。这能够有效捕捉到 慢变,避免系统错过重要的变化。
并发控制与总线熔断(bus-recovery)
在多属性并发刷新时,可能会导致 I2C 总线资源的竞争。为了避免这种情况,我们采用了 并发控制 和 总线熔断 的机制:
并发控制:通过使用
spinlock或其他锁机制,确保同一时刻只有一个任务访问同一个属性的缓存或 I2C 总线,避免并发冲突。总线熔断:当 I2C 总线出现过多错误或延迟时,系统会进行熔断处理,暂停所有的 I2C 访问,直到总线恢复正常。这样可以避免错误的连锁反应,保持系统稳定。
关键算法
自适应刷新算法
自适应刷新算法的核心目标是根据 属性的变化频率和敏感性 动态调整缓存的有效期(TTL)。TTL 决定了缓存何时过期,需要重新获取数据。算法的思路是,属性变化越频繁,其 TTL 越短;变化较少的属性可以延长 TTL,减少不必要的刷新请求。
基本概念
TTL (Time-To-Live):属性缓存的有效期。TTL 较短时,属性会更频繁地刷新,TTL 较长时,属性的刷新频率较低。
显著变化:当属性的变化超过了一个设定的阈值时,认为发生了显著变化,TTL 应缩短。
稳定状态:如果属性的值保持不变,TTL 可以逐渐增大,减少刷新频率。
自适应算法的核心逻辑
显著变化检测:
当属性的值发生显著变化时(例如,电流或电压的变化超过一定的阈值),TTL 会缩短,强制进行更频繁的刷新。
显著变化的判定通常通过比较当前值与前一个值之间的差异(
delta)来实现。
稳定状态检测:
当属性值稳定并且变化幅度较小(低于设定的噪声门限和窗口振幅),TTL 会逐渐增大,减少刷新频率。
自适应更新:
基于 噪声门限(noise threshold) 和 窗口振幅(range threshold),调整 TTL。噪声门限用于避免小幅度的波动影响刷新频率,而窗口振幅则用于检测慢变趋势。
算法公式
显著变化检测
其中:
Δ=∣current−previous∣是两次采样的差值
\epsilon是噪声门限或动态变化门限,通常通过噪声估计(EWMA)来计算
TTL 更新
这样,TTL 在 显著变化时会被缩短(0.5倍系数),在 稳定时逐渐延长(1.5倍系数),但仍然在设置的min_ttl ~ max_ttl内。系数可以按需调整
比如:
当发现温度在一段时间t内变化很小,那下一次的缓存刷新时间就变成1.5t,
然后再1.5t时间内如果发现仍然变化很小,则再下一次的缓存刷新时间就变成了1.5t*1.5,
然后再1.5t*1.5时间内,发现缓存刷新时间很显著,则下一次缓存刷新时间变为 1.5t*1.5 / 2
当然所有的刷新时间都必须满足在min_ttl和max_ttl之内!
自适应刷新伪代码
// 获取当前的显著变化(delta)和噪声估计值
delta = abs(current_value - previous_value);
noise = calculate_noise_estimate();
// 计算当前的门限(阈值),根据噪声门限和窗口振幅动态调整
eps = max(abs_eps_floor, k_noise * noise);
range = calculate_window_range();
// 判断是否为显著变化
if (delta > eps || range > range_eps_floor) {
// 显著变化:加速刷新,缩短 TTL
ttl = max(min_ttl, ttl / 2); // 缩短 TTL
stable_count = 0; // 重置稳定计数
} else {
// 稳定状态:延长 TTL,减少刷新频率
stable_count++;
if (stable_count >= 3) { // 3次稳定才延长TTL
ttl = min(max_ttl, ttl + (ttl / 2)); // 延长 TTL
}
}
// 更新缓存
update_cache(ttl, current_value);噪声门限算法
噪声门限算法的目标是避免将小的测量误差、噪声或量化误差当作有效变化来进行处理。通过设定一个阈值(eps),当属性值变化小于该阈值时,认为其变化不足以触发刷新的操作。
噪声门限的作用
过滤噪声:由于 ADC、传感器或者电路噪声,电流/电压等信号可能会产生小幅度波动,这些波动不代表实际的系统状态变化,通过噪声门限来避免这些微小的变化触发刷新。
减少不必要的刷新:如果每次微小变化都触发 I2C 请求,会导致 I2C 总线的拥塞。噪声门限能够有效减少这种情况。
噪声门限算法原理
噪声门限的基本思想是,通过计算变化值
delta(即当前值与前一个值的差异),与设定的门限值eps进行比较。当
delta > eps时,认为发生了显著变化,需要刷新缓存。当
delta <= eps时,认为变化太小,可以忽略,不进行刷新。
算法公式
噪声估计(EWMA)
这里Δ是当前值与前一个值的差异,noise是当前的噪声估计。
1/8的平滑系数可以改动,是一个暂时合理的数字,具体还是需要在实际中调整
过大的平滑系数会使得噪声估计对短期波动反应过于敏感,导致噪声门限过低,有可能会触发误判
过小的平滑系数会使得噪声估计对短期波动反应无反应,导致慢变的真实反映被平滑掉,系统无法相应这样的变化
门限计算
其中:
\epsilon是计算出的门限,用于判断是否触发刷新。
\text{abs\_eps\_floor}是设置的最低门限,防止门限过小导致不必要的触发。
EWMA(指数加权移动平均):用于计算当前的噪声估计,避免瞬时波动对系统的影响。通过 k_noise 参数来控制噪声敏感度,k_noise 越大,系统对噪声的敏感度越低。
变化显著判定:
只有当 变化幅度 delta 大于门限 $$\epsilon$$时,才认为是 显著变化。
噪声门限算法伪代码
// 更新噪声估计(采用EWMA)
noise = (current_delta - noise) / 8 + noise;
// 计算当前的噪声门限
eps = max(abs_eps_floor, k_noise * noise);
// 检测变化是否显著
if (abs(current_value - previous_value) > eps) {
// 发生显著变化
update_cache(current_value);
}窗口振幅算法
窗口振幅算法的作用是检测属性的 累计变化。即使单步的变化 delta 小于噪声门限,如果多次小的变化累计到一定程度,也应该触发刷新。
窗口振幅的作用
检测慢变变化:例如,充电电流可能逐步增加,而每次变化都很小。虽然这些变化小于噪声门限,但它们的累计效果可能对系统有影响。通过窗口振幅,我们可以检测到这些慢变的趋势。
避免TTL过度延长:在某些情况下,尽管变化幅度很小,但如果变化累积起来,它可能会导致系统状态发生显著变化。窗口振幅能够确保系统能够及时响应这些变化。
窗口振幅算法原理
在每次属性值变化时,都会将当前值与历史值存入一个滑动窗口。
通过计算窗口内的最大值和最小值,得到窗口振幅(
range = max - min)。如果窗口振幅超过设定的阈值,则认为发生了显著变化,并触发刷新。
算法公式
窗口内值更新
每次有新值时,都将该值插入滑动窗口,并更新窗口位置。
窗口振幅计算
计算滑动窗口内的 最大值与最小值的差,作为变化的幅度。
显著变化判定
如果 窗口振幅 超过了设定的阈值,认为发生了显著变化,触发刷新。
窗口振幅算法伪代码
// 更新滑动窗口
push_to_window(current_value);
// 计算窗口振幅
range = window_max - window_min;
// 如果振幅超过阈值,认为是显著变化
if (range > range_eps_floor) {
// 发生显著变化
update_cache(current_value);
}
引擎适配
引擎驱动
charger_property_engine.ko已在本地编译成功
