Embed/microCLib

JiuHuan authored at 2026-09-18 11:56:11
a4a163a
Tree
1 Parent(s) 72debd6
Summary: 17 changed files with 1345 additions and 121 deletions.
Modified +43 -12
Modified +63 -8
Modified +24 -47
Modified +11 -10
Modified +112 -0
Modified +48 -0
Modified +1 -1
Modified +47 -22
Modified +9 -0
Modified +1 -0
Added +257 -0
Added +150 -0
Added +414 -0
Added +131 -0
Modified +12 -8
Modified +22 -2
Modified +0 -11
Modified +43 -12
diff --git a/Core/AdcFilter.c b/Core/AdcFilter.c
index 046089b..76aceaa 100644
--- a/Core/AdcFilter.c
+++ b/Core/AdcFilter.c
@@ -72,7 +72,7 @@ short AdcFilterCalc(AdcFilter_t* filter, int valueMax, int outRange)
 
 
 /** @brief 初始化ADC范围,计算完K B之后会调整 adc Min Max 范围 */
-bool AdcScopeInit(AdcScope_t* scope, ushort adcMin, ushort adcMax, int valueMin, int valueMax)
+bool AdcConvertInit(AdcConvert_t* scope, ushort adcMin, ushort adcMax, int valueMin, int valueMax)
 {
     scope->ValueMax = valueMax;
 	scope->ValueMin = valueMin;
@@ -80,7 +80,7 @@ bool AdcScopeInit(AdcScope_t* scope, ushort adcMin, ushort adcMax, int valueMin,
 	scope->AdcMin = adcMin;
     // 计算K和B值
     scope->K = (float)(valueMax - valueMin) / (adcMax - adcMin);
-    scope->B = valueMin - (scope->K * adcMin);
+    scope->B = (float)valueMin - (scope->K * adcMin);
     
     return true;
 }
@@ -90,24 +90,51 @@ bool AdcScopeInit(AdcScope_t* scope, ushort adcMin, ushort adcMax, int valueMin,
  * @param filter 滤波器
  * @param scope 范围
  * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
- * @return double 
+ * @return float 
  */
-double AdcScopeCalc_Double(AdcFilter_t* filter,AdcScope_t* scp,int fixThr)
+float AdcConvertCalc_Float(AdcFilter_t* filter,AdcConvert_t* scp,int fixThr)
 {
-    if(scp->K < 0.0)return -1;
+    return AdcConvertCalc_GainFloat(filter,scp,fixThr,100000);
+}
+
+/**
+ * @brief 范围矫正
+ * @param filter 滤波器
+ * @param scope 范围
+ * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
+ * @return int 
+ */
+int AdcConvertCalc(AdcFilter_t* filter,AdcConvert_t* scp,int fixThr)
+{
+    return AdcConvertCalc_Gain(filter,scp,fixThr,100000);
+}
+
+/**
+ * @brief 范围矫正
+ * @param filter 滤波器
+ * @param scope 范围
+ * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
+ * @param adcGain ADC数值增益,用于处理参考电压问题(原本是浮点,优化整型定点,扩大100_000)
+ * @return float 
+ */
+float AdcConvertCalc_GainFloat(AdcFilter_t* filter, AdcConvert_t* scp, int fixThr, int adcGain)
+{
+    if(scp->K < 0.0f)return -1.0f;
 
     int adc = AdcFilterCalc(filter, 4096,4096);
+    if(adcGain != 100000)adc = (int)(adc * adcGain / 100000);
+
     // 超出修正范围返回 -1
-    if(adc + fixThr < scp->AdcMin)return -1;
-    if(adc - fixThr > scp->AdcMax)return -1;
+    if(adc + fixThr < scp->AdcMin)return -1.0f;
+    if(adc - fixThr > scp->AdcMax)return -1.0f;
     // 超出范围,修正范围
     if(adc < scp->AdcMin)adc = scp->AdcMin;
     if(adc > scp->AdcMax)adc = scp->AdcMax;
     // 计算
-    int result = adc * scp->K + scp->B;
+    float result = adc * scp->K + scp->B;
     // 结果限定一下,避免因为K B 计算不准,导致结果超出范围
-    if(result < scp->ValueMin)return scp->ValueMin;
-    if(result > scp->ValueMax)return scp->ValueMax;
+    if(result < scp->ValueMin)return (float)scp->ValueMin;
+    if(result > scp->ValueMax)return (float)scp->ValueMax;
 
     return result;
 }
@@ -117,13 +144,16 @@ double AdcScopeCalc_Double(AdcFilter_t* filter,AdcScope_t* scp,int fixThr)
  * @param filter 滤波器
  * @param scope 范围
  * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
+ * @param adcGain ADC数值增益,用于处理参考电压问题(原本是浮点,优化整型定点,扩大100_000)
  * @return int 
  */
-int AdcScopeCalc(AdcFilter_t* filter,AdcScope_t* scp,int fixThr)
+int AdcConvertCalc_Gain(AdcFilter_t* filter, AdcConvert_t* scp, int fixThr, int adcGain)
 {
-    if(scp->K < 0.0)return -1;
+    if(scp->K < 0.0f)return -1;
 
     int adc = AdcFilterCalc(filter, 4096,4096);
+    if(adcGain != 100000)adc = (int)(adc * adcGain / 100000);
+
     // 超出修正范围返回 -1
     if(adc + fixThr < scp->AdcMin)return -1;
     if(adc - fixThr > scp->AdcMax)return -1;
@@ -138,3 +168,4 @@ int AdcScopeCalc(AdcFilter_t* filter,AdcScope_t* scp,int fixThr)
 
     return result;
 }
+
Modified +63 -8
diff --git a/Core/AdcFilter.h b/Core/AdcFilter.h
index c2aeed8..cf5fa05 100644
--- a/Core/AdcFilter.h
+++ b/Core/AdcFilter.h
@@ -42,29 +42,84 @@ typedef struct
     int ValueMax;
 
     // Y = KX + B
-    double K;
+    float K;
     // Y = KX + B
-    double B;
-}AdcScope_t;
+    float B;
+}AdcConvert_t;
 
-/** @brief 初始化ADC范围,计算K B */
-bool AdcScopeInit(AdcScope_t* scope, ushort adcMin, ushort adcMax, int valueMin, int valueMax);
+/**
+ * @brief 初始化ADC范围,计算K B
+ * @param scope 
+ * @param adcMin 
+ * @param adcMax 正常在 4096 以下。如果大于,需要相应修改 valueMax 范围。
+ * @param valueMin 
+ * @param valueMax 控制 valueMax - valueMin < 1_000_000,避免误差
+ * @return true 
+ * @return false 
+ */
+bool AdcConvertInit(AdcConvert_t* scope, ushort adcMin, ushort adcMax, int valueMin, int valueMax);
+
+/**
+ * @brief 范围矫正
+ * @param filter 滤波器
+ * @param scope 范围
+ * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
+ * @return float 
+ */
+float AdcConvertCalc_Float(AdcFilter_t* filter, AdcConvert_t* scp, int fixThr);
+
+/**
+ * @brief 范围矫正
+ * @param filter 滤波器
+ * @param scope 范围
+ * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
+ * @return int 
+ */
+int AdcConvertCalc(AdcFilter_t* filter, AdcConvert_t* scp, int fixThr);
+
+/*
+一个信号,来自于不是基于ADC参考电源的模拟量。
+即:ADC参考电源和被测信号的参考电源不是同一个。
+测出的ADC数据是无法直接使用的,需要矫正。
+
+解决办法:
+ ADC1 采集被测信号参考电源
+ ADC2 采集被测信号
+ 两个ADC同步采样,避免时域上的干扰。
+
+矫正计算
+ ADC1数值/理想ADC1数值 = 增益
+ ADC2数值 * 增益 = 矫正后数值
+
+建议将【增益】数值做一下处理。
+低于 0.9999  到 1.0001 直接当 1.0 处理,减少函数内 float 运算。
+
+*/
 
 /**
  * @brief 范围矫正
  * @param filter 滤波器
  * @param scope 范围
  * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
- * @return double 
+ * @param adcGain ADC数值增益,用于处理参考电压问题(原本是浮点,优化整型定点,扩大100_000)
+ * @return float 
  */
