请注意,本文编写于 320 天前,最后修改于 318 天前,其中某些信息可能已经过时。
前言
在博文使用合宙AIR724UG进行TCP连接中提到的项目需要检测三相电路的电流和温度。三相电路中的电流一般很大,我们不能直接接触检测,所以就需要用互感器来将大电流变成小电流然后采样使用。而温度部分的数据相对比较简单,直接使用NTC分压后采样使用。
硬件部分
三路温度+三路电流+环境温度+剩余电流一共8路检测口,我使用74HC4052D和MCP6004来节省IO。
模拟开关74HC4052D
74HC405D可以通过两个选择口来控制2路四选一输出。每一路通过S0, S1以及低有效使能E来控制选择四个独立输入和一个输出相连。来看下原理图和增值表:


从上面增值表可以看出,E低有效,S0:S1=0~3 来对应选择那一个输入和输出相连。所以我们将8路输入分为4路温度,3路电流使用模拟开关来控制检测具体某一路,剩余电流单独接运放。
运放MCP6004
运放MCP6004是一款14脚四合一的运放芯片,可以很方便的搭建不同的运放应用电路,首先看下该芯片的管脚原理图。

在介绍两个电路前,要知道两个知识点,运放的虚短与虚短,即运放的两个输入端电压相等(虚短),且运放的两个输入端输入电流为零(虚断)。
电压跟随电路
下图是运放的一种特殊应用方式,根据上一节的提到的两个知识点,很容易得到Vout=Vin这个结论。其实这个电路也可以称为放大一倍的电路。为什么要使用这个电路呢?一般情况下后一级的电路有内阻,那么前一级分压就会受到影响。我们知道运放的输入阻抗无穷大,而输出阻抗无穷小,基于此特性,可以用于信号的隔离。所以这个项目中温度采集电路就可以用到这个应用。

反向放大电路
反向放大电路如下图:

- 根据虚断,$i_{-}=0$,所以$i_{R1}=i_{R3}$
- 根据虚短,$V_{+}=V_{-}=0V$
- 所以,$\frac{V_{vin}}{R_{1}}+\frac{V_{out}}{R_{3}}=0V$ 即$V_{out}=-\frac{R_{3}}{R_{1}}V_{vin}$
- $R_{2}$是平衡电阻
同相放大电路
同相放大电路分析和反向差不多,电路如下图

- 根据虚断,$i_{-}=0$,所以$i_{R1}=i_{R3}$
- 根据虚短,$V_{+}=V_{-}=V_{vin}$
- 所以,$\frac{V_{out}-V_{vin}}{R_{3}}=\frac{V_{vin}}{R_{1}}$ 即$V_{out}=(1+\frac{R_{3}}{R_{1}})V_{vin}$
- $R_{2}$是平衡电阻
差分放大电路
差分放大电路是放大的两个输入的差值,所以输入信号是交流信号。电路图和输入输出波形图见下图。


- 交流电输出分别是$V_{i+}和V_{i-}$
- 根据虚断,$i_{-}=0$,所以$i_{R1}=i_{R3}$
- 根据虚短,$V_{+}=V_{-}$
- $\frac{V_{i-}-V_{-}}{R_{1}}=\frac{V_{-}-V_{out}}{R_{3}}$
- $\frac{V_{i+}-V_{-}}{R_{2}}=\frac{V_{-}}{R_{4}}$
- 一般情况下使$R_{4}=R_{3},R_{1}=R_{2}$
- 带入以上得出:$V_{out}=\frac{R_{3}}{R_{1}}(V_{i+}-V_{i-})$
电流采集电路
我们的目的是采集交流电的电流大小,表现在电路上就是电压的大小。另外我们的MCU电路只能采集0~VDD的电压,所以运放还需要增加偏置电压,将负电压抬高。那么电路就如下:


