<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://photonicat.com/index.php?action=history&amp;feed=atom&amp;title=Photonicat_2_SOC_Calculation</id>
	<title>Photonicat 2 SOC Calculation - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://photonicat.com/index.php?action=history&amp;feed=atom&amp;title=Photonicat_2_SOC_Calculation"/>
	<link rel="alternate" type="text/html" href="https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;action=history"/>
	<updated>2026-07-26T12:01:24Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1020&amp;oldid=prev</id>
		<title>C2h2：​Point cross-links at the renamed SOC pages directly instead of via redirects</title>
		<link rel="alternate" type="text/html" href="https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1020&amp;oldid=prev"/>
		<updated>2026-07-26T09:25:21Z</updated>

		<summary type="html">&lt;p&gt;Point cross-links at the renamed SOC pages directly instead of via redirects&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;zh-Hans-CN&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;←上一版本&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;2026年7月26日 (日) 17:25的版本&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l8&quot;&gt;第8行：&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;第8行：&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;For the indicator see [[Photonicat 2 LED Indicator]]; for sysfs paths see&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;For the indicator see [[Photonicat 2 LED Indicator]]; for sysfs paths see&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;[[Photonicat 2 sysfs]]. 中文版见 [[&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;光影猫2 是如何计算电量的&lt;/del&gt;]]。&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;[[Photonicat 2 sysfs]]. 中文版见 [[&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;Photonicat 2 SOC 计算&lt;/ins&gt;]]。&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;== Why voltage alone is not enough ==&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;== Why voltage alone is not enough ==&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;

&lt;!-- diff cache key pcat_wiki:diff:1.41:old-1017:rev-1020:php=table --&gt;
&lt;/table&gt;</summary>
		<author><name>C2h2</name></author>
	</entry>
	<entry>
		<id>https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1017&amp;oldid=prev</id>
		<title>C2h2：​C2h2移动页面How Photonicat 2 Calculates SOC至Photonicat 2 SOC Calculation：​Align with the Photonicat 2 topic prefix used by sibling pages</title>
		<link rel="alternate" type="text/html" href="https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1017&amp;oldid=prev"/>
		<updated>2026-07-26T09:24:51Z</updated>

		<summary type="html">&lt;p&gt;C2h2移动页面&lt;a href=&quot;/wiki/How_Photonicat_2_Calculates_SOC&quot; class=&quot;mw-redirect&quot; title=&quot;How Photonicat 2 Calculates SOC&quot;&gt;How Photonicat 2 Calculates SOC&lt;/a&gt;至&lt;a href=&quot;/wiki/Photonicat_2_SOC_Calculation&quot; title=&quot;Photonicat 2 SOC Calculation&quot;&gt;Photonicat 2 SOC Calculation&lt;/a&gt;：​Align with the Photonicat 2 topic prefix used by sibling pages&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;zh-Hans-CN&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;←上一版本&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;2026年7月26日 (日) 17:24的版本&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;4&quot; class=&quot;diff-notice&quot; lang=&quot;zh-Hans-CN&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;（没有差异）&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;!-- diff cache key pcat_wiki:diff:1.41:old-1009:rev-1017 --&gt;
&lt;/table&gt;</summary>
		<author><name>C2h2</name></author>
	</entry>
	<entry>
		<id>https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1009&amp;oldid=prev</id>
		<title>C2h2：​Fix: use &amp;#124; entity for absolute-value bars; backslash escapes are not MediaWiki syntax and rendered literally</title>
		<link rel="alternate" type="text/html" href="https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1009&amp;oldid=prev"/>
		<updated>2026-07-26T09:21:37Z</updated>

		<summary type="html">&lt;p&gt;Fix: use | entity for absolute-value bars; backslash escapes are not MediaWiki syntax and rendered literally&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;zh-Hans-CN&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;←上一版本&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;2026年7月26日 (日) 17:21的版本&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l134&quot;&gt;第134行：&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;第134行：&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;! Condition !! Threshold !! Why&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;! Condition !! Threshold !! Why&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;|-&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;|-&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| Current small enough || &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;\|&lt;/del&gt;I&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;\| &lt;/del&gt;≤ 340 mA (~0.05C) || IR-model error is amplified at higher current&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| Current small enough || &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&amp;amp;#124;&lt;/ins&gt;I&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&amp;amp;#124; &lt;/ins&gt;≤ 340 mA (~0.05C) || IR-model error is amplified at higher current&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;|-&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;|-&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| Held long enough || ≥ 30 s || Lets polarisation and surface charge relax&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| Held long enough || ≥ 30 s || Lets polarisation and surface charge relax&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l160&quot;&gt;第160行：&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;第160行：&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;so roughly ±60 mV for a 2S pack). The firmware therefore carries three tables:&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;so roughly ±60 mV for a 2S pack). The firmware therefore carries three tables:&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* &#039;&#039;&#039;Discharge table&#039;&#039;&#039; — used in discharge mode (&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;\|&lt;/del&gt;I&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;\| &lt;/del&gt;&amp;gt; 900 mA)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* &#039;&#039;&#039;Discharge table&#039;&#039;&#039; — used in discharge mode (&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&amp;amp;#124;&lt;/ins&gt;I&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&amp;amp;#124; &lt;/ins&gt;&amp;gt; 900 mA)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* &amp;#039;&amp;#039;&amp;#039;Charge table&amp;#039;&amp;#039;&amp;#039; — used in charge mode&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* &amp;#039;&amp;#039;&amp;#039;Charge table&amp;#039;&amp;#039;&amp;#039; — used in charge mode&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* &#039;&#039;&#039;Rest table&#039;&#039;&#039; — the average of the two, used at low current (&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;\|&lt;/del&gt;I&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;\| &lt;/del&gt;≤ 600 mA)&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;* &#039;&#039;&#039;Rest table&#039;&#039;&#039; — the average of the two, used at low current (&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&amp;amp;#124;&lt;/ins&gt;I&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&amp;amp;#124; &lt;/ins&gt;≤ 600 mA)&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Mode switching is &amp;#039;&amp;#039;&amp;#039;hysteretic&amp;#039;&amp;#039;&amp;#039; (enter at 900 mA, leave at 600 mA) so it does&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;Mode switching is &amp;#039;&amp;#039;&amp;#039;hysteretic&amp;#039;&amp;#039;&amp;#039; (enter at 900 mA, leave at 600 mA) so it does&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;