-double AdcScopeCalc_Double(AdcFilter_t* filter,AdcScope_t* scp,int fixThr);
+float AdcConvertCalc_GainFloat(AdcFilter_t* filter, AdcConvert_t* scp, int fixThr, int adcGain);
 
 /**
  * @brief 范围矫正
  * @param filter 滤波器
  * @param scope 范围
  * @param fixThr 修正阈值,ADC抖动大于这个值,不进行范围矫正
+ * @param adcGain ADC数值增益,用于处理参考电压问题(原本是浮点,优化整型定点,扩大100_000)
  * @return int 
  */
-int AdcScopeCalc(AdcFilter_t* filter,AdcScope_t* scope,int fixThr);
+int AdcConvertCalc_Gain(AdcFilter_t* filter, AdcConvert_t* scp, int fixThr, int adcGain);
+
+/*
+测PTC/NTC电阻计算公式
+
+分压:3V3-5.1K-NTC-GND,adc/4096 = ntc/(5100+ntc) → ntc = 5100*adc/(4096-adc)
+分压:3V3-NTC-5.1K-GND,adc/4096 = 5100/(5100+ntc) → ntc = 5100*4096/adc - 5100
+*/
 
Modified +24 -47
diff --git a/Core/Array.c b/Core/Array.c
index 4a2c0c0..03e6b38 100644
--- a/Core/Array.c
+++ b/Core/Array.c
@@ -1,5 +1,28 @@
-
 #include "Array.h"
+#include "Debug.h"
+
+void ShowArray(char* name, byte* pdata, int datalen, int showlen)
+{
+#ifdef DEBUG
+	uint printlen = datalen > showlen ? showlen : datalen;
+	DebugPrintf("%s [%d] = ", name, datalen);
+
+	if (printlen > 32)
+	{
+		for (int i = 0; i < printlen; i++)
+		{
+			if (i % 32 == 0)DebugPrintf("\r\n");
+			DebugPrintf("%02X ", pdata[i]);
+		}
+	}
+	else
+	{
+		for (int i = 0; i < printlen; i++)DebugPrintf("%02X ", pdata[i]);
+	}
+
+	DebugPrintf(printlen == datalen ? "\r\n" : "...\r\n");
+#endif
+}
 
 int ArrayIndexOf(byte* src, int srclen, byte* sub, int sublen)
 {
@@ -83,22 +106,6 @@ int ArrayIndexOf_Byte_Offset(byte* src, int len, byte by, int offset)
 	return -1;
 }
 
-int ArrayIndexOfBuff(Buffer_t* src, Buffer_t* sub)
-{
-	if (src == NULL)return -1;
-	if (sub == NULL)return -1;
-
-	return ArrayIndexOf(src->Data, src->Len, sub->Data, sub->Len);
-}
-
-int ArrayIndexOfBuff2(Buffer2_t* src, Buffer2_t* sub)
-{
-	if (src == NULL)return -1;
-	if (sub == NULL)return -1;
-
-	return ArrayIndexOf(src->Data, src->Len, sub->Data, sub->Len);
-}
-
 int ArrayLastIndexOf(byte* src, int srclen, byte* sub, int sublen)
 {
 	if (srclen < 1)return -1;
@@ -131,22 +138,6 @@ int ArrayLastIndexOf(byte* src, int srclen, byte* sub, int sublen)
 	return -1;
 }
 
-int ArrayLastIndexOfBuff(Buffer_t* src, Buffer_t* sub)
-{
-	if (src == NULL)return -1;
-	if (sub == NULL)return -1;
-
-	return ArrayLastIndexOf(src->Data, src->Len, sub->Data, sub->Len);
-}
-
-int ArrayLastIndexOfBuff2(Buffer2_t* src, Buffer2_t* sub)
-{
-	if (src == NULL)return -1;
-	if (sub == NULL)return -1;
-
-	return ArrayLastIndexOf(src->Data, src->Len, sub->Data, sub->Len);
-}
-
 void ArrayReverse(byte* data, int len)
 {
 	if (data == NULL)return;
@@ -169,20 +160,6 @@ void ArrayReverse(byte* data, int len)
 	}
 }
 
-void ArrayReverseBuff(Buffer_t* buff)
-{
-	if (buff == NULL)return;
-
-	ArrayReverse(buff->Data, buff->Len);
-}
-
-void ArrayReverseBuff2(Buffer2_t* buff)
-{
-	if (buff == NULL)return;
-
-	ArrayReverse(buff->Data, buff->Len);
-}
-
 void ArraySortByte(byte* data, int len)
 {
 	for (int i = 0; i < len - 1; i++)
Modified +11 -10
diff --git a/Core/Array.h b/Core/Array.h
index e68d262..c84bf80 100644
--- a/Core/Array.h
+++ b/Core/Array.h
@@ -1,7 +1,16 @@
 #pragma once
-
 #include "Type.h"
-#include "Buffer.h"
+
+/**
+ * @brief 打印数组内容
+ * @param head 数组名称
+ * @param pdata 数组指针
+ * @param datalen 数组长度
+ * @param showlen 打印长度
+ */
+void ShowArray(char* name, byte* pdata, int datalen, int showlen);
+// 数组长度
+#define ArrayLength(arr) ((int)(sizeof(arr)/sizeof(arr[0])))
 
 // 返回第一个符合的位置
 // 没有找到返回 -1
@@ -11,19 +20,12 @@ int ArrayIndexOf_Byte(byte* src, int len, byte by);
 int ArrayIndexOf_Offset(byte* src, int srclen, byte* sub, int sublen, int offset);
 int ArrayIndexOf_Byte_Offset(byte* src, int srclen, byte by, int offset);
 
-int ArrayIndexOfBuff(Buffer_t* src,Buffer_t* sub);
-int ArrayIndexOfBuff2(Buffer2_t* src, Buffer2_t* sub);
-
 // 从尾部匹配
 // 返回从前往后的下标。
 int ArrayLastIndexOf(byte* src, int srclen, byte* sub, int sublen);
-int ArrayLastIndexOfBuff(Buffer_t* src, Buffer_t* sub);
-int ArrayLastIndexOfBuff2(Buffer2_t* src, Buffer2_t* sub);
 
 // 倒序
 void ArrayReverse(byte* data, int len);
-void ArrayReverseBuff(Buffer_t* buff);
-void ArrayReverseBuff2(Buffer2_t* buff);
 
 // 排序 从小到大 内部使用冒泡排序
 void ArraySortByte(byte* data, int len);
@@ -33,7 +35,6 @@ void ArraySortInt(int* data, int len);
 // 其他
 // copy 直接用 c 库
 
-
 // 对比数组,相同返回true
 bool ArrayEqual(byte* b1, byte* b2, int len);
 
Modified +112 -0
diff --git a/Core/BitHelper.c b/Core/BitHelper.c
index 7041c98..2dd9d73 100644
--- a/Core/BitHelper.c
+++ b/Core/BitHelper.c
@@ -135,3 +135,115 @@ int BitCount(byte* data, int len)
 	return cnt;
 }
 
