惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
F
Fortinet All Blogs
Cloudbric
Cloudbric
P
Palo Alto Networks Blog
T
Threatpost
T
Tor Project blog
T
Tenable Blog
AWS News Blog
AWS News Blog
Project Zero
Project Zero
L
LangChain Blog
Cyberwarzone
Cyberwarzone
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
C
CERT Recently Published Vulnerability Notes
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Security Latest
Security Latest
云风的 BLOG
云风的 BLOG
I
Intezer
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
A
Arctic Wolf
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
MongoDB | Blog
MongoDB | Blog
aimingoo的专栏
aimingoo的专栏
K
Kaspersky official blog
Jina AI
Jina AI
N
News | PayPal Newsroom
T
The Blog of Author Tim Ferriss
D
DataBreaches.Net
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Secure Thoughts
TaoSecurity Blog
TaoSecurity Blog
P
Privacy & Cybersecurity Law Blog
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
IT之家
IT之家
Forbes - Security
Forbes - Security
The Hacker News
The Hacker News
Last Week in AI
Last Week in AI
T
Threat Research - Cisco Blogs
Y
Y Combinator Blog

空屿

塔塔次方“铝坨坨”三模机械键盘开箱——诚意满满的EVO75 - 空屿 产品开发日志:红外遥控原理——NEC协议与美的R05D解码分析 - 空屿 在曼谷追星,在大城府看遗迹:6天泰国旅行记 - 空屿 千岛湖三日游记:在湖畔遇见银河 - 空屿 产品开发日志:MCU Bootloader 利用网络实现远程升级的方法 - 空屿 延时摄影入门:拍摄步骤、相机模式与后期合成分享 - 空屿 摄影布光入门:人像三点布光在手办静物拍摄中的实践 - 空屿 缝纫机成品小结及线迹记录 - 空屿 Bing居然屏蔽了我的网站?一年终于放出来了! - 空屿 产品开发日志:合宙Air724UG与云端服务器进行TCP直连 - 空屿
产品开发日志:运放电路的计算和使用 - 空屿
HelloGakki · 2025-07-29 · via 空屿

请注意,本文编写于 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这个结论。其实这个电路也可以称为放大一倍的电路。为什么要使用这个电路呢?一般情况下后一级的电路有内阻,那么前一级分压就会受到影响。我们知道运放的输入阻抗无穷大,而输出阻抗无穷小,基于此特性,可以用于信号的隔离。所以这个项目中温度采集电路就可以用到这个应用。

电压跟随
电压跟随

反向放大电路

反向放大电路如下图:

反向放大电路
反向放大电路
  1. 根据虚断,$i_{-}=0$,所以$i_{R1}=i_{R3}$
  2. 根据虚短,$V_{+}=V_{-}=0V$
  3. 所以,$\frac{V_{vin}}{R_{1}}+\frac{V_{out}}{R_{3}}=0V$ 即$V_{out}=-\frac{R_{3}}{R_{1}}V_{vin}$
  4. $R_{2}$是平衡电阻

同相放大电路

同相放大电路分析和反向差不多,电路如下图

同相放大电路
同相放大电路
  1. 根据虚断,$i_{-}=0$,所以$i_{R1}=i_{R3}$
  2. 根据虚短,$V_{+}=V_{-}=V_{vin}$
  3. 所以,$\frac{V_{out}-V_{vin}}{R_{3}}=\frac{V_{vin}}{R_{1}}$ 即$V_{out}=(1+\frac{R_{3}}{R_{1}})V_{vin}$
  4. $R_{2}$是平衡电阻

差分放大电路

差分放大电路是放大的两个输入的差值,所以输入信号是交流信号。电路图和输入输出波形图见下图。

差分放大电路
差分放大电路
波形图
波形图
  1. 交流电输出分别是$V_{i+}和V_{i-}$
  2. 根据虚断,$i_{-}=0$,所以$i_{R1}=i_{R3}$
  3. 根据虚短,$V_{+}=V_{-}$
  4. $\frac{V_{i-}-V_{-}}{R_{1}}=\frac{V_{-}-V_{out}}{R_{3}}$
  5. $\frac{V_{i+}-V_{-}}{R_{2}}=\frac{V_{-}}{R_{4}}$
  6. 一般情况下使$R_{4}=R_{3},R_{1}=R_{2}$
  7. 带入以上得出:$V_{out}=\frac{R_{3}}{R_{1}}(V_{i+}-V_{i-})$

电流采集电路

我们的目的是采集交流电的电流大小,表现在电路上就是电压的大小。另外我们的MCU电路只能采集0~VDD的电压,所以运放还需要增加偏置电压,将负电压抬高。那么电路就如下:

采集电路
采集电路
波形图
波形图
  1. 我们使用一个电压跟随电路输出1.65V的偏置电压到$V_{i-}$
  2. 这个电路其实是同相放大器+偏置电压的电路,至于为什么不用差分放大器+偏置电压,可以自己用仿真器试一下,这里就不展开讲了
  3. 根据虚断,$i_{-}=0$,所以$i_{R2}=i_{R4}$
  4. 根据虚短,$V_{+}=V_{-}=V_{i+}$
  5. 所以,$\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_{偏置}$
  6. 我们的最终目的是算电流,交流电电流就是有效值电流,那么$V_{有效}=\frac{V_{pp}}{2\sqrt{2}}$,$i_{有效}=\frac{V_{有效}}{R_{3}}$
  7. 峰峰值怎么来的?就是两个$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. 当互感器没有产生感应电流的时候,电压为1.65即基准电压是1.65,AD值则为2047;
  2. 交流电输入,交流电输出,50hz工频也就是20毫秒一个周期,我设置的心跳是500微秒一次,所以刚好40次采样次数;
  3. 计算有效电流根据电流采集电路这一小节的公式,另外在根据互感器匝比计算出实际每一相的电流,也就是81~96行;
  4. 最后需要根据实际情况进行补偿计算,还是那句话,实际电路应用中都会有误差;

最后

模拟电路部分计算起来确实比数字电路要复杂一点,现代电气工程好多以前的经典电路都被封装成了一个个IC,使用起来十分方便,但掌握基本的计算方法还是有必要的,毕竟IC贵不是?


本文作者:HelloGakki

本文链接:https://pinaland.cn/archives/development-log-opamp-circuit-design.html

版权声明:所有文章除特别声明外均系本人自主创作,本文遵循署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议,转载请注明出处。