&lt;!-- diff cache key pcat_wiki:diff:1.41:old-1007:rev-1009:php=table --&gt;
&lt;/table&gt;</summary>
		<author><name>C2h2</name></author>
	</entry>
	<entry>
		<id>https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1007&amp;oldid=prev</id>
		<title>C2h2：​Create: how the coulomb counter computes SOC (hardware, integration, OCV fusion, self-learning, temperature, Linux sysfs/RTC chain)</title>
		<link rel="alternate" type="text/html" href="https://photonicat.com/index.php?title=Photonicat_2_SOC_Calculation&amp;diff=1007&amp;oldid=prev"/>
		<updated>2026-07-26T09:21:09Z</updated>

		<summary type="html">&lt;p&gt;Create: how the coulomb counter computes SOC (hardware, integration, OCV fusion, self-learning, temperature, Linux sysfs/RTC chain)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;#039;&amp;#039;&amp;#039;Photonicat 2&amp;#039;&amp;#039;&amp;#039; does not guess the battery level from voltage. It runs a&lt;br /&gt;
proper &amp;#039;&amp;#039;&amp;#039;coulomb counter&amp;#039;&amp;#039;&amp;#039; (also called a fuel gauge): think of it as a water&lt;br /&gt;
meter on the battery, accounting for every milliamp-second in and out.&lt;br /&gt;
&lt;br /&gt;
This page explains how that system works — from the measuring hardware, through&lt;br /&gt;
the firmware&amp;#039;s integration, self-learning and temperature compensation, to the&lt;br /&gt;
Linux kernel driver that turns it all into ordinary sysfs files.&lt;br /&gt;
&lt;br /&gt;
For the indicator see [[Photonicat 2 LED Indicator]]; for sysfs paths see&lt;br /&gt;
[[Photonicat 2 sysfs]]. 中文版见 [[光影猫2 是如何计算电量的]]。&lt;br /&gt;
&lt;br /&gt;
== Why voltage alone is not enough ==&lt;br /&gt;
&lt;br /&gt;
This is the first problem every fuel gauge has to solve. A lithium cell&amp;#039;s&lt;br /&gt;
voltage-vs-charge curve is remarkably flat in the middle. The figures below come&lt;br /&gt;
from the firmware&amp;#039;s &amp;#039;&amp;#039;&amp;#039;discharge OCV table&amp;#039;&amp;#039;&amp;#039; (IR-compensated pack voltage):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Pack OCV (open-circuit voltage) !! Charge level&lt;br /&gt;
|-&lt;br /&gt;
| 7.58 V || 70 %&lt;br /&gt;
|-&lt;br /&gt;
| 7.50 V || 60 %&lt;br /&gt;
|-&lt;br /&gt;
| 7.44 V || 50 %&lt;br /&gt;
|-&lt;br /&gt;
| 7.36 V || 40 %&lt;br /&gt;
|-&lt;br /&gt;
| 7.28 V || 30 %&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Going from 70 % down to 30 % moves the voltage by just &amp;#039;&amp;#039;&amp;#039;0.30 V&amp;#039;&amp;#039;&amp;#039; — roughly&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;6 mV per percent&amp;#039;&amp;#039;&amp;#039;. Put a load on the battery and the IR drop across its&lt;br /&gt;
internal resistance easily amounts to tens of millivolts. In other words,&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;voltage alone can be off by more than 10 %&amp;#039;&amp;#039;&amp;#039;, and it gets worse the heavier&lt;br /&gt;
the load.&lt;br /&gt;
&lt;br /&gt;
The two ends, by contrast, are steep: in this table 8.34 V is 100 % and 6.34 V is&lt;br /&gt;
0 %. So the right approach is to &amp;#039;&amp;#039;&amp;#039;anchor at the ends with voltage and advance&lt;br /&gt;
through the middle by integration&amp;#039;&amp;#039;&amp;#039; — which is exactly what the system below&lt;br /&gt;
does.&lt;br /&gt;
&lt;br /&gt;
== The idea in one line ==&lt;br /&gt;
&lt;br /&gt;
  charge = starting charge − ∫(discharge current)dt + ∫(charge current)dt&lt;br /&gt;