+#include "Debug.h"
+
+/**
+ * @brief 打印内容
+ * @param name 名称
+ * @param data 指针
+ * @param blen 长度
+ */
+void ShowBits(char* name, byte* data, int blen)
+{
+	if(data == NULL)return;
+	if(blen < 1)return;
+	if(name != NULL)
+	{
+		DebugPrintf("%s%s",name, (blen > 16 ? "\r\n" : " "));
+	}
+
+	// 按字节分组 "1010 1010\0"  中间空格,末尾\0
+	char chs[8 + 2];
+	memset(chs,' ',sizeof(chs) - 1);
+	chs[sizeof(chs)-1] = 0;
+
+	for (int i = 0; ; i++)
+	{
+		bool value = (data[i >> 3] & (0x01 << (i & 0x07))) != 0;
+		
+		// chs 内下标
+		int cidx = i & 0x7;
+		// "1010 1010"
+		chs[cidx + (cidx > 3 ? 1 : 0)] = value ? '1':'0';
+
+		if((i & 0x7) == 0x7)
+		{
+			DebugPrintf("%s ",chs);
+			memset(chs,' ',sizeof(chs) - 1);
+		}
+		if((i & 0xf) == 0xf) { DebugPrintf("\r\n"); }
+		if(i >= blen - 1)return;
+	}
+}
+
+/**
+ * @brief 打印寄存器,每个寄存器都有名字,每个寄存器1bit
+ * @param regs 寄存器名称,NULL 或者长度0 忽略
+ * @param data 数值
+ * @param reglen 寄存器个数
+ */
+void ShowBitRegs(char** regs, byte* data, int rlen)
+{
+	if(regs == NULL)return;
+	if(data == NULL)return;
+	if(rlen < 1)return;
+
+	for (int i = 0; i < rlen; i++)
+	{
+		if(regs[i] == NULL)continue;
+		if(strlen(regs[i]) < 1)continue;
+
+		bool value = (data[i >> 3] & (0x01 << (i & 0x07))) != 0;		
+		DebugPrintf("%s %s\r\n",value?"[1]":"[0]", regs[i]);
+	}
+}
+
+/**
+ * @brief 读寄存器
+ * @param reg 
+ * @param data 
+ * @return uint 
+ */
+uint RegRead(RegItem_t* reg, byte* data)
+{
+	if(reg == NULL)return 0;
+	if(data == NULL)return 0;
+
+	uint res = 0;
+	BitCopy((byte*)&res, 0, data, reg->Offset, reg->BLen);
+	return res;
+}
+
+/**
+ * @brief 写寄存器
+ * @param reg 
+ * @param data 
+ */
+void RegWrite(RegItem_t* reg, byte* data, uint value)
+{
+	if(reg == NULL)return;
+	if(data == NULL)return;
+
+	BitCopy(data, reg->Offset, (byte*)&value, 0, reg->BLen);
+}
+
+/**
+ * @brief 打印寄存器值
+ * @param regs 寄存器信息
+ * @param data 内存
+ * @param rlen 寄存器长度
+ */
+void ShowRegs(RegItem_t* regs, byte* data, int rlen)
+{
+	if(regs == NULL)return;
+	if(data == NULL)return;
+
+	for (int i = 0; i < rlen; i++)
+	{
+		RegItem_t* reg = &regs[i];
+
+		uint value = 0;
+		BitCopy((byte*)&value, 0, data, reg->Offset, reg->BLen);
+		DebugPrintf("%s \t%d\r\n",reg->Name,value);
+	}
+}
Modified +48 -0
diff --git a/Core/BitHelper.h b/Core/BitHelper.h
index 42d54b1..db2b165 100644
--- a/Core/BitHelper.h
+++ b/Core/BitHelper.h
@@ -41,3 +41,51 @@ bool SetBits(byte* dst, int dst_offset, byte* src, int src_offset, int bits);
 bool BitCopy(byte* dst, int dst_offset, byte* src, int src_offset, int bits);
 */
 
+/**
+ * @brief 打印内容
+ * @param name 名称。 
+ * @param data 指针
+ * @param blen 长度
+ */
+void ShowBits(char* name, byte* data, int blen);
+
+/**
+ * @brief 打印寄存器,每个寄存器都有名字,每个寄存器1bit
+ * @param regs 寄存器名称,NULL 或者长度0 忽略
+ * @param data 数值
+ * @param rlen 寄存器个数
+ */
+void ShowBitRegs(char** regs, byte* data, int rlen);
+
+/** @brief 寄存器,小于4字节,且边界对齐 */
+typedef struct
+{
+    // 名称
+    char* Name;
+    // 内存对齐用 ushort
+    ushort Offset;
+    // 长度
+    ushort BLen;
+}RegItem_t;
+
+/**
+ * @brief 读寄存器
+ * @param reg 
+ * @param data 
+ * @return uint 
+ */
+uint RegRead(RegItem_t* reg, byte* data);
+/**
+ * @brief 写寄存器
+ * @param reg 
+ * @param data 
+ */
+void RegWrite(RegItem_t* reg, byte* data, uint value);
+/**
+ * @brief 打印寄存器值
+ * @param regs 寄存器信息
+ * @param data 内存
+ * @param rlen 寄存器长度
+ */
+void ShowRegs(RegItem_t* regs, byte* data, int rlen);
+
Modified +1 -1
diff --git a/Core/BitmapMlmeMem.c b/Core/BitmapMlmeMem.c
index dbfa3a4..86d94e0 100644
--- a/Core/BitmapMlmeMem.c
+++ b/Core/BitmapMlmeMem.c
@@ -226,7 +226,7 @@ bool BitmapMlmeCheck(BitmapMlme_t* mlme, ushort bitIdx)
 	byte itemmask = 0x01 << (bitIdx & 0x7);
 
 	// 判断是否置1
-	return ((*mapitem_p) & itemmask != 0x00);
+	return (((*mapitem_p) & itemmask) != 0x00);
 }
 
 int BitmapMlmeGetEmptyIdx(BitmapMlme_t* mlme, ushort bitStart, byte* buff, int bufflen)
Modified +47 -22
diff --git a/Core/Buffer.c b/Core/Buffer.c
index 0a96a4a..b71f819 100644
--- a/Core/Buffer.c
+++ b/Core/Buffer.c
@@ -1,6 +1,7 @@
 
 #include "Buffer.h"
 #include "Debug.h"
+#include "Array.h"
 
 __inline void InitBuffer(Buffer_t* data)	{ memset(data, 0x00, sizeof(Buffer_t));	data->Free = GlobleFree;}
 __inline void InitBuffer2(Buffer2_t* data)	{ memset(data, 0x00, sizeof(Buffer2_t));data->Free = GlobleFree;}
@@ -31,35 +32,59 @@ __inline void TryCleanBuffer2(Buffer2_t* data)
 	}
 }
 
-void ShowArray(char* name, byte* pdata, int datalen, int showlen)
+void ShowBuff(Buffer_t* buff, int len)
 {
-#ifdef DEBUG
-	uint printlen = datalen > showlen ? showlen : datalen;
-	DebugPrintf("%s [%d] = ", name, datalen);
+	ShowArray("",buff->Data,buff->Len,len);
+}
 
-	if (printlen > 32)
-	{
-		for (int i = 0; i < printlen; i++)
-		{
-			if (i % 32 == 0)DebugPrintf("\r\n");
-			DebugPrintf("%02X ", pdata[i]);
-		}
-	}
-	else
-	{
-		for (int i = 0; i < printlen; i++)DebugPrintf("%02X ", pdata[i]);
-	}
+void ShowBuff2(Buffer2_t* buff, int len)
+{
+	ShowArray("", buff->Data, buff->Len, len);
+}
 
-	DebugPrintf(printlen == datalen ? "\r\n" : "...\r\n");
-#endif
+
+int BuffIndexOf(Buffer_t* src, Buffer_t* sub)
+{
+	if (src == NULL)return -1;
+	if (sub == NULL)return -1;
+
+	return ArrayIndexOf(src->Data, src->Len, sub->Data, sub->Len);
 }
 
-void ShowBuff(Buffer_t* buff, int len)
+int Buff2IndexOf(Buffer2_t* src, Buffer2_t* sub)
 {
-	ShowArray("",buff->Data,buff->Len,len);
+	if (src == NULL)return -1;
+	if (sub == NULL)return -1;
+
+	return ArrayIndexOf(src->Data, src->Len, sub->Data, sub->Len);
 }
 
