线性插值在BMS开发中的应用

原创 小飞哥玩嵌入式 2023-12-23 11:41

关注、星标公众号,直达精彩内容


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] 
= {2966314032443343342734913525,
                                       3576363336873730377238133858,
                                       3914395540074054407740994180};

/**
 *根据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[0]) // 0%
  {
    return 0;
  }
  else if (cellvol >= cell_table[SOC_FULL / SOC_OCV_STEP]) // 100%
  {
    return SOC_FULL;
  }

  for (i = 0; i < SOC_FULL / SOC_OCV_STEP;)
  {
    if (cellvol > 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("\n\n----soc_ocv = %d---\n\n", 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 6

const uint16_t voltage[CELLVOL_LEN] 
= {2966314032443343342734913525,
                                       3576363336873730377238133858,
                                       3914395540074054407740994180};

static const int32_t current_map[CURRENT_LEN] = {050200500,
                                                 120025002501}; // 横轴
static const uint16_t voltage_map[VOLTAGE_LEN] = {025003200,
                                                  338035003501}; // 纵轴

static const int16_t load_soc_map[CURRENT_LEN][VOLTAGE_LEN] = {
    {100100100100100100},
    {0041535100},
    {-2, -20822100},
    {-4, -4, -1415100},
    {-6, -6, -3210100},
    {-6, -6, -3210100},
    {-6, -6, -3210100}};

/**
 *根据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[0]) // 0%
  {
    return 0;
  }
  else if (cellvol >= cell_table[SOC_FULL / SOC_OCV_STEP]) // 100%
  {
    return SOC_FULL;
  }

  for (i = 0; i < SOC_FULL / SOC_OCV_STEP;)
  {
    if (cellvol > 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 < CURRENT_LEN; i++) // 循环电流
  {
    if (abs(current) < current_map[i])
    {

      if (i != 0)
      {
        map_current_low = i - 1;
        map_current_high = i;
      }
      else
      {
        map_current_low = i;
        map_current_high = i;
      }
      break;
    }
  }
  if (((map_voltage_high == map_voltage_low) ||
       (abs(current) > current_map[CURRENT_LEN - 1]) ||
       (voltage > 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----\n", 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!!!!\n\n", current, cell_vol, soc_ocv);
    if (current > 2600)
      return 0;

    Sleep(1000);
  }

  return 0;
}

看下运行结果,验证也是OK的,这个代码写的略微shi,大家可以自己优化优化,可以把一维线性函数抽出来封装,这样单线性和双线性可以复用函数,代码更简洁一些。

Part3经验交流

欢迎关注公众号“小飞哥玩嵌入式”,交流更多嵌入式相关的开发知识,目前从事BMS行业,也可以交流BMS开发相关的内容哈


小飞哥玩嵌入式 分享嵌入式开发相关知识,喜欢DIY分享
评论
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 110浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 72浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 93浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 71浏览
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 82浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 171浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 48浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 97浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 76浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 166浏览
  • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
    克里雅半导体科技 2025-01-03 16:11 178浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 89浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