&lt;br /&gt;
The integral itself is accurate, provided the current measurement is. But it has&lt;br /&gt;
two weaknesses: &amp;#039;&amp;#039;&amp;#039;where does the starting point come from&amp;#039;&amp;#039;&amp;#039;, and &amp;#039;&amp;#039;&amp;#039;error&lt;br /&gt;
accumulates&amp;#039;&amp;#039;&amp;#039;. So a real fuel gauge is integration &amp;#039;&amp;#039;plus&amp;#039;&amp;#039; a set of correction&lt;br /&gt;
mechanisms. Photonicat 2 layers them like this:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Layer !! Job&lt;br /&gt;
|-&lt;br /&gt;
| Measurement hardware || TPA626 samples voltage/current every 100 ms&lt;br /&gt;
|-&lt;br /&gt;
| Coulomb integration || Accumulates current into a µAs ledger every 200 ms&lt;br /&gt;
|-&lt;br /&gt;
| OCV fusion || At rest, looks up charge from voltage and gently pulls the integral back&lt;br /&gt;
|-&lt;br /&gt;
| Anchors || At full/empty, pins the value directly to 100 % / 0 %&lt;br /&gt;
|-&lt;br /&gt;
| Self-learning || Capacity, resistance, empty point and charge gain — learned per unit&lt;br /&gt;
|-&lt;br /&gt;
| Temperature || Cold raises resistance and shrinks usable capacity; both corrected&lt;br /&gt;
|-&lt;br /&gt;
| Display layer || Maps internal charge to the 1–100 % the user sees&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 1. Hardware: how it measures ==&lt;br /&gt;
&lt;br /&gt;
The measuring chip is a &amp;#039;&amp;#039;&amp;#039;TPA626&amp;#039;&amp;#039;&amp;#039; (an I²C current/voltage monitor, in the same&lt;br /&gt;
family as the INA226). It sits across a milliohm-class shunt resistor in the&lt;br /&gt;
battery path, deriving current from the voltage drop while also measuring bus&lt;br /&gt;
voltage.&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Sampling&amp;#039;&amp;#039;&amp;#039;: roughly every 100 ms (&amp;lt;code&amp;gt;VD1_Collect&amp;lt;/code&amp;gt;)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Median filtering&amp;#039;&amp;#039;&amp;#039;: voltage and current each pass through a median filter&lt;br /&gt;
  that rejects occasional I²C glitches and load spikes&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Raw values kept separately&amp;#039;&amp;#039;&amp;#039;: the median filter lags by about 3 s, which&lt;br /&gt;
  would smear out the voltage step when a charger is plugged or unplugged — and&lt;br /&gt;
  the internal-resistance learning depends on exactly that step, so the firmware&lt;br /&gt;
  keeps unfiltered raw values alongside&lt;br /&gt;
&lt;br /&gt;
=== Current calibration ===&lt;br /&gt;
&lt;br /&gt;
The shunt and amplifier have unit-to-unit variation, so each board is calibrated&lt;br /&gt;
against a precision multimeter and fitted to a quadratic:&lt;br /&gt;
&lt;br /&gt;
  I_true(mA) = C2·x² + C1·x + C0&lt;br /&gt;
&lt;br /&gt;
Current coefficients (second calibration round, 2026-07-24; residuals ≤3 mA&lt;br /&gt;
across four points):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Term !! Value !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| C2 || −4.444×10⁻⁵ || Quadratic term, corrects high-current nonlinearity&lt;br /&gt;
|-&lt;br /&gt;
| C1 || 1.0411 || Linear term (gain)&lt;br /&gt;
|-&lt;br /&gt;
| C0 || +20 mA (discharge) / 0 mA (charge) || Zero offset, per direction&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A few engineering details worth calling out:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Clamp before evaluating&amp;#039;&amp;#039;&amp;#039;: outside the calibrated range the parabola bends&lt;br /&gt;
  back on itself (vertex near +11.7 A), so the input is clamped to ±3 A and&lt;br /&gt;
  extended linearly by the endpoint slope beyond that.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;64-bit arithmetic&amp;#039;&amp;#039;&amp;#039;: x² reaches 9×10⁶, which overflows int32 once scaled.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;55 mA dead band&amp;#039;&amp;#039;&amp;#039;: anything smaller counts as zero, so idle noise cannot&lt;br /&gt;
  slowly skew the ledger.&lt;br /&gt;