-void ShowBuff2(Buffer2_t* buff, int len)
+int BuffLastIndexOf(Buffer_t* src, Buffer_t* sub)
 {
-	ShowArray("", buff->Data, buff->Len, len);
+	if (src == NULL)return -1;
+	if (sub == NULL)return -1;
+
+	return ArrayLastIndexOf(src->Data, src->Len, sub->Data, sub->Len);
+}
+
+int Buff2LastIndexOf(Buffer2_t* src, Buffer2_t* sub)
+{
+	if (src == NULL)return -1;
+	if (sub == NULL)return -1;
+
+	return ArrayLastIndexOf(src->Data, src->Len, sub->Data, sub->Len);
+}
+
+void BuffReverse(Buffer_t* buff)
+{
+	if (buff == NULL)return;
+
+	ArrayReverse(buff->Data, buff->Len);
+}
+
+void Buff2Reverse(Buffer2_t* buff)
+{
+	if (buff == NULL)return;
+
+	ArrayReverse(buff->Data, buff->Len);
 }
Modified +9 -0
diff --git a/Core/Buffer.h b/Core/Buffer.h
index 9d1c111..96b3535 100644
--- a/Core/Buffer.h
+++ b/Core/Buffer.h
@@ -35,3 +35,12 @@ void TryCleanBuffer2(Buffer2_t* data);
 void ShowBuff(Buffer_t* buff, int len);
 void ShowBuff2(Buffer2_t* buff, int len);
 
+int BuffIndexOf(Buffer_t* src,Buffer_t* sub);
+int Buff2IndexOf(Buffer2_t* src, Buffer2_t* sub);
+
+int BuffLastIndexOf(Buffer_t* src, Buffer_t* sub);
+int Buff2LastIndexOf(Buffer2_t* src, Buffer2_t* sub);
+
+void BuffReverse(Buffer_t* buff);
+void Buff2Reverse(Buffer2_t* buff);
+
Modified +1 -0
diff --git a/Core/Debug.c b/Core/Debug.c
index e5335ca..58d3665 100644
--- a/Core/Debug.c
+++ b/Core/Debug.c
@@ -1,5 +1,6 @@
 #include "Debug.h"
 // #include "Uart.h"
+#include "Array.h"
 
 #ifdef DEBUG
 
