什么是线性插值?一维线性插值和双线性插值在BMS开发中的应用

part11、什么是线性插值
线性插值法(linear interpolation),是指使用连接两个已知量的直线来确定在这两个已知量之间的一个未知量的值的方法。
有好几种插值方法,本文仅仅介绍一维线性插值和双线性插值在bms开发中的应用。
11.1、 一维线性插值
如下图:
已知坐标 (x0, y0) 与 (x1, y1),要得到 [x0, x1] 区间内某一位置 x 在直线上的值。
从数学上来看,3点处于1条直线,斜率是相等的,于是有:
由于 x 值已知,所以可以从公式得到 y 的值:
公式太长不好记,可以进行简化方便记忆,方然推导也没问题....
由,
令α = (y-y0)/(x-x0),同样有,α = (y1 - y0)/(x1 - x0),上面的方程式就可以简化为:
y = y0 + α(y1 − y0)
这样已知x的值,就可以轻松计算出y的值,同样的,已知y的值,可以轻松求出x的值。
21.2、双线性插值
在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。
以下理论搬自网络。
红色的数据点与待插值得到的绿色点
假如我们想得到未知函数 f 在点 p = (x, y) 的值,假设我们已知函数 f 在 q11 = (x1, y1)、q12 = (x1, y2), q21 = (x2, y1) 以及 q22 = (x2, y2) 四个点的值。
首先在 x 方向进行线性插值,得到:
然后在 y 方向进行线性插值,得到:
这样就得到所要的结果 f(x, y):
part22、线性插值在bms中的应用
32.1 一维线性插值在bms中的应用
电芯soc和开路电压是有一定关系的,也就是我们常听说的ocv,ocv是open circuit voltage(开路电压),指的是电池不放电开路时,两极之间的电位差.
但是因为电池的极化效应,想要测量准确的ocv得静止2小时,假设我们通过设置放电电流来控制电池的soc从100%-0%变化,间隔为1%,那么整个实验做完至少需要200小时。
来看一组电池数据,一般电芯厂家提供的都是5%步进的soc对应的电压值,在两个电压点之间的soc可以近似直线,当然这样也是有误差的。
那么如何利用一维线性差值计算不同电压下对应的soc值呢?
例如:计算红框中的某一电压对应的soc值
根据一维线性差值的公式编写代码如下:
#include #include #define soc_full (100)#define soc_ocv_step (soc_full / 20)#define cellvol_len (21)const uint16_t voltage[cellvol_len] = {2966, 3140, 3244, 3343, 3427, 3491, 3525,                                       3576, 3633, 3687, 3730, 3772, 3813, 3858,                                       3914, 3955, 4007, 4054, 4077, 4099, 4180};/** *根据ocv曲线计算soc */uint8_t get_soc_by_cellocv(const uint16_t *cell_table, uint16_t cellvol){  /** y = y0 + (x-x0)*(y1 − y0)/(x1-x0)*/  /**计算3343和3427电压直接的电压对应的soc值,取3400电压下的soc*/  uint16_t soc;  uint8_t i;  if (cellvol = cell_table[soc_full / soc_ocv_step]) // 100%  {    return soc_full;  }  for (i = 0; i  cell_table[i])    {      i++;    }    else    {      break;    }  }  /**y0 = (i - 1) * soc_ocv_step*/  /**(x-x0) = (cellvol - cell_table[i - 1]) */  /**(y1 − y0) = soc_ocv_step,这里由于soc是5%均匀步进,所以直接取了步进值作为y1-y0*/  soc = (i - 1) * soc_ocv_step + soc_ocv_step * (cellvol - cell_table[i - 1]) /                                     (cell_table[i] - cell_table[i - 1]);  return soc;}int main(void){  uint16_t soc_ocv = 0;  soc_ocv = get_soc_by_cellocv(voltage, 3400);  printf(----soc_ocv = %d---, soc_ocv);  return 0;}  
vscode环境下编译看看结果,我们要计算的是3400mv时候对应的soc为18%,计算结果是ok的,要注意限幅处理,0%和100%对应的点。
42.2 双线性插值在bms中的应用
要计算在负载情况下的soc,需要对电压和电流做建模,获得比较准确的soc,当然这个soc也只是尽可能准确一些,相比较ocv,电池工作过程中是不能直接使用ocv计算soc的。
包括电池的充放电map,都是需要进行二维插值计算的,例如:
看一组数据,横轴是电流,纵轴是电压,中间数据为soc值,接下来看看如何利用双线性插值计算soc,这里取得都是1%精度,没有用浮点类型数据。
还是要回归到第一章节介绍的公式,双线性插值实际上是进行3次单线性插值,x轴进行2次插值计算,y轴进行1次插值计算。
就不再针对公式一一分析了,直接上代码:
#include #include #include #include #define soc_full (100)#define soc_ocv_step (soc_full / 20)#define cellvol_len (21)#define current_len 7#define voltage_len 6const uint16_t voltage[cellvol_len] = {2966, 3140, 3244, 3343, 3427, 3491, 3525,                                       3576, 3633, 3687, 3730, 3772, 3813, 3858,                                       3914, 3955, 4007, 4054, 4077, 4099, 4180};static const int32_t current_map[current_len] = {0, 50, 200, 500,                                                 1200, 2500, 2501}; // 横轴static const uint16_t voltage_map[voltage_len] = {0, 2500, 3200,                                                  3380, 3500, 3501}; // 纵轴static const int16_t load_soc_map[current_len][voltage_len] = {    {100, 100, 100, 100, 100, 100},    {0, 0, 4, 15, 35, 100},    {-2, -2, 0, 8, 22, 100},    {-4, -4, -1, 4, 15, 100},    {-6, -6, -3, 2, 10, 100},    {-6, -6, -3, 2, 10, 100},    {-6, -6, -3, 2, 10, 100}};/** *根据ocv曲线计算soc */uint8_t get_soc_by_cellocv(const uint16_t *cell_table, uint16_t cellvol){  /** y = y0 + (x-x0)*(y1 − y0)/(x1-x0)*/  /**计算3343和3427电压直接的电压对应的soc值,取3400电压下的soc*/  uint16_t soc;  uint8_t i;  if (cellvol = cell_table[soc_full / soc_ocv_step]) // 100%  {    return soc_full;  }  for (i = 0; i  cell_table[i])    {      i++;    }    else    {      break;    }  }  /**y0 = (i - 1) * soc_ocv_step*/  /**(x-x0) = (cellvol - cell_table[i - 1]) */  /**(y1 − y0) = soc_ocv_step,这里由于soc是5%均匀步进,所以直接取了步进值作为y1-y0*/  soc = (i - 1) * soc_ocv_step + soc_ocv_step * (cellvol - cell_table[i - 1]) /                                     (cell_table[i] - cell_table[i - 1]);  return soc;}/* *负载soc查表 */static int16_t get_soc_by_load_map(const int16_t *table, uint16_t voltage, int32_t current){  int16_t result = 0;  int16_t map_voltage_low = 0, map_voltage_high = 0, map_current_low = 0,          map_current_high = 0;  int16_t map_high = 0, map_low = 0;  uint8_t i = 0;  int16_t vol_step = 0;  int16_t vol_diff = 0;  int32_t current_step = 0;  int32_t current_diff = 0;  int16_t soc_diff = 0;  int16_t soc_step = 0;  for (i = 0; i < voltage_len; i++) // 循环电压  {    if (voltage < voltage_map[i])    {      if (i != 0)      {        map_voltage_low = i - 1;        map_voltage_high = i;      }      else      {        map_voltage_low = i;        map_voltage_high = i;      }      break;    }  }  for (i = 0; i  voltage_map[voltage_len - 1])))  {    return 100;  }  vol_diff = voltage - voltage_map[map_voltage_low];  vol_step = voltage_map[map_voltage_high] -             voltage_map[map_voltage_low];  soc_step = table[(current_len - 1 - map_current_low) * voltage_len +                   map_voltage_high] -             table[(current_len - 1 - map_current_low) * voltage_len +                   map_voltage_low];  map_low = (int16_t)(soc_step * vol_diff / vol_step +                      table[(current_len - 1 - map_current_low) * voltage_len + map_voltage_low]);  vol_diff = voltage - voltage_map[map_voltage_low];  vol_step = (voltage_map[map_voltage_high] -              voltage_map[map_voltage_low]);  soc_step = (table[(current_len - 1 - map_current_high) * voltage_len +                    map_voltage_high] -              table[(current_len - 1 - map_current_high) * voltage_len +                    map_voltage_low]);  map_high = (int16_t)(vol_diff * soc_step / vol_step +                       table[(current_len - 1 - map_current_high) * voltage_len +                             map_voltage_low]);  result =      (int16_t)((abs(current) - current_map[map_current_low]) *                    (map_high - map_low) / (current_map[map_current_high] - current_map[map_current_low]) +                map_low);  return result;}int main(void){  int16_t soc_ocv = 0;  uint16_t cell_vol = 3200;  int32_t current = 0;  while (1)  {    current += 100;    soc_ocv = get_soc_by_cellocv(voltage, 3400);    printf(----soc_ocv = %d----, soc_ocv);    soc_ocv = get_soc_by_load_map((const int16_t *)load_soc_map, cell_vol, current);    printf(!!!!current = %d,cell_vol = %d,soc_load = %d!!!!, current, cell_vol, soc_ocv);    if (current > 2600)      return 0;    sleep(1000);  }  return 0;}  
看下运行结果,验证也是ok的,这个代码写的略微shi,大家可以自己优化优化,可以把一维线性函数抽出来封装,这样单线性和双线性可以复用函数,代码更简洁一些。
part3经验交流


如何在Windows 10上将MP4刻录到DVD
浅谈贴片电感的失效原因(二)
智能魔镜显示屏带你感受未来式的智慧生活
通用的交流信号电平移位器电路的介绍
常用流量计的选用原则
什么是线性插值?一维线性插值和双线性插值在BMS开发中的应用
三星S8接班爆了的三星Note7 四大特色真旗舰复兴
GS4 PHEV 车型的技术亮点
vivo S7、NEX3S等多款机型获得OriginOS内测版推送
荣耀官宣新机定档10月28日,两款重磅新品同台发布
基于PSOCTM 62处理器的谐波发生器设计实现
中央处理器cpu性能排名
ADI推出4GHz PLL合成器具领先相位杂讯性能
新一代深度学习模型取决于迁移学习
紫光展锐携手中国移动共创数字未来
流延膜表面缺陷检测设备的原理及功能
诺基亚终于归来!但是诺基亚6的骁龙430真的够用吗?
小米集团武汉总部大厦举行奠基仪式
新能源汽车动力电池售后保障乱象
基于ARM技术的模拟数字量采集模块