&lt;br /&gt;
== 2. Coulomb integration: the ledger ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;coulomb_counter_update()&amp;lt;/code&amp;gt; runs every &amp;#039;&amp;#039;&amp;#039;200 ms&amp;#039;&amp;#039;&amp;#039;, multiplying current&lt;br /&gt;
by elapsed time into a 64-bit &amp;#039;&amp;#039;&amp;#039;µAs (microamp-second)&amp;#039;&amp;#039;&amp;#039; ledger. µAs rather than&lt;br /&gt;
mAh avoids fixed-point rounding error piling up over long runs.&lt;br /&gt;
&lt;br /&gt;
* Discharge subtracts, charge adds&lt;br /&gt;
* Currents under &amp;#039;&amp;#039;&amp;#039;30 mA&amp;#039;&amp;#039;&amp;#039; in magnitude are ignored (dead band, anti-drift)&lt;br /&gt;
* The charge direction carries its own learned &amp;#039;&amp;#039;&amp;#039;gain&amp;#039;&amp;#039;&amp;#039; — charging is not 100 %&lt;br /&gt;
  efficient&lt;br /&gt;
&lt;br /&gt;
== 3. OCV fusion: pulling the drift back ==&lt;br /&gt;
&lt;br /&gt;
Integration error accumulates, so whenever the battery is &amp;#039;&amp;#039;&amp;#039;near rest&amp;#039;&amp;#039;&amp;#039; the&lt;br /&gt;
firmware looks up charge from voltage and applies a gentle correction.&lt;br /&gt;
&lt;br /&gt;
The entry conditions are strict, because misjudging &amp;quot;at rest&amp;quot; would drag a&lt;br /&gt;
perfectly good integral off course:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Condition !! Threshold !! Why&lt;br /&gt;
|-&lt;br /&gt;
| Current small enough || \|I\| ≤ 340 mA (~0.05C) || IR-model error is amplified at higher current&lt;br /&gt;
|-&lt;br /&gt;
| Held long enough || ≥ 30 s || Lets polarisation and surface charge relax&lt;br /&gt;
|-&lt;br /&gt;
| Voltage slew || ≤ 20 mV/s || Still in a transient, so not really at rest&lt;br /&gt;
|-&lt;br /&gt;
| Current slew || ≤ 2000 mA/s || Likewise&lt;br /&gt;
|-&lt;br /&gt;
| Plug/unplug grace || 10 s || Voltage has not settled after a charger event&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Even then it does not overwrite — it &amp;#039;&amp;#039;&amp;#039;blends&amp;#039;&amp;#039;&amp;#039;:&lt;br /&gt;
&lt;br /&gt;
* Weight capped at &amp;#039;&amp;#039;&amp;#039;α = 0.07&amp;#039;&amp;#039;&amp;#039; (OCV contributes at most 7 %), ramping linearly&lt;br /&gt;
  from 0 over 8 s&lt;br /&gt;
* Any single correction is limited to &amp;#039;&amp;#039;&amp;#039;0.20 %&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
So OCV nudges the integral back rather than yanking it, and the user never sees&lt;br /&gt;
the reading jump.&lt;br /&gt;
&lt;br /&gt;
=== Three OCV tables ===&lt;br /&gt;
&lt;br /&gt;
Lithium cells show &amp;#039;&amp;#039;&amp;#039;hysteresis&amp;#039;&amp;#039;&amp;#039;: at the same charge level, terminal voltage&lt;br /&gt;
reads higher while charging and lower while discharging (about 30 mV per cell,&lt;br /&gt;
so roughly ±60 mV for a 2S pack). The firmware therefore carries three tables:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Discharge table&amp;#039;&amp;#039;&amp;#039; — used in discharge mode (\|I\| &amp;gt; 900 mA)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Charge table&amp;#039;&amp;#039;&amp;#039; — used in charge mode&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Rest table&amp;#039;&amp;#039;&amp;#039; — the average of the two, used at low current (\|I\| ≤ 600 mA)&lt;br /&gt;
&lt;br /&gt;
Mode switching is &amp;#039;&amp;#039;&amp;#039;hysteretic&amp;#039;&amp;#039;&amp;#039; (enter at 900 mA, leave at 600 mA) so it does&lt;br /&gt;
not chatter around the threshold.&lt;br /&gt;
&lt;br /&gt;
Before any lookup the reading is &amp;#039;&amp;#039;&amp;#039;IR-compensated&amp;#039;&amp;#039;&amp;#039;: OCV = terminal voltage −&lt;br /&gt;
I×R_pack, using the learned resistance described below.&lt;br /&gt;
&lt;br /&gt;
== 4. Anchors: getting the ends right ==&lt;br /&gt;
&lt;br /&gt;
The middle is carried by integration; the two ends are pinned directly:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Full anchor&amp;#039;&amp;#039;&amp;#039;: charger present, vbus ≥ 8 V, and current has fallen low →&lt;br /&gt;
  charge = 100 %. Requiring the charger is deliberate: &amp;#039;&amp;#039;&amp;#039;resting OCV at 95 % is&lt;br /&gt;
  about 8.16 V&amp;#039;&amp;#039;&amp;#039;, so a high but resting pack is easy to mistake for full. A poor&lt;br /&gt;
  or under-voltage charger cannot push the pack to true full, and is refused.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Empty anchor&amp;#039;&amp;#039;&amp;#039;: discharged to the cutoff point → charge = 0 %, which also&lt;br /&gt;
  triggers empty-point learning.&lt;br /&gt;