- 我们使用一个电压跟随电路输出1.65V的偏置电压到$V_{i-}$
- 这个电路其实是同相放大器+偏置电压的电路,至于为什么不用差分放大器+偏置电压,可以自己用仿真器试一下,这里就不展开讲了
- 根据虚断,$i_{-}=0$,所以$i_{R2}=i_{R4}$
- 根据虚短,$V_{+}=V_{-}=V_{i+}$
- 所以,$\frac{V_{out}-V_{i+}}{R_{4}}=\frac{V_{i+}-V_{偏置}}{R_{2}}$ 即$V_{out}=(1+\frac{R_{4}}{R_{2}})V_{i+}-\frac{R_{4}}{R_{2}}V_{偏置}$
- 我们的最终目的是算电流,交流电电流就是有效值电流,那么$V_{有效}=\frac{V_{pp}}{2\sqrt{2}}$,$i_{有效}=\frac{V_{有效}}{R_{3}}$
- 峰峰值怎么来的?就是两个$V_{out}$在正半轴和负半轴最大的值相减,即$V_{pp}=(1+\frac{R_{4}}{R_{2}})V_{vpp}$
软件部分
和使用合宙AIR724UG进行TCP连接中相反,这部分内容的软件比硬件简单很多。测温部分就是按照探头的B值和R(25℃)进行计算,一般推荐使用查表法,毕竟现实情况是都会有误差。电流采样则是根据上面的电流采集电路一节中提到的计算公式来计算就可以了。
温度采集
static const u16 stemperatureTable[] = {
382,402,424,446,469,493,518,544,570,598, // -20~-11
626,656,686,717,749,782,816,851,886,922, // -10~-1
959,997,1036,1076,1116,1156,1198,1240,1282,1325, // 0~9
1369,1413,1457,1502,1547,1592,1637,1683,1729,1774, // 10~19
1820,1866,1911,1957,2002,2047,2092,2136,2180,2224, // 20~29
2267,2310,2353,2395,2436,2477,2517,2557,2596,2634, // 30~39
2671,2708,2745,2780,2815,2849,2882,2916,2948,2979, // 40~49
3010,3040,3069,3098,3125,3153,3179,3205,3230,3255, // 50~59
3278,3302,3324,3346,3368,3388,3409,3428,3447,3466, // 60~69
3484,3501,3518,3535,3551,3566,3582,3596,3610,3624, // 70~70
3638,3651,3663,3675,3687,3698,3709,3721,3731,3741, // 80~89
3751,3760,3770,3779,3787,3796,3803,3812,3819,3827, // 90~99
3834,3840,3847,3853,3859,3866,3871,3877,3882,3888, // 100~109
3893,3898,3904,3908,3913,3918,3923,3928,3932,3936, // 110~119
3941}; // 120使用查表法,那么就先要把NTC供应商提供的对照表计算成对应的AD值保存在表中。
/*----------------------------------------------------------------
*Function: TemperatureHandle
*Description: 温度数据处理
*Input: none
*Output: none
*Return: none
*Others: none
//----------------------------------------------------------------*/
void TemperatureHandle(void)
{
static u16 u16Buff[4];
u16 temp[4];
s8 temperature[4];
u8 i, j;
static u8 inCheck[4];
if(++inCheck[0] < 9)
{
u16Buff[0] += gsAdcValue.temperatureA;
}
else
{
inCheck[0] = 0;
temp[0] = (u16Buff[0] >> 3);
u16Buff[0] = 0;
}
if(++inCheck[1] < 9)
{
u16Buff[1] += gsAdcValue.temperatureB;
}
else
{
inCheck[1] = 0;
temp[1] = (u16Buff[1] >> 3);
u16Buff[1] = 0;
}
if(++inCheck[2] < 9)
{
u16Buff[2] += gsAdcValue.temperatureC;
}
else
{
inCheck[2] = 0;
temp[2] = (u16Buff[2] >> 3);
u16Buff[2] = 0;
}
if(++inCheck[3] < 9)
{
u16Buff[3] += gsAdcValue.temperatureAmb;
}
else
{
inCheck[3] = 0;
temp[3] = (u16Buff[3] >> 3);
u16Buff[3] = 0;
}
for(i = 0; i < 4; i++)
{
if(u16Buff[i] == 0)
{
for(j = 0; j < (sizeof(stemperatureTable)/sizeof(u16)); j++)
{
if(temp[i] >= stemperatureTable[j])
{
temperature[i] = j-20;
}
}
gsModuleProperty.temperatureA.value = temperature[0];
gsModuleProperty.temperatureA.dataType = TCP_NUM;
gsModuleProperty.temperatureA.updata = SLAVE;
gsModuleProperty.temperatureB.value = temperature[1];
gsModuleProperty.temperatureB.dataType = TCP_NUM;
gsModuleProperty.temperatureB.updata = SLAVE;
gsModuleProperty.temperatureC.value = temperature[2];
gsModuleProperty.temperatureC.dataType = TCP_NUM;
gsModuleProperty.temperatureC.updata = SLAVE;
gsModuleProperty.temperatureAmb.value = temperature[3];
gsModuleProperty.temperatureAmb.dataType = TCP_NUM;
gsModuleProperty.temperatureAmb.updata = SLAVE;
}
}
}这是温度计算的部分,非常简单,先采样8次来取平均,之后带入到表中比较,得出的值-20就是温度值。
/*----------------------------------------------------------------
*Function: CurrentHandle
*Description: 电流数据处理
*Input: none
*Output: none
*Return: none
*Others: none
//----------------------------------------------------------------*/
void CurrentHandle(void)
{
u8 i;
FP32 vout;
static u16 maxTemp[4] = {2047, 2047, 2047, 2047}, minTemp[4] = {2047, 2047, 2047, 2047};
static u16 maxTempBuff[4], minTempBuff[4];
static u8 cnt;
static u8 inCheck[4];
if(inCheck[0]++ < 40)
{
maxTemp[0] = (gsAdcValue.currentA > maxTemp[0])?gsAdcValue.currentA:maxTemp[0];
minTemp[0] = (gsAdcValue.currentA < minTemp[0])?gsAdcValue.currentA:minTemp[0];
}
else
{
inCheck[0] = 0;
maxTempBuff[0] += maxTemp[0];
minTempBuff[0] += minTemp[0];
maxTemp[0] = 2047;
minTemp[0] = 2047;
}
if(inCheck[1]++ < 40)
{
maxTemp[1] = (gsAdcValue.currentB > maxTemp[1])?gsAdcValue.currentB:maxTemp[1];
minTemp[1] = (gsAdcValue.currentB < minTemp[1])?gsAdcValue.currentB:minTemp[1];
}
else
{
inCheck[1] = 0;
maxTempBuff[1] += maxTemp[1];
minTempBuff[1] += minTemp[1];
maxTemp[1] = 2047;
minTemp[1] = 2047;
}
if(inCheck[2]++ < 40)
{
maxTemp[2] = (gsAdcValue.currentC > maxTemp[2])?gsAdcValue.currentC:maxTemp[2];
minTemp[2] = (gsAdcValue.currentC < minTemp[2])?gsAdcValue.currentC:minTemp[2];
}
else
{
inCheck[2] = 0;
maxTempBuff[2] += maxTemp[2];
minTempBuff[2] += minTemp[2];
maxTemp[2] = 2047;
minTemp[2] = 2047;
}
if(inCheck[3]++ < 40)
{
maxTemp[3] = (gsAdcValue.currentResidual > maxTemp[3])?gsAdcValue.currentResidual:maxTemp[3];
minTemp[3] = (gsAdcValue.currentResidual < minTemp[3])?gsAdcValue.currentResidual:minTemp[3];
}
else
{
inCheck[3] = 0;
maxTempBuff[3] += maxTemp[3];
minTempBuff[3] += minTemp[3];
maxTemp[3] = 2047;
minTemp[3] = 2047;
}
if((inCheck[0] == 0) && (inCheck[1] == 0) && (inCheck[2] == 0) && (inCheck[3] == 0))
{
if(++cnt >= 8)
{
cnt = 0;
for(i = 0; i < (sizeof(maxTempBuff) / sizeof(u16)); i++)
{
maxTempBuff[i] = maxTempBuff[i] >> 3;
minTempBuff[i] = minTempBuff[i] >> 3;
if(i < 3)
{
vout = (maxTempBuff[i] - minTempBuff[i]) / 2 / CURRENT_OP_GAIN;
vout = vout / 4095 * CURRENT_VDD;
vout = vout / 1.4142 / CURRENT_SENSE_RES * CURRENT_TRANS_GAIN;
vout *= CURRENT_COMPENSATION;
}
else
{
vout = (maxTempBuff[i] - minTempBuff[i]) / 2 / CURRENT_OP_GAIN2;
vout = vout / 4095 * CURRENT_VDD;
vout = vout / 1.4142 / CURRENT_SENSE_RES2 * CURRENT_TRANS_GAIN2;
vout = (vout >= 0.013)?(vout-0.013):0; // 补偿
vout = vout * 1000;
// vout *= CURRENT_COMPENSATION;
}
switch (i)
{
case 0:
vout = (vout >= 0.1)?(vout-0.1):0; // 补偿
gsModuleProperty.currentA.value = vout;
gsModuleProperty.currentA.dataType = TCP_NUM;
gsModuleProperty.currentA.updata = SLAVE; // 更新标识
break;
case 1:
vout = (vout >= 0.79)?(vout-0.79):0; // 补偿
gsModuleProperty.currentB.value = vout;
gsModuleProperty.currentB.dataType = TCP_NUM;
gsModuleProperty.currentB.updata = SLAVE; // 更新标识
break;
case 2:
vout = (vout >= 0.28)?(vout-0.28):0; // 补偿
gsModuleProperty.currentC.value = vout;
gsModuleProperty.currentC.dataType = TCP_NUM;
gsModuleProperty.currentC.updata = SLAVE; // 更新标识
break;
case 3:
gsModuleProperty.currentResidual.value = vout;
gsModuleProperty.currentResidual.dataType = TCP_NUM;
gsModuleProperty.currentResidual.updata = SLAVE;
default:
break;
}
maxTempBuff[i] = minTempBuff[i] = 0; // 清除缓存
}
}
}
}这里需要注意几个点:
- 当互感器没有产生感应电流的时候,电压为1.65即基准电压是1.65,AD值则为2047;
- 交流电输入,交流电输出,50hz工频也就是20毫秒一个周期,我设置的心跳是500微秒一次,所以刚好40次采样次数;
- 计算有效电流根据电流采集电路这一小节的公式,另外在根据互感器匝比计算出实际每一相的电流,也就是81~96行;
- 最后需要根据实际情况进行补偿计算,还是那句话,实际电路应用中都会有误差;
最后
模拟电路部分计算起来确实比数字电路要复杂一点,现代电气工程好多以前的经典电路都被封装成了一个个IC,使用起来十分方便,但掌握基本的计算方法还是有必要的,毕竟IC贵不是?
本文作者:HelloGakki
本文链接:https://pinaland.cn/archives/development-log-opamp-circuit-design.html
版权声明:所有文章除特别声明外均系本人自主创作,本文遵循署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议,转载请注明出处。

