Added +257 -0
diff --git a/Core/EConfigBase.c b/Core/EConfigBase.c
new file mode 100644
index 0000000..21cf99c
--- /dev/null
+++ b/Core/EConfigBase.c
@@ -0,0 +1,257 @@
+
+#include "EConfigBase.h"
+#include "SysInfo.h"
+#include "Array.h"
+
+// #define DebugPrintf( ... ) printf(__VA_ARGS__)
+#include "Debug.h"
+
+/** @brief 注意 8 字节对齐。 */
+typedef struct
+{
+	EConfig_t ECfg;
+	ushort Sun;
+	ushort Idx;
+}ECfgItem_t;
+
+int ECfgItemSize(void){ return sizeof(ECfgItem_t); }
+
+/**
+ * @brief 计算EEPROM最大存储份数。 用于计算 CfgCap
+ * 
+ * @param eepromCap EEPROM 容量
+ * @return int CfgCap
+ */
+int ECfgMaxCap(int eepromCap)
+{
+	for (int i = 1; i < 31; i++) 
+	{
+		int temp = (1 << (i + 1)) ;
+		if(temp * sizeof(ECfgItem_t) > eepromCap)return (1 << i);	
+	}
+
+	return 1 << 31;
+}
+
+/**
+ * @brief 打印内容
+ * @param ecf 配置项指针
+ */
+static void ECfItemShow(ECfgItem_t* ecf){	DebugPrintf("IDX %05d  CRC %04X\r\n", ecf->Idx, ecf->Sun); }
+
+/**
+ * @brief 修正数据,计算校验什么的
+ * @param ecf 配置项指针
+ * @param idx 索引
+ */
+static void ECfgItemFix(ECfgItem_t* ecf, int idx)
+{
+	// 计算sun
+	byte* p = (byte*)&ecf->ECfg;
+	ushort sun = 0;
+	for (int i = 0; i < sizeof(EConfig_t); i++) sun += p[i];
+
+	ecf->Sun = sun;
+	ecf->Idx = idx;
+}
+
+/**
+ * @brief 判断数据是否OK
+ * @param ecf 配置项指针
+ * @return true 有效,false 无效
+ */
+static bool ECfgItemIsOK(ECfgItem_t* ecf)
+{
+	// 计算sun
+	byte* p = (byte*)&ecf->ECfg;
+	ushort sun = 0;
+	for (int i = 0; i < sizeof(EConfig_t); i++) sun += p[i];
+
+	if(ecf->Sun != sun)return false;
+	return true;
+}
+
+/**
+ * @brief 初始化
+ * @param ec 配置管理控制块
+ * @param eapi EEPROM 读写 API
+ * @param cap EEPROM 里面存 配置的份数,2的幂次,不小于2
+ */
+void ECfgInit(ECfgCtrl_t* ec, EEPROM_t* eapi, int cap)
+{
+	if(ec == NULL)return;
+	if(eapi == NULL)return;
+	
+	// 检查 cap 参数
+	bool ok = false;
+	for (int i = 1; i < 32; i++)
+	{
+		int cc = 1 << i;
+		if(cc == cap)
+		{
+			ok = true;
+			break;
+		}
+	}
+	if(!ok)
+	{
+		ErrPrintf("cap not allow\r\n");
+		return;
+	}
+
+	memcpy(&ec->EApi,eapi,sizeof(EEPROM_t));
+	ec->BaseAddr = 0;
+	ec->CfgCap = cap;
+}
+
+/**
+ * @brief 扫描整个存储区,找到最新且有效的配置取出来
+ * @param ec 配置管理控制块
+ * @param ecf 输出的配置指针
+ * @return true 加载成功,false 失败或没有有效配置
+ */
+bool ECfgLoad(ECfgCtrl_t* ec, EConfig_t* ecf)
+{
+	if(ec == NULL)return false;
+	if(ecf == NULL)return false;
+	if(ec->EApi.ERead == NULL)return false;
+
+	// DebugPrintf("%s:\r\n",__func__);
+
+	// EEPROM 当前数据
+	ECfgItem_t temp;
+	int len = sizeof(ECfgItem_t);
+
+	int max = -1;
+	int limitMax = -1;
+	for (int i = 0; i < ec->CfgCap; i++)
+	{
+		// 读
+		uint addr = sizeof(ECfgItem_t) * i + ec->BaseAddr;
+		ec->EApi.ERead(addr, (byte*)&temp, sizeof(temp));
+
+		bool ok = ECfgItemIsOK(&temp);
+		if((temp.Idx & (ec->CfgCap - 1)) != i) ok = false;		
+
+		// DebugPrintf("Addr %08X %s",addr,ok ? "O ":"N ");
+		// ShowArray("READ", (byte*)&temp, len,len);
+		
+		// 筛选有效的。
+		if(!ok)continue;
+
+		// 最大的
+		if(temp.Idx > max)max = temp.Idx;
+		// 小于16里面的最大。
+		if(temp.Idx < ec->CfgCap)
+		{
+			if(temp.Idx > limitMax)limitMax = temp.Idx;
+		}
+	}
+
+	// 没有有效的配置,直接从0开始。
+	if(max == -1)
+	{
+		ec->CfgIndex = 0;
+		memset(&ec->CurrECfg,0,sizeof(EConfig_t));
+		return false;
+	}
+
+	// 判定最后写入是哪个
+	int last = -1;
+	if((max > 65535 - ec->CfgCap) && (limitMax != -1))
+	{
+		// 刚好跨界了。大的被丢弃。使用小的里面最大的。
+		last = limitMax;
+	}
+	else
+	{
+		last = max;
+	}
+
+	{
+		// 读回来
+		uint addr = sizeof(ECfgItem_t) * (last & (ec->CfgCap - 1)) + ec->BaseAddr;
+		ec->EApi.ERead(addr, (byte*)&temp, sizeof(ECfgItem_t));		
+	}
+
+	memcpy(&ec->CurrECfg, &temp.ECfg, sizeof(EConfig_t));
+	memcpy(ecf, &temp.ECfg, sizeof(EConfig_t));
+	ec->CfgIndex = temp.Idx;
+
+	DebugPrintf("%s ",__func__);
+	ECfItemShow(&temp);
+
+	return true;
+}
+
+/*
+// @brief 清理具体某个配置
+// @param ec 配置管理控制块
+// @param idx 索引
+// @return true 成功,false 失败
+static bool ECfgClean(ECfgCtrl_t* ec, int idx)
+{
+	uint16_t addr = sizeof(ECfgItem_t) * (idx & (ec->CfgCap - 1));
+	addr += offsetof2(ECfgItem_t,Sun);
+	addr += ec->BaseAddr;
+	ushort data = 0xffff;
+	return ec->EApi.EWrite(addr, (byte*)&data, sizeof(ushort));
+}
+*/
+
+/**
+ * @brief 保存
+ * @param ec 配置管理控制块
+ * @param ecf 待保存的配置指针
+ * @return true 保存成功,false 保存失败
+ */
+bool ECfgSave(ECfgCtrl_t* ec, EConfig_t* ecf)
+{
+	if(ec == NULL)return false;
+	if(ec->EApi.EWrite == NULL)return false;
+	if(ecf == NULL)return false;
+
+	// 相同,不存。
+	EConfig_t* curr = &ec->CurrECfg;
+	if(ArrayEqual((byte*)ecf, (byte*)curr,sizeof(EConfig_t)))return true;
+
+	// 下一个位置
+	ushort idx = ec->CfgIndex + 1;
+
+	// 实际写入
+	ECfgItem_t item;
+	memset(&item,0x00,sizeof(ECfgItem_t));
+	memcpy(&item.ECfg, ecf, sizeof(EConfig_t));
+	ECfgItemFix(&item, idx);
+	uint16_t addr = sizeof(ECfgItem_t) * (idx & (ec->CfgCap - 1)) + ec->BaseAddr;
+	bool res = ec->EApi.EWrite(addr, (byte*)&item, sizeof(ECfgItem_t));
+
+	// 失败直接返回
+	if(!res)return false;
+
+	// 写入完成,先回读校验,通过才提交内存状态
+	ECfgItem_t temp;
+	memset(&temp,0x00,sizeof(ECfgItem_t));
+	ec->EApi.ERead(addr, (byte*)&temp, sizeof(ECfgItem_t));	
+
+	int len = sizeof(ECfgItem_t);
+	DebugPrintf("%s Addr %08X  ",__func__ , addr);
+	// ShowArray("Write", (byte*)&item, len,len);
+	ECfItemShow(&temp);
+	if(!ArrayEqual((byte*)&temp, (byte*)&item,sizeof(ECfgItem_t)))
+	{
+		DebugPrintf("   Error\r\n");
+		ShowArray("Write", (byte*)&item, len,len);
+		ShowArray("Read ", (byte*)&temp, len,len);
+	
+		return false;
+	}
+
+	DebugPrintf("\r\n");
+
+	// 校验通过,内存留一份
+	ec->CfgIndex = idx;
+	memcpy(&ec->CurrECfg,ecf,sizeof(EConfig_t));
+	return true;
+}
+
Added +150 -0
diff --git a/Core/EConfigBase.h b/Core/EConfigBase.h
new file mode 100644
index 0000000..eaf733c
--- /dev/null
+++ b/Core/EConfigBase.h
@@ -0,0 +1,150 @@
+#pragma once
+
+#include "Type.h"
+#include "EConfig.h"
+/*
+基于 EEPROM 的配置信息。
+特点:高频写入,不怕写入时断电。
+EPROM 保留了很多份有效配置,因为断电造成写入失败,上电会取用上一份有效内容
+循环写入,寿命达到 ItemCapNum X 100w 次。
+*/
+
+// EEPROM 里面存的份数,只能是  2,4,8,16,32
+// sizeof(ECfgItem_t) * ItemCapNum 不能大于 EEPROM 容量
+
+/** @brief EEPROM 操作函数 */
+typedef struct
+{
+	/** @brief FLASH 写 */
+	bool (*EWrite)(uint address, byte* buf, int len);
+	/** @brief 可以直接通过指针访问的 addr ,此函数为 NULL。 */
+	bool (*ERead)(uint address, byte* buf, int len);
+}EEPROM_t;
+
+/** @brief 配置管理 */
+typedef struct
+{
+	/** @brief EEPROM 内当前最新配置,用于对比是否需要写入。 */
+	EConfig_t CurrECfg;
+	/** @brief EEPROM 读写接口 */
+	EEPROM_t EApi;
+	/** @brief 储存区储存份数。只支持 2 的幂次。 */
+	ushort CfgCap;
+	/** @brief 当前使用的 IDEX 。 存放位置就是 %ItemCapNum */
+	ushort CfgIndex;
+	/** @brief EPROM 基地址 */
+	ushort BaseAddr;
+}ECfgCtrl_t;
+
+/**
+ * @brief 初始化
+ * @param ec 配置管理控制块
+ * @param eapi EEPROM 读写 API
+ * @param cap EEPROM 里面存 配置的份数,2的幂次,不小于2
+ */
+void ECfgInit(ECfgCtrl_t* ec, EEPROM_t* eapi, int cap);
+
+/**
+ * @brief 计算EEPROM最大存储份数。 用于计算 CfgCap
+ * @param eepromCap EEPROM 容量
+ * @return int CfgCap
+ */
+int ECfgMaxCap(int eepromCap);
+
+/**
+ * @brief 一份储存的FLASH容量
+ * @return int 
+ */
+int ECfgItemSize(void);
+
+/**
+ * @brief 扫描整个存储区,找到最新且有效的配置取出来
+ * @param ec 配置管理控制块
+ * @param ecf 输出的配置指针
+ * @return true 加载成功,false 失败或没有有效配置
+ */
+bool ECfgLoad(ECfgCtrl_t* ec, EConfig_t* ecf);
+
+/**
+ * @brief 保存
+ * @param ec 配置管理控制块
+ * @param ecf 待保存的配置指针
+ * @return true 保存成功,false 保存失败
+ */
+bool ECfgSave(ECfgCtrl_t* ec, EConfig_t* ecf);
+
+/**
+ * @brief 测试
+ */
+void ECfgTest(void);
+
+
+/*
+
+#include "AT24CXX.h"
+
+static AT24Cxx_t AT24C02;
+static bool EpWrite(uint address, byte* buf, int len)
+{
+	AT24Cxx_t* dev = &AT24C02;
+	return AT24CXXWrite(dev,address,buf,len);
+}
+
+static bool EpRead(uint address, byte* buf, int len)
+{
+	AT24Cxx_t* dev = &AT24C02;
+	return AT24CXXRead(dev,address,buf,len);
+}
+
+
+typedef struct
+{
+    // 演示数据
+	uint Temp[3];
+}EConfig_t;
+
+/// @brief 测试
+/// @param  
+void ECfgTest(void)
+{
+    static I2C_Info_t i2c;	
+    i2c.SCK = PA4;
+    i2c.SDA = PA3;
+    i2c.TimeDelayUs = 1;
+
+	AT24Cxx_t* dev = &AT24C02;
+    AT24CXX_Init(dev,0,2,&i2c);
+
+	ECfgCtrl_t ECtrl;
+    EEPROM_t eapi = { .EWrite = EpWrite, .ERead = EpRead, };
+    ECfgInit(&ECtrl,&eapi,ECfgMaxCap(dev->Capacity));
+
+	EConfig_t cfg;
+	ECfgCtrl_t* ec = &ECtrl;
+	ECfgLoad(ec, &cfg);
+	for (int i = 0; i < ec->CfgCap; i++)
+	{
+		ECfgClean(ec,i);
+	}
+	ECfgLoad(ec, &cfg);
+	
+	EConfig_t ecf;
+	memset(&ecf,0,sizeof(EConfig_t));
+	ecf.Temp[0] = 20;
+	ECfgSave(ec, &ecf);
+
+	for (int i = 0; i < 20; i++)
+	{
+		DebugPrintf("FOR %d\r\n",i);
+
+		ecf.Temp[0]++;
+		ECfgSave(ec, &ecf);
+
+		ECfgLoad(ec, &cfg);
+		// ECfgLoad(ec,&ecf);
+		// ECfItemShow(&ecf);	
+	}
+}
+
+*/
+
Added +414 -0
diff --git a/Core/FConfigBase.c b/Core/FConfigBase.c
new file mode 100644
index 0000000..df8fda2
--- /dev/null
+++ b/Core/FConfigBase.c
@@ -0,0 +1,414 @@
+
+#include "FConfigBase.h"
+#include "Flash.h"
+#include "Crc.h"
+#include "Debug.h"
+#include "Crc.h"
+#include "Array.h"
+
+
+// Config 有效性 取决于 MagicNum SaveIdx Crc32
+typedef struct
+{
+	// 配置实际内容。
+	FConfig_t Config;
+
+	// 魔数  内部使用。 外部不要碰
+	// 默认为 : 'M' 'a' 'g' '&'   0x4D616726
+	uint MagicNum;
+	// 保存号,也是位置号。内部使用。外部不要碰  0-65535
+	ushort Index;
+	// 校验  内部使用。 外部不要碰
+	ushort Crc16;
+}FConfigItem_t;
+
+// 只存一份简化版
+#if 1
+
+static void FixFCfg(FConfigItem_t* cfg, ushort idx);
+static bool FCfgIsOK(FConfigItem_t* cfg);
+
+
+/** @brief 加载配置,只存一份简化版 */
+bool LoadFConfig(FConfig_t* cfg, NorFlash_t* fapi, uint addr)
+{
+	if(cfg == NULL)return false;
+	if(fapi == NULL)return false;
+
+	FConfigItem_t temp;
+	FConfigItem_t* fp = &temp;
+	if(fapi->FlashRead != NULL)
+	{
+		// 读取 配置项
+		memset(fp, 0x00, sizeof(FConfigItem_t));
+		if(!fapi->FlashRead(addr,(byte*)fp,sizeof(FConfigItem_t)))return false;				
+	}
+	else
+	{
+		// 直接指针过去
+		fp = (FConfigItem_t*)addr;
+	}
+
+	// 配置有效性检查
+	if(!FCfgIsOK(fp))return false;
+
+	memcpy(cfg,&fp->Config,sizeof(FConfig_t));
+	return true;
+}
+
+/** @brief 保存配置,只存一份简化版 */
+bool SaveFConfig(FConfig_t* cfg, NorFlash_t* fapi, uint addr)
+{
+	if(cfg == NULL)return false;
+	if(fapi == NULL)return false;
+
+	FConfigItem_t item;
+	if(LoadFConfig(&item.Config,fapi,addr))
+	{
+		if(ArrayEqual((byte*)cfg,(byte*)&item.Config,sizeof(FConfig_t)))return true;
+	}
+
+	memcpy(&item.Config,cfg,sizeof(FConfig_t));
+	FixFCfg(&item,0xffff);
+
+	// 擦
+	if(fapi->FlashErase == NULL)return false;
+	if(!fapi->FlashErase(addr))return false;	
+	// 写
+	if(fapi->FlashWrite == NULL)return false;
+	return fapi->FlashWrite(addr , (byte*)&item, sizeof(FConfigItem_t));
+}
+
+/** @brief 擦除配置,只存一份简化版 */
+bool CleanFConfig(NorFlash_t* fapi, uint addr)
+{
+	if(fapi == NULL)return false;
+	if(fapi->FlashErase == NULL)return false;
+
+	if(!fapi->FlashErase(addr))return false;
+	return true;
+}
+
+#endif
+
+
+/**
+ * @brief 修正内部数据
+ * @param cfg 配置
+ * @param idx 必要条件
+ */
+static void FixFCfg(FConfigItem_t* cfg, ushort idx)
+{
+	cfg->Index = idx;
+	cfg->MagicNum = 0x4D616726;
+	byte* p = (byte*)cfg;
+	int len = offsetof2(FConfigItem_t,Crc16);
+	cfg->Crc16 = CaclcCRC16_MODBUS(p, len);
+	// cfg->Crc16 = CaclcCRC16_MODBUS(p,sizeof(FConfigItem_t) - 2);
+}
+
+/**
+ * @brief 检查配置是否有效
+ * @param cfg
+ */
+static bool FCfgIsOK(FConfigItem_t* cfg) 
+{
+	if(cfg == NULL)return false;
+	if(cfg->MagicNum != 0x4D616726)return false;
+	
+	byte* p = (byte*)cfg;
+	int len = offsetof2(FConfigItem_t,Crc16);
+ 	ushort cl = CaclcCRC16_MODBUS(p, len);
+ 	// ushort cl = CaclcCRC16_MODBUS(p,sizeof(FConfigItem_t) - 2);
+	if(cl != cfg->Crc16)return false;
+
+	return true;
+}
+
+/**
+ * @brief 所在块的块地址
+ * @param ctl
+ * @param idx
+ */
+static uint FCfgSecAddr(FCfgCtrl_t* ctl,int idx)
+{
+	// 块个数
+	int sCnt = 1 << ctl->FSecBit;
+	// 所在块
+	int sec = idx & (sCnt - 1);
+	// 块地址
+	uint addr = ctl->FSecAddrs[sec];
+	return addr;
+}
+
+// #define FSECADDR(ctl,idx)  (ctl->FSecAddrs[(idx & ((1 << ctl->FSecBit) - 1)))])
+// FSECADDR FCfgSecAddr
+
+/**
+ * @brief 块内偏移量
+ * @param ctl
+ * @param idx
+ */
+static uint FSecOffset(FCfgCtrl_t* ctl,int idx)
+{
+	// 块内编号
+	idx >>=  ctl->FSecBit;
+	// 块内个数
+	int cCnt = 1 << ctl->FSecCfgBit;
+	// 取余
+	int sidx = idx & (cCnt - 1);
+	// 块内偏移
+	uint offset = sizeof(FConfigItem_t) * sidx;
+	return offset;
+}
+
+// #define FSECOFFSET(ctl,idx)  (sizeof(FConfigItem_t) * ((idx >> ctl->FSecBit) & (1 << ctl->FSecCfgBit) - 1))))
+// #define FSECOFFSET FSecOffset
+
+/**
+ * @brief 初始化
+ * @param ctl
+ * @param api NOR FLASH 接口
+ * @param sad FLASH 块地址数组
+ * @param sadlen 块数组长度
+ * @param ccnt 块内 存放配置的份数
+ * @return 0 无错
+ */
+int FCfgInit(FCfgCtrl_t* ctl, NorFlash_t* api, uint* sad, byte sadlen, byte ccnt)
+{
+	int err = 0;
+	byte temp = 0;
+	if(ctl == NULL){ err = __LINE__; goto reserr; }
+	if(api == NULL){ err = __LINE__; goto reserr; }
+	if(sad == NULL){ err = __LINE__; goto reserr; }
+	if(sadlen < 2) { err = __LINE__; goto reserr; }
+	if(ccnt < 1)   { err = __LINE__; goto reserr; }
+
+	memcpy(&ctl->FApi,api,sizeof(NorFlash_t));
+	ctl->FSecAddrs = sad;
+
+	// 计算位数
+	temp = 0;
+	for (int i = 1; ; i++)
+	{
+		int cnt = 1 << i;
+		if(sadlen >= cnt)temp = i;
+		else break;
+	}	
+	ctl->FSecBit = temp;
+
+	temp = 0;
+	for (int i = 0; ; i++)
+	{
+		int cnt = 1 << i;
+		if(ccnt >= cnt)temp = i;
+		else break;
+	}
+	ctl->FSecCfgBit = temp;
+	return 0;
+
+	reserr:
+	DebugPrintf("%s [%d] ERROR\r\n",__func__,__LINE__);
+	return err;
+}
+
+/**
+ * @brief 扫描整个 FLASH 存储区,找到最新且有效的配置取出来
+ * @param ctl
+ * @param cfg
+ */
+bool FCfgLoad(FCfgCtrl_t* ctl, FConfig_t* cfg) 
+{
+	if(ctl == NULL)return false;
+	if(ctl->FSecAddrs == NULL)return false;
+	if(ctl->FSecBit < 1)return false;
+	if(cfg == NULL)return false;
+
+	// 最大同时存在配置个数
+	int limit = (1 << ctl->FSecBit) *  (1 << ctl->FSecCfgBit);
+	// 已存在最大IDX
+	int max = -1;
+	// 小于limit的,已存在最大IDX
+	int limitMax = -1;
+	
+	FConfigItem_t temp;
+	FConfigItem_t* fp = &temp;
+	// 块个数
+	int slen = 1 << ctl->FSecBit;
+	// 块内配置个数
+	int sCfgCnt = 1 << ctl->FSecCfgBit;
+
+	for (int i = 0; i < slen; i++)
+	{
+		// 块首地址
+		uint base = ctl->FSecAddrs[i];
+		for (int j = 0; j < sCfgCnt; j++)
+		{			
+			// 得到地址
+			uint addr = sizeof(FConfigItem_t) * j + base;
+			
+			if(ctl->FApi.FlashRead != NULL)
+			{
+				// 读取 配置项
+				memset(fp, 0x00, sizeof(FConfigItem_t));
+				if(!ctl->FApi.FlashRead(addr,(byte*)fp,sizeof(FConfigItem_t)))continue;				
+			}
+			else
+			{ 
+				// 直接指针过去
+				fp = (FConfigItem_t*)addr;
+			}
+
+			// 配置有效性检查
+			if(!FCfgIsOK(fp))continue;
+
+			// 选取最后存入的 idx
+			ushort idx = fp->Index;
+			if(idx > max)max = idx;
+			if(idx < limit)
+			{
+				if(idx > limitMax)limitMax = idx;
+			}			
+		}
+	}
+	
+	// 没有任何有效配置
+	if(max == -1)
+	{
+		ctl->CurrIndex = 0;
+		memset(&ctl->CurrCfg,0x00,sizeof(FConfigItem_t));		
+		return false;
+	}
+
+	// 判定最后写入是哪个
+	int last = -1;
+	if((max > 65535 - limit) && (limitMax != -1))
+	{
+		// 刚好跨界了。大的被丢弃。使用小的里面最大的。
+		last = limitMax;
+	}
+	else
+	{
+		last = max;
+	}
+
+	// 块地址 + 块内偏移
+	uint addr = FCfgSecAddr(ctl,last) + FSecOffset(ctl,last);
+	if(ctl->FApi.FlashRead != NULL)
+	{
+		// 读取 配置项
+		memset(fp, 0x00, sizeof(FConfigItem_t));
+		ctl->FApi.FlashRead(addr,(byte*)fp,sizeof(FConfigItem_t));				
+	}
+	else
+	{ 
+		// 直接指针过去
+		fp = (FConfigItem_t*)addr;
+	}
+
+	memcpy(&ctl->CurrCfg,&fp->Config,sizeof(FConfig_t));
+	ctl->CurrIndex = fp->Index;
+
+	memcpy(cfg,&ctl->CurrCfg,sizeof(FConfig_t));
+	return true;
+}
+
+/**
+ * @brief 写配置进去,地址为块首地址的时候擦除块。
+ * @param ctl
+ * @param cfg
+ */
+static bool FCfgWrite(FCfgCtrl_t* ctl, FConfigItem_t* cfg)
+{
+	if(ctl->FApi.FlashWrite == NULL)return false;
+
+	int idx = cfg->Index;
+	// 块地址
+	uint addr = FCfgSecAddr(ctl,idx);
+	// 块内偏移
+	uint offset = FSecOffset(ctl,idx);
+
+	if(offset == 0)
+	{
+		if(ctl->FApi.FlashErase == NULL)return false;
+		if(!ctl->FApi.FlashErase(addr))return false;
+	}
+	
+	// 写
+	return ctl->FApi.FlashWrite(addr + offset, (byte*)cfg, sizeof(FConfigItem_t));
+}
+
+/**
+ * @brief 保存配置
+ * @param ctl
+ * @param cfg
+ */
+bool FCfgSave(FCfgCtrl_t* ctl, FConfig_t* cfg)
+{
+	if(ctl == NULL)return false;
+	if(ctl->FSecAddrs == NULL)return false;
+	if(ctl->FSecBit < 1)return false;
+	if(cfg == NULL)return false;
+
+	// 相同,不存。
+	FConfig_t* curr = &ctl->CurrCfg;
+	if(!ArrayEqual((byte*)cfg, (byte*)curr,sizeof(FConfig_t)))return true;
+
+	// 下一个位置保存
+	ushort idx = ctl->CurrIndex + 1;
+	FConfigItem_t item;
+	memcpy(&item.Config,cfg,sizeof(FConfig_t));
+	FixFCfg(&item,idx);
+	bool res = FCfgWrite(ctl, &item);
+	// 保存完成,更新本地信息
+	if(res)
+	{
+		memcpy(&ctl->CurrCfg,cfg,sizeof(FConfig_t));
+		ctl->CurrIndex = item.Index;
+	}
+
+	return res;
+}
+
+/**
+ * @brief 删除一个配置
+ * @param ctl
+ * @param idx
+ */
+bool FCfgClean(FCfgCtrl_t* ctl, int idx)
+{
+	if(ctl->FApi.FlashWrite == NULL)return false;
+
+	// 块地址
+	uint addr = FCfgSecAddr(ctl,idx);
+	// 块内偏移
+	uint offset = FSecOffset(ctl,idx);
+
+	// 配置内 magic 位置
+	offset += offsetof2(FConfigItem_t,MagicNum);
+
+	// FLASH 擦完是 ff 只能往 00 写
+	uint magic = 0;
+	return ctl->FApi.FlashWrite(addr+offset, (byte*)&magic, sizeof(magic));
+}
+
+/**
+ * @brief 擦除所有 FLASH 区域,一个不留
+ * @param ctl
+ */
+bool FCfgCleanAll(FCfgCtrl_t* ctl)
+{
+	if(ctl == NULL)return false;
+	if(ctl->FSecAddrs == NULL)return false;
+	if(ctl->FSecBit < 1)return false;
+	if(ctl->FApi.FlashErase == NULL)return false;
+
+	int slen = 1 << ctl->FSecBit;
+	for (int i = 0; i < slen; i++)
+	{
+		ctl->FApi.FlashErase(ctl->FSecAddrs[i]);
+	}
+	ctl->CurrIndex = 0;
+
+	return true;
+}
Added +131 -0
diff --git a/Core/FConfigBase.h b/Core/FConfigBase.h
new file mode 100644
index 0000000..22a12ee
--- /dev/null
+++ b/Core/FConfigBase.h
@@ -0,0 +1,131 @@
+#pragma once
+
+#include "Type.h"
+// 需要在自己项目内补充。 ConfigBase_t 只提供读写保存。
+#include "FConfig.h"
+
+/*
+写很多份配置的驱动。
+NOR FLASH 作为存储载体。
+多擦除块设计。
+处理写入时断电问题。
+*/
+
+// 块:擦除块
+typedef struct
+{
+	// FLASH 写
+	bool (*FlashWrite)(uint address, byte* buf, int len);
+	// 可以直接通过指针访问的 addr ,此函数为 NULL。
+	bool (*FlashRead)(uint address, byte* buf, int len);
+	// FLASH 擦块
+	bool (*FlashErase)(uint address);
+}NorFlash_t;
+
+// 只存一份简化版
+#if 1
+
+/** @brief 加载配置,只存一份简化版 */
+bool LoadFConfig(FConfig_t* cfg, NorFlash_t* fapi, uint addr);
+/** @brief 保存配置,只存一份简化版 */
+bool SaveFConfig(FConfig_t* cfg, NorFlash_t* fapi, uint addr);
+/** @brief 擦除配置,只存一份简化版 */
+bool CleanFConfig(NorFlash_t* fapi, uint addr);
+
+#endif
+
+// 完整多份存储版本,频繁写入,处理FLASH磨损问题
+#if 1
+
+// 对象
+typedef struct
+{
+	// 内当前最新配置,用于对比是否需要写入。
+	FConfig_t CurrCfg;
+	// NOR FLASH 接口
+	NorFlash_t FApi;
+	// FLASH Section 地址数组,数组长度是 FlashSecCnt
+    uint* FSecAddrs;
+	// FLASH Section 块个数。 个数 FlashSecCnt = (1 << FSecBit)
+	byte FSecBit;
+	// FLASH Section 存 Config_t 的份数 = (1 << FSecCfgBit)
+	byte FSecCfgBit;
+
+	// 当前使用的 IDEX 。也是存放位置。
+	ushort CurrIndex;    
+}FCfgCtrl_t;
+
+/* 用法
+
+// FLASH 擦除块地址
+const uint secAddr[] = 
+{ 
+	0    + 0x8000000 + (48 * 1024),
+	1024 + 0x8000000 + (48 * 1024),
+	2048 + 0x8000000 + (48 * 1024),
+	4096 + 0x8000000 + (48 * 1024)
+};
+
+// 单片机内部 NOR FLASH,读不用函数直接用指针,外部FLASH需要写函数
+NorFlash_t nfunc = 
+{
+	.FlashWrite = XXX
+	.FlashRead = NULL,
+	.FlashErase = XXX,
+};
+
+// 直接定义
+FCfgCtrl_t FCtl =
+{
+	.FlashWrite = xxx,
+	.FlashErase = xxx,
+	.BaseAddrs = secAddr,
+	.FSecCnt = 2, 			// (1<<2) 有4块
+	.FSecCfgCnt = 3,		// (1<<3) 存8份
+	.CfgIndex = 0,
+};
+
+或者 使用 FCfgInit 函数配置
+FCfgInit(&FCtl, &nfunc, secAddr, ArrayLength(secAddr), 8);
+
+*/
+
+/**
+ * @brief 初始化
+ * @param ctl
+ * @param api NOR FLASH 接口
+ * @param sad FLASH 块地址数组
+ * @param sadlen 块数组长度
+ * @param ccnt 块内 存放配置的份数
+ * @return 0 无错
+ */
+int FCfgInit(FCfgCtrl_t* ctl, NorFlash_t* api, uint* sad, byte sadlen, byte ccnt);
+
+/**
+ * @brief 扫描整个 FLASH 存储区,找到最新且有效的配置取出来
+ * @param ctl
+ * @param cfg
+ */
+bool FCfgLoad(FCfgCtrl_t* ctl, FConfig_t* cfg);
+
+/**
+ * @brief 保存配置
+ * @param ctl
+ * @param cfg
+ */
+bool FCfgSave(FCfgCtrl_t* ctl, FConfig_t* cfg);
+
+/**
+ * @brief 删除一个配置,应该用不到
+ * @param ctl
+ * @param idx
+ */
+bool FCfgClean(FCfgCtrl_t* ctl, int idx);
+
+/**
+ * @brief 擦除所有 FLASH 区域,一个不留
+ * @param ctl
+ */
+bool FCfgCleanAll(FCfgCtrl_t* ctl);
+
+#endif
Modified +12 -8
diff --git a/Core/GitInfo.bat b/Core/GitInfo.bat
index 1e61317..2e28c88 100644
--- a/Core/GitInfo.bat
+++ b/Core/GitInfo.bat
@@ -5,19 +5,23 @@
 
 
 @echo off