&lt;br /&gt;
== 5. Self-learning: every unit is different ==&lt;br /&gt;
&lt;br /&gt;
This is the most interesting part of the system. Even within one batch of cells&lt;br /&gt;
and boards, unit-to-unit variation can exceed 10 %. So the firmware &amp;#039;&amp;#039;&amp;#039;learns&lt;br /&gt;
four quantities per unit&amp;#039;&amp;#039;&amp;#039; and stores them in data flash, where they survive&lt;br /&gt;
power loss.&lt;br /&gt;
&lt;br /&gt;
=== 5.1 Full-capacity learning (ageing) ===&lt;br /&gt;
&lt;br /&gt;
A cell rated 6800 mAh may be down to 5500 mAh after two years. With capacity&lt;br /&gt;
hard-coded, an aged pack reads optimistically — the display still shows charge&lt;br /&gt;
remaining when the device is about to cut out.&lt;br /&gt;
&lt;br /&gt;
How it learns:&lt;br /&gt;
&lt;br /&gt;
# The full anchor opens a window and zeroes the discharge accumulator&lt;br /&gt;
# The pack discharges all the way to the low-voltage cutoff&lt;br /&gt;
# Full capacity = accumulated discharge + &amp;#039;&amp;#039;&amp;#039;700 mAh&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
# Detecting any charging mid-window voids that attempt&lt;br /&gt;
&lt;br /&gt;
That &amp;#039;&amp;#039;&amp;#039;700 mAh&amp;#039;&amp;#039;&amp;#039; matters: the device never runs the pack down to a true 0 V, so&lt;br /&gt;
at the cutoff (around 6.3–6.45 V) roughly 700 mAh is still left inside and must&lt;br /&gt;
be added back.&lt;br /&gt;
&lt;br /&gt;
The learned value also yields &amp;#039;&amp;#039;&amp;#039;health&amp;#039;&amp;#039;&amp;#039;:&lt;br /&gt;
&lt;br /&gt;
  health = learned capacity / 6800 mAh&lt;br /&gt;
&lt;br /&gt;
Health is folded in slowly via an EMA (Apple-style gradual change), so a single&lt;br /&gt;
measurement never makes it plunge.&lt;br /&gt;
&lt;br /&gt;
=== 5.2 Internal-resistance learning (voltage-step method) ===&lt;br /&gt;
&lt;br /&gt;
IR compensation needs a resistance value. The firmware gets it neatly: at the&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;instant a charger is plugged or unplugged&amp;#039;&amp;#039;&amp;#039;, current steps by ΔI and voltage&lt;br /&gt;
steps by ΔV, so&lt;br /&gt;
&lt;br /&gt;
  R = ΔV / ΔI&lt;br /&gt;
&lt;br /&gt;
Conditions: voltage &amp;gt; 7.8 V (near full, otherwise OCV itself drifts with charge&lt;br /&gt;
and spoils the measurement), current step ≥ 300 mA, voltage step ≥ 15 mV. Results&lt;br /&gt;
fold in by EMA at 3/10 weight, clamped to 10–200 mΩ, defaulting to 50 mΩ.&lt;br /&gt;
&lt;br /&gt;
Note this measures the &amp;#039;&amp;#039;&amp;#039;whole loop&amp;#039;&amp;#039;&amp;#039;: cell body + wiring/connectors (20 mΩ) +&lt;br /&gt;
protection-board MOSFET (15 mΩ). IR compensation wants exactly that series total,&lt;br /&gt;
so nothing is subtracted; only when the host wants to display &amp;quot;cell internal&lt;br /&gt;
resistance&amp;quot; does it subtract the 35 mΩ of board parasitics.&lt;br /&gt;
&lt;br /&gt;
=== 5.3 Empty-point learning ===&lt;br /&gt;
&lt;br /&gt;
A problem found in the field: &amp;#039;&amp;#039;&amp;#039;the device cut out while still showing 20 %&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
Each unit&amp;#039;s current-sense gain and true capacity differ slightly, so the moment&lt;br /&gt;
internal SOC reaches zero and the moment the hardware actually gives up do not&lt;br /&gt;
line up.&lt;br /&gt;
&lt;br /&gt;
The fix is not to touch the current/mAh chain — that would introduce new error —&lt;br /&gt;
but to apply a per-unit learned linear map at the &amp;#039;&amp;#039;&amp;#039;display layer&amp;#039;&amp;#039;&amp;#039;:&lt;br /&gt;
&lt;br /&gt;
  displayed = (internal SOC − empty) × 100 / (100 − empty)&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Empty&amp;#039;&amp;#039;&amp;#039; is the internal SOC recorded the last time the battery genuinely ran&lt;br /&gt;
out. Learning events: a low-voltage shutdown command, an unexpected host death&lt;br /&gt;
while discharging at low voltage, or the empty anchor.&lt;br /&gt;
&lt;br /&gt;
There is also &amp;#039;&amp;#039;&amp;#039;downward self-correction&amp;#039;&amp;#039;&amp;#039;: if voltage is still healthy&lt;br /&gt;
(&amp;gt; 6.8 V) during discharge but internal SOC has already fallen below the empty&lt;br /&gt;
point, then empty was learned too high and follows the reading down. A replaced&lt;br /&gt;
battery, or an overshoot, heals within one discharge cycle.&lt;br /&gt;
&lt;br /&gt;
=== 5.4 Charge-gain learning ===&lt;br /&gt;
&lt;br /&gt;
Charging is not 100 % efficient, so charge pushed in differs from charge actually&lt;br /&gt;
stored. This gain is learned at the full anchor and written to flash when it moves&lt;br /&gt;
by ≥1 %.&lt;br /&gt;
&lt;br /&gt;
== 6. Temperature compensation ==&lt;br /&gt;
&lt;br /&gt;
Lithium cells dislike the cold. An on-board NTC feeds temperature to the coulomb&lt;br /&gt;
counter every 200 ms, driving two corrections.&lt;br /&gt;
&lt;br /&gt;
=== 6.1 Resistance temperature factor k(T) ===&lt;br /&gt;
&lt;br /&gt;
Internal resistance varies strongly with temperature, which directly affects IR&lt;br /&gt;
compensation:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Temperature !! Resistance factor k(T)&lt;br /&gt;
|-&lt;br /&gt;
| −20 °C || ×3.00&lt;br /&gt;
|-&lt;br /&gt;
| −10 °C || ×2.30&lt;br /&gt;
|-&lt;br /&gt;
| 0 °C || ×1.80&lt;br /&gt;
|-&lt;br /&gt;
| 10 °C || ×1.35&lt;br /&gt;
|-&lt;br /&gt;
| 25 °C || ×1.00 (reference)&lt;br /&gt;
|-&lt;br /&gt;
| 40 °C || ×0.85&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
At −20 °C the resistance is &amp;#039;&amp;#039;&amp;#039;three times&amp;#039;&amp;#039;&amp;#039; its room-temperature value. The&lt;br /&gt;
learned resistance is normalised to 25 °C and multiplied by k(T) in use.&lt;br /&gt;
&lt;br /&gt;
=== 6.2 Cold-weather capacity compensation ===&lt;br /&gt;
&lt;br /&gt;
In the cold, usable capacity shrinks — the energy is not gone, it just cannot be&lt;br /&gt;
drawn out. The firmware handles this by &amp;#039;&amp;#039;&amp;#039;raising the display empty point&amp;#039;&amp;#039;&amp;#039;, so&lt;br /&gt;
the reading falls earlier and nobody is misled by &amp;quot;15 % left&amp;quot; outdoors at −10 °C:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Temperature !! Empty point raised by&lt;br /&gt;
|-&lt;br /&gt;
| ≥ 15 °C || 0 (no compensation)&lt;br /&gt;
|-&lt;br /&gt;
| 0 °C || about +4 %&lt;br /&gt;
|-&lt;br /&gt;
| −10 °C || about +8 %&lt;br /&gt;
|-&lt;br /&gt;
| ≤ −20 °C || +12 % (capped)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
An NTC reading outside −30…70 °C is treated as a fault, and the last valid&lt;br /&gt;
temperature is held (initially 25 °C).&lt;br /&gt;
&lt;br /&gt;
== 7. The display curve, and why it is &amp;quot;Tesla-style&amp;quot; ==&lt;br /&gt;
&lt;br /&gt;
The internally computed SOC is not shown directly. One more mapping sits in&lt;br /&gt;
between:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;4 % → displays 1 %&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;96 % → displays 100 %&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* Linear in between (slope 99/92), clipped at both ends&lt;br /&gt;
&lt;br /&gt;
The point is that &amp;#039;&amp;#039;&amp;#039;when the display reads 0 %, roughly 4 % is still in the&lt;br /&gt;
pack&amp;#039;&amp;#039;&amp;#039; (plus the hardware margin below the empty point), giving time to save work&lt;br /&gt;
and shut down cleanly. Likewise 96 % shows as 100 % so nobody agonises over the&lt;br /&gt;
last few percent — both are standard consumer-electronics practice.&lt;br /&gt;
&lt;br /&gt;
The reported percentage is also &amp;#039;&amp;#039;&amp;#039;clamped to a minimum of 1 %&amp;#039;&amp;#039;&amp;#039;: as long as the&lt;br /&gt;
device is running, it will not show 0 %.&lt;br /&gt;
&lt;br /&gt;
== 8. After power-off: not losing the reading ==&lt;br /&gt;
&lt;br /&gt;
This is where many fuel gauges fall down. Photonicat 2 has three paths.&lt;br /&gt;
&lt;br /&gt;
=== 8.1 SOC snapshot ===&lt;br /&gt;
&lt;br /&gt;
While running, SOC is written to flash whenever it drifts by ≥5 %; shutdown, host&lt;br /&gt;
power-down, standby entry and OTA reset all force a write and refresh the&lt;br /&gt;
timestamp.&lt;br /&gt;
&lt;br /&gt;
On boot:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;≤12 hours&amp;#039;&amp;#039;&amp;#039;: SOC = snapshot − 2.0 mA × Δt (standby current), and OCV&lt;br /&gt;
  re-estimation is &amp;#039;&amp;#039;&amp;#039;blocked&amp;#039;&amp;#039;&amp;#039; from overriding it. This is what fixed the old&lt;br /&gt;
  &amp;quot;10 % becomes 1 % after a quick reboot&amp;quot; behaviour.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;&amp;gt;12 hours&amp;#039;&amp;#039;&amp;#039;: the snapshot is stale, so a cold-start OCV estimate is used.&lt;br /&gt;