+
+:: Get the directory where this batch file resides (with trailing backslash)
+set "OUT=%~dp0"
+
 where git >nul 2>&1
 if %errorlevel%==0 (
-    echo #define GITINFO \>GitInfo.h
-	:git log -1 --pretty=format:"\"%%ci \r\n%%H \r\n%%d \r\n%%an\"" >> GitInfo.h
-	git log -1 --pretty=format:"\"%%ci \r\n%%H \r\n%%d\"" >> GitInfo.h
+    echo #define GITINFO \>%OUT%GitInfo.h
+	:git log -1 --pretty=format:"\"%%ci \r\n%%H \r\n%%d \r\n%%an\"" >> %OUT%GitInfo.h
+	git log -1 --pretty=format:"\"%%ci \r\n%%H \r\n%%d\"" >> %OUT%GitInfo.h
 ) else (
-    echo #define GITINFO \>GitInfo.h
-	echo "2000-01-01 00:00:00 +0800 \r\n0000000000000000000000000000000000000000 \r\n (HEAD -> NO_GIT, origin/NO_GIT)" >>GitInfo.h
+    echo #define GITINFO \>%OUT%GitInfo.h
+	echo "2000-01-01 00:00:00 +0800 \r\n0000000000000000000000000000000000000000 \r\n (HEAD -> NO_GIT, origin/NO_GIT)" >>%OUT%GitInfo.h
 )
 
 if not %errorlevel%==0 (
-    echo #define GITINFO \>GitInfo.h
-	echo "2000-01-01 00:00:00 +0800 \r\n0000000000000000000000000000000000000000 \r\n (HEAD -> NO_GIT, origin/NO_GIT)" >>GitInfo.h
+    echo #define GITINFO \>%OUT%GitInfo.h
+	echo "2000-01-01 00:00:00 +0800 \r\n0000000000000000000000000000000000000000 \r\n (HEAD -> NO_GIT, origin/NO_GIT)" >>%OUT%GitInfo.h
 )
 