&lt;br /&gt;
=== 8.2 Deep-sleep discharge estimation ===&lt;br /&gt;
&lt;br /&gt;
In deep sleep the TPA626 is powered down and no current can be measured. The&lt;br /&gt;
firmware records an RTC minute stamp on the way in and, on waking, subtracts the&lt;br /&gt;
whole-device standby draw of about &amp;#039;&amp;#039;&amp;#039;2.0 mA&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=== 8.3 Detecting a battery swap ===&lt;br /&gt;
&lt;br /&gt;
Remove the battery and the RTC loses power and resets, so its time runs backwards.&lt;br /&gt;
The firmware treats that as a battery swap, voids the snapshot and re-estimates&lt;br /&gt;
from cold. (A 10-minute tolerance is allowed; without it, a valid snapshot could&lt;br /&gt;
be discarded after an update reboot and the SOC would visibly step.)&lt;br /&gt;
&lt;br /&gt;
== 9. How the data reaches Linux ==&lt;br /&gt;
&lt;br /&gt;
Once the MCU has computed everything, it sends status frames to the host&lt;br /&gt;
(RK3576) over &amp;#039;&amp;#039;&amp;#039;UART&amp;#039;&amp;#039;&amp;#039;. Two consumers sit on the host side.&lt;br /&gt;
&lt;br /&gt;
=== 9.1 Kernel driver: photonicat-pm ===&lt;br /&gt;
&lt;br /&gt;
This is a &amp;#039;&amp;#039;&amp;#039;serdev&amp;#039;&amp;#039;&amp;#039; driver (&amp;lt;code&amp;gt;990-photonicat-pm-add-driver.patch&amp;lt;/code&amp;gt;,&lt;br /&gt;
kernel 6.12) that attaches to the serial port, parses status frames, and&lt;br /&gt;
registers as standard Linux devices:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;power_supply&amp;#039;&amp;#039;&amp;#039; class → &amp;lt;code&amp;gt;/sys/class/power_supply/battery&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;RTC&amp;#039;&amp;#039;&amp;#039; class → &amp;lt;code&amp;gt;/dev/rtc&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Because these are &amp;#039;&amp;#039;&amp;#039;standard subsystems&amp;#039;&amp;#039;&amp;#039;, any existing Linux tool works&lt;br /&gt;
directly — nothing needs to know the private protocol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Charge level (%) — the final output of everything described above&lt;br /&gt;
cat /sys/class/power_supply/battery/capacity&lt;br /&gt;
&lt;br /&gt;
# Voltage µV / current µA / power µW&lt;br /&gt;
cat /sys/class/power_supply/battery/voltage_now&lt;br /&gt;
cat /sys/class/power_supply/battery/current_now&lt;br /&gt;
cat /sys/class/power_supply/battery/power_now&lt;br /&gt;
&lt;br /&gt;
# Energy µWh: energy_full is the learned capacity — compare against&lt;br /&gt;
# energy_full_design to see ageing&lt;br /&gt;
cat /sys/class/power_supply/battery/energy_now&lt;br /&gt;
cat /sys/class/power_supply/battery/energy_full&lt;br /&gt;
cat /sys/class/power_supply/battery/energy_full_design&lt;br /&gt;
&lt;br /&gt;
# Charger online&lt;br /&gt;
cat /sys/class/power_supply/charger/online&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;The MCU is the hardware RTC.&amp;#039;&amp;#039;&amp;#039; There is no separate RTC chip on the board —&lt;br /&gt;
the MCU is always powered, keeps time itself, and sends it up with the status&lt;br /&gt;
frames. The driver registers this through &amp;lt;code&amp;gt;rtc_class_ops&amp;lt;/code&amp;gt;, so standard&lt;br /&gt;
tools just work:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
hwclock -r          # read the time from the MCU&lt;br /&gt;
hwclock -w          # write system time back to the MCU&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is why &amp;#039;&amp;#039;&amp;#039;the clock is still right after days unplugged and offline&amp;#039;&amp;#039;&amp;#039; — and&lt;br /&gt;
it is also the mechanism behind the battery-swap detection in 8.3, since a&lt;br /&gt;
battery removal resets the RTC and time runs backwards.&lt;br /&gt;
&lt;br /&gt;
=== 9.2 Userspace daemon: pcat-manager ===&lt;br /&gt;
&lt;br /&gt;
The kernel driver only consumes the power-related part of the status frame.&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;The rest of the UART protocol is handled by pcat-manager&amp;#039;&amp;#039;&amp;#039;: modem management,&lt;br /&gt;
watchdog, power on/off policy, button events, pushing the charge threshold down,&lt;br /&gt;
MCU firmware updates and so on.&lt;br /&gt;
&lt;br /&gt;
The division of labour:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Component !! Responsibility&lt;br /&gt;
|-&lt;br /&gt;
| MCU firmware || Measurement, integration, learning, compensation — all the algorithms&lt;br /&gt;
|-&lt;br /&gt;
| photonicat-pm (kernel) || Power/RTC data → standard sysfs interfaces&lt;br /&gt;
|-&lt;br /&gt;
| pcat-manager (userspace) || Everything else: modem, watchdog, power policy, OTA&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A concrete example is the &amp;#039;&amp;#039;&amp;#039;charge limit&amp;#039;&amp;#039;&amp;#039;. Set 80 % in the web UI and&lt;br /&gt;
pcat-manager sends it to the MCU over UART; the MCU stores it in data flash and&lt;br /&gt;
drives the charging MOSFET directly. From firmware &amp;#039;&amp;#039;&amp;#039;RA2E1260726005&amp;#039;&amp;#039;&amp;#039; the&lt;br /&gt;
setting persists on the MCU side and survives resets and OTA updates, while&lt;br /&gt;
pcat-manager also keeps its own mirror and re-sends it if it detects an MCU&lt;br /&gt;
restart — belt and braces.&lt;br /&gt;
&lt;br /&gt;
== 10. FAQ ==&lt;br /&gt;
&lt;br /&gt;
;Why does the reading occasionally jump?&lt;br /&gt;
:Usually OCV fusion or an anchor correcting integration drift. A single&lt;br /&gt;
 correction is capped at 0.20 %, so you should not see large jumps in normal use;&lt;br /&gt;
 a large one generally means a stale snapshot led to a cold-start re-estimate.&lt;br /&gt;
&lt;br /&gt;
;Why does charge drop faster in winter?&lt;br /&gt;
:Two effects together: the pack genuinely has less usable capacity when cold, and&lt;br /&gt;
 the firmware deliberately raises the display empty point (about +8 % at −10 °C)&lt;br /&gt;
 so you learn early that it needs charging. It recovers as things warm up.&lt;br /&gt;
&lt;br /&gt;
;Why does 96 % show as 100 %?&lt;br /&gt;
:The display curve is designed that way — see section 7. By the same token, there&lt;br /&gt;
 is still margin left when it reads 0 %.&lt;br /&gt;
&lt;br /&gt;
;Does a new battery need &amp;quot;calibration&amp;quot;?&lt;br /&gt;
:Not deliberately. One full discharge (from full down to automatic shutdown) lets&lt;br /&gt;
 the firmware learn true capacity and the empty point; ordinary use gets there&lt;br /&gt;
 gradually anyway.&lt;br /&gt;
&lt;br /&gt;
;How often does health update?&lt;br /&gt;
:It needs a complete discharge window (full anchor → cutoff), and any charging&lt;br /&gt;
 mid-window voids it. So it updates slowly and changes gradually by EMA — that is&lt;br /&gt;
 intentional, so one bad measurement cannot make health plunge.&lt;/div&gt;</summary>
		<author><name>C2h2</name></author>
	</entry>
</feed>