-echo.>>GitInfo.h
+echo.>>%OUT%GitInfo.h
Modified +22 -2
diff --git a/Core/TimeHelper.c b/Core/TimeHelper.c
index c5173cb..5978a3d 100644
--- a/Core/TimeHelper.c
+++ b/Core/TimeHelper.c
@@ -33,13 +33,33 @@ time_t GeTimeStampBase(void)
 	return mktime(&time);
 }
 
+/*
+ * 把 time_t 时间戳转换为 struct tm。
+ * ARMCC(AC5) 和 ARMCLANG(AC6) 的 C 库不提供 POSIX 的 localtime_r(),
+ * 使用标准 C 的 localtime()。GNU/GCC 环境则使用线程安全的 localtime_r()。
+ */
+static void TimeToTm(time_t *tsec, struct tm *out)
+{
+#if defined(__CC_ARM) || defined(__clang__)
+	/* ARM 工具链 (AC5 / AC6)。 */
+	struct tm *ptm = localtime(tsec);
+	if( ptm != NULL )
+	{
+		*out = *ptm;
+	}
+#else
+	/* GNU / GCC 环境。 */
+	localtime_r(tsec, out);
+#endif
+}
+
 void ShowTimeStamp_Sec(int sec)
 {
 	time_t tsec = GeTimeStampBase();
 	tsec += sec;
 
 	struct tm time = {0};
-	localtime_r(&tsec, &time);
+	TimeToTm(&tsec, &time);
 
 	print_tm(&time);
 }
@@ -50,7 +70,7 @@ void ShowTimeStamp_Min(int min)
 	tsec += (min * 60);
 
 	struct tm time = {0};
-	localtime_r(&tsec, &time);
+	TimeToTm(&tsec, &time);
 
 	print_tm(&time);
 }
Modified +0 -11
diff --git a/Core/Type.h b/Core/Type.h
index 04e8508..0c29ce7 100644
--- a/Core/Type.h
+++ b/Core/Type.h
@@ -42,17 +42,6 @@ extern MemOptions_t GlobleMemOpt;
 // 一次设置 GlobleMalloc  GlobleFree  GlobleMemOpt
 void SetGlobleMemOpt(MallocFunc malloc, FreeFunc free);
 
-/**
- * @brief 打印数组内容
- * @param head 数组名称
- * @param pdata 数组指针
- * @param datalen 数组长度
- * @param showlen 打印长度
- */
-void ShowArray(char* name, byte* pdata, int datalen, int showlen);
-// 数组长度
-#define ArrayLength(arr) ((int)(sizeof(arr)/sizeof(arr[0])))
-
 // 大小端转换
 #define __REV16x(x) ((ushort)((x >> 8) & 0x00ff) | ((x << 8) & 0xff00))
 // 大小端切换