嵌入式分享合集26

折腾一天摄像头烦四了 弄点这个把 我们这边还有个sb还fa值 罚款的值吗 搞笑+

一、开关电源的8个损耗

能量转换系统必定存在能耗,虽然实际应用中无法获得100%的转换效率,但是,一个高质量的电源效率可以达到非常高的水平,效率接近95%。绝大多数电源IC 的工作效率可以在特定的工作条件下测得,数据资料中给出了这些参数。一般厂商会给出实际测量的结果,但我们只能对我们自己的数据担保。

    下图1给出了一个SMPS降压转换器的电路实例,转换效率可以达到97%,即使在轻载时也能保持较高效率。采用什么秘诀才能达到如此高的效率?我们最好从了解SMPS损耗的公共问题开始,开关电源的损耗大部分来自开关器件(MOSFET和二极管),另外小部分损耗来自电感和电容。但是,如果使用非常廉价的电感和电容(具有较高电阻),将会导致损耗明显增大。

    选择IC时,需要考虑控制器的架构和内部元件,以期获得高效指标。例如,图1采用了多种方法来降低损耗,其中包括:同步整流,芯片内部集成低导通电阻的MOSFET,低静态电流和跳脉冲控制模式。我们将在本文展开讨论这些措施带来的好处。

    上图,图1:降压转换器集成了低导通电阻的MOSFET,采用同步整流,效率曲线如上。

● 降压型SMPS

    损耗是任何SMPS架构都面临的问题,我们在此以下图2所示降压型(或buck)转换器为例进行讨论,图中标明各点的开关波形,用于后续计算。

    上图,图2。

    降压转换器的主要功能是把一个较高的直流输入电压转换成较低的直流输出电压。为了达到这个要求,MOSFET以固定频率(fS),在脉宽调制信号(PWM)的控制下进行开、关操作。当MOSFET导通时,输入电压给电感和电容(L和COUT)充电,通过它们把能量传递给负载。在此期间,电感电流线性上升,电流回路如图2 中的回路1所示。

    当MOSFET断开时,输入电压断开与电感的连接,电感和输出电容为负载供电。电感电流线性下降,电流流过二极管,电流回路如图中的环路2所示。MOSFET的导通时间定义为PWM信号的占空比(D)。D把每个开关周期分成[D × tS]和[(1 - D) × tS]两部分,它们分别对应于MOSFET的导通时间(环路1)和二极管的导通时间(环路2)。所有SMPS拓扑(降压、反相等)都采用这种方式划分开关周期,实现电压转换。

    对于降压转换电路,较大的占空比将向负载传输较多的能量,平均输出电压增加。相反,占空比较低时,平均输出电压也会降低。根据这个关系,可以得到以下理想情况下(不考虑二极管或MOSFET的压降)降压型SMPS的转换公式:

VOUT = D × VIN

IIN = D × IOUT

    需要注意的是,任何SMPS在一个开关周期内处于某个状态的时间越长,那么它在这个状态所造成的损耗也越大。对于降压型转换器,D越低(相应的VOUT越低),回路2产生的损耗也大。

1、开关器件的损耗MOSFET传导损耗

    上文,图2(以及其它绝大多数DC-DC转换器拓扑)中的MOSFET 和二极管是造成功耗的主要因素。相关损耗主要包括两部分:传导损耗和开关损耗。

    MOSFET和二极管是开关元件,导通时电流流过回路。器件导通时,传导损耗分别由MOSFET的导通电阻(RDS(ON))和二极管的正向导通电压决定。

    MOSFET的传导损耗(PCOND(MOSFET))近似等于导通电阻RDS(ON)、占空比(D)和导通时MOSFET的平均电流(IMOSFET(AVG))的乘积。

    PCOND(MOSFET) (使用平均电流) = IMOSFET(AVG)² × RDS(ON) × D

    上式给出了SMPS 中MOSFET 传导损耗的近似值,但它只作为电路损耗的估算值,因为电流线性上升时所产生的功耗大于由平均电流计算得到的功耗。对于“峰值”电流,更准确的计算方法是对电流峰值和谷值(图3中的IV 和IP)之间的电流波形的平方进行积分得到估算值。 

    上图,图3:典型的降压型转换器的MOSFET 电流波形,用于估算MOSFET 的传导损耗。

    下式给出了更准确的估算损耗的方法,利用IP 和IV 之间电流波形I²的积分替代简单的I²项。

PCOND(MOSFET) = [(IP3 - IV3)/3] × RDS(ON) × D

= [(IP3 - IV3)/3] × RDS(ON) × VOUT/VIN

    式中,IP 和IV 分别对应于电流波形的峰值和谷值,如图3 所示。MOSFET 电流从IV 线性上升到IP,例如:如果IV 为0.25A,IP 为1.75A,RDS(ON)为0.1Ω,VOUT 为VIN/2 (D = 0.5),基于平均电流(1A)的计算结果为:

PCOND(MOSFET) (使用平均电流) = 12 × 0.1 × 0.5 = 0.050W

    利用波形积分进行更准确的计算:

PCOND(MOSFET) (使用电流波形积分进行计算) = [(1.753 - 0.253)/3] × 0.1 × 0.5 = 0.089W

    或近似为78%,高于按照平均电流计算得到的结果。对于峰均比较小的电流波形,两种计算结果的差别很小,利用平均电流计算即可满足要求。

2、二极管传导损耗

    MOSFET 的传导损耗与RDS(ON)成正比,二极管的传导损耗则在很大程度上取决于正向导通电压(VF)。二极管通常比MOSFET 损耗更大,二极管损耗与正向电流、VF 和导通时间成正比。由于MOSFET 断开时二极管导通,二极管的传导损耗(PCOND(DIODE))近似为:

PCOND(DIODE) = IDIODE(ON) × VF × (1 - D)

    式中,IDIODE(ON)为二极管导通期间的平均电流。图2 所示,二极管导通期间的平均电流为IOUT,因此,对于降压型转换器,PCOND(DIODE)可以按照下式估算:

PCOND(DIODE) = IOUT × VF × (1 - VOUT/VIN)

    与MOSFET 功耗计算不同,采用平均电流即可得到比较准确的功耗计算结果,因为二极管损耗与I 成正比,而不是I2。

    显然,MOSFET或二极管的导通时间越长,传导损耗也越大。对于降压型转换器,输出电压越低,二极管产生的功耗也越大,因为它处于导通状态的时间越长。

3、开关动态损耗

    由于开关损耗是由开关的非理想状态引起的,很难估算MOSFET和二极管的开关损耗,器件从完全导通到完全关闭或从完全关闭到完全导通需要一定时间,在这个过程中会产生功率损耗。图4所示MOSFET 的漏源电压(VDS)和漏源电流(IDS)的关系图可以很好地解释MOSFET 在过渡过程中的开关损耗,从上半部分波形可以看出,tSW(ON)和tSW(OFF)期间电压和电流发生瞬变,MOSFET 的电容进行充电、放电。

    图4所示,VDS 降到最终导通状态(= ID × RDS(ON))之前,满负荷电流(ID)流过MOSFET。相反,关断时,VDS 在MOSFET电流下降到零值之前逐渐上升到关断状态的最终值。开关过程中,电压和电流的交叠部分即为造成开关损耗的来源,从图4 可以清楚地看到这一点。

    上图,图4:开关损耗发生在MOSFET 通、断期间的过渡过程。

    开关损耗随着SMPS频率的升高而增大,这一点很容易理解,随着开关频率提高(周期缩短),开关过渡时间所占比例增大,从而增大开关损耗。开关转换过程中,开关时间是占空比的二十分之一对于效率的影响要远远小于开关时间为占空比的十分之一的情况。由于开关损耗和频率有很大的关系,工作在高频时,开关损耗将成为主要的损耗因素。MOSFET 的开关损耗(PSW(MOSFET))可以按照图3 所示三角波进行估算,公式如下:

PSW(MOSFET) = 0.5 × VD × ID × (tSW(ON) + tSW(OFF)) × fS

    其中,VD为MOSFET关断期间的漏源电压,ID是MOSFET导通期间的沟道电流,tSW(ON)和tSW(OFF)是导通和关断时间。对于降压电路转换,VIN 是MOSFET关断时的电压,导通时的电流为IOUT。

    为了验证MOSFET的开关损耗和传导损耗,图5给出了降压转换器中集成高端MOSFET的典型波形:VDS和IDS。电路参数为:VIN = 10V、VOUT = 3.3V、IOUT = 500mA、RDS(ON) = 0.1Ω、fS = 1MHz、开关瞬变时间(tON + tOFF)总计为38ns。

    在下图5,可以看出,开关变化不是瞬间完成的,电流和电压波形交叠部分导致功率损耗。MOSFET“导通”时(图2),流过电感的电流IDS 线性上升,与导通边沿相比,断开时的开关损耗更大。

    利用上述近似计算法,MOSFET的平均损耗可以由下式计算:

PT(MOSFET) = PCOND(MOSFET) + PSW(MOSFET)

= [(I13 - I03)/3] × RDS(ON) × VOUT/VIN + 0.5 × VIN × IOUT × (tSW(ON) + tSW(OFF)) × fS

= [(13 - 03)/3] × 0.1 × 3.3/10 + 0.5 × 10 × 0.5 × (38 × 10-9) × 1 × 106

= 0.011 + 0.095 = 106mW

    这一结果与图5下方曲线测量得到的117.4mW接近。

    注意:这种情况下,fS 足够高,PSW(MOSFET)是功耗的主要因素。 

    上图,图5:降压转换器高端MOSFET的典型开关周期,输入10V、输出3.3V (输出电流500mA)。开关频率为1MHz,开关转换时间是38ns。

    与MOSFET相同,二极管也存在开关损耗。这个损耗很大程度上取决于二极管的反向恢复时间(tRR),二极管开关损耗发生在二极管从正向导通到反向截止的转换过程。

    当反向电压加在二级管两端时,正向导通电流在二极管上产生的累积电荷需要释放,产生反向电流尖峰(IRR(PEAK)),极性与正向导通电流相反,从而造成V × I 功率损耗,因为反向恢复期内,反向电压和反向电流同时存在于二极管。图6给出了二极管在反向恢复期间的PN结示意图: 

    上图,图6:二极管结反偏时,需要释放正向导通期间的累积电荷,产生峰值电流(IRR(PEAK))。

    了解了二极管的反向恢复特性,可以由下式估算二极管的开关损耗(PSW(DIODE)):

PSW(DIODE) = 0.5 × VREVERSE × IRR(PEAK) × tRR2 × fS

    其中,VREVERSE 是二极管的反向偏置电压,IRR(PEAK)是反向恢复电流的峰值,tRR2 是从反向电流峰值IRR 到恢复电流为正的时间。对于降压电路,当MOSFET 导通的时候,VIN 为MOSFET 导通时二极管的反向偏置电压。

    为了验证二极管损耗计算公式,图7 显示了典型的降压转换器中PN 结的开关波形,VIN = 10V、VOUT =3.3V,测得IRR(PEAK) = 250mA、IOUT = 500mA、fS = 1MHz、 tRR2 = 28ns、VF = 0.9V。利用这些数值可以得到:

     该结果接近于图7所示测量结果358.7mW。考虑到较大的VF和较长的二极管导通周期,tRR 时间非常短,开关损耗(PSW(DIODE))在二极管损耗中占主导地位。

    上图,图7:降压型转换器中PN 结开关二极管的开关波形,从10V 输入降至3.3V 输出,输出电流为500mA。其它参数包括:1MHz 的fS,tRR2 为28ns,VF = 0.9V。

● 提高效率

    基于上述讨论,通过哪些途径可以降低电源的开关损耗呢?直接途径是:选择低导通电阻RDS(ON)、可快速切换的MOSFET;选择低导通压降VF、可快速恢复的二极管。

    直接影响MOSFET 导通电阻的因素有几点,通常增加芯片尺寸和漏源极击穿电压(VBR(DSS)),由于增加了器件中的半导体材料,有助于降低导通电阻RDS(ON)。另一方面,较大的MOSFET会增大开关损耗。因此,虽然大尺寸MOSFET降低了RDS(ON),但也导致小器件可以避免的效率问题。当管芯温度升高时,MOSFET导通电阻会相应增大。必须保持较低的结温,使导通电阻RDS(ON)不会过大。导通电阻RDS(ON)和栅源偏置电压成反比。

    因此,推荐使用足够大的栅极电压以降低RDS(ON)损耗,但此时也会增大栅极驱动损耗,需要平衡降低RDS(ON)的好处和增大栅极驱动的缺陷。MOSFET的开关损耗与器件电容有关,较大的电容需要较长的充电时间,使开关切换变缓,消耗更多能量。米勒电容通常在MOSFET数据资料中定义为反向传输电容(CRSS)或栅-漏电容(CGD),在开关过程中对切换时间起决定作用。米勒电容的充电电荷用QGD表示,为了快速切换MOSFET,要求尽可能低的米勒电容。

    一般来说,MOSFET的电容和芯片尺寸成反比,因此必须折衷考虑开关损耗和传导损耗,同时也要谨慎选择电路的开关频率。对于二极管,必须降低导通压降,以降低由此产生的损耗。对于小尺寸、额定电压较低的硅二极管,导通压降一般在0.7V到1.5V之间。二极管的尺寸、工艺和耐压等级都会影响导通压降和反向恢复时间,大尺寸二极管通常具有较高的VF 和tRR,这会造成比较大的损耗。

    开关二极管一般以速度划分,分为“高速”、“甚高速”和“超高速”二极管,反向恢复时间随着速度的提高而降低。快恢复二极管的tRR 为几百纳秒,而超高速快恢复二极管的tRR 为几十纳秒。低功耗应用中,替代快恢复二极管的一种选择是肖特基二极管,这种二极管的恢复时间几乎可以忽略,反向恢复电压VF 也只有快恢复二极管的一半(0.4V 至1V),但肖特基二极管的额定电压和电流远远低于快恢复二极管,无法用于高压或大功率应用。另外,肖特基二极管与硅二极管相比具有较高的反向漏电流,但这些因素并不限制它在许多电源中的应用。

    然而,在一些低压应用中,即便是具有较低压降的肖特基二极管,所产生的传导损耗也无法接受。比如,在输出为1.5V 的电路中,即使使用0.5V 导通压降VF 的肖特基二极管,二极管导通时也会产生33%的输出电压损耗!

    为了解决这一问题,可以选择低导通电阻RDS(ON)的MOSFET实现同步控制架构。用MOSFET 取代二极管(对比图1 和图2 电路),它与电源的主MOSFET 同步工作,所以在交替切换的过程中,保证只有一个导通。导通的二极管由导通的MOSFET 所替代,二极管的高导通压降VF 被转换成MOSFET 的低导通压降(MOSFET RDS(ON) × I),有效降低了二极管的传导损耗。

    当然,同步整流与二极管相比也只是降低了MOSFET的压降,另一方面,驱动同步整流MOSFET的功耗也不容忽略。IC数据资料以上讨论了影响开关电源效率的两个重要因素(MOSFET 和二极管)。回顾图1所示降压电路,从数据资料中可以获得影响控制器IC 工作效率的主要因素。

    首先,开关元件集成在IC内部,可以节省空间、降低寄生损耗。其次,使用低导通电阻RDS(ON)的MOSFET,在小尺寸集成降压IC (如MAX1556)中,其NMOS和PMOS 的导通电阻可以达到0.27Ω (典型值)和0.19Ω (典型值)。最后,使用的同步整流电路。对于500mA负载,占空比为50%的开关电路,可以将低边开关(或二极管)的损耗从225mW (假设二极管压降为 1V)降至 34mW。合理选择SMPS IC合理选择 SMPS IC的封装、控制架构,并进行合理设计,可以有效提高转换效率。

4、集成功率开关

    功率开关集成到IC 内部时可以省去繁琐的MOSFET 或二极管选择,而且使电路更加紧凑,由于降低了线路损耗和寄生效应,可以在一定程度上提高效率。根据功率等级和电压限制,可以把MOSFET、二极管(或同步整流MOSFET)集成到芯片内部。将开关集成到芯片内部的另一个好处是栅极驱动电路的尺寸已经针对片内MOSFET 进行了优化,因而无需将时间浪费在未知的分立MOSFET 上。

● 静态电流

    电池供电设备特别关注IC 规格中的静态电流(IQ),它是维持电路工作所需的电流。重载情况下(大于十倍或百倍的静态电流IQ),IQ 对效率的影响并不明显,因为负载电流远大于IQ,而随着负载电流的降低,效率有下降的趋势,因为IQ 对应的功率占总功率的比例提高。

    这一点对于大多数时间处于休眠模式或其它低功耗模式的应用尤其重要,许多消费类产品即使在“关闭”状态下,也需要保持键盘扫描或其它功能的供电,这时,无疑需要选择具有极低IQ的电源。

● 电源架构对效率的提高

    SMPS 的控制架构是影响开关电源效率的关键因素之一。这一点我们已经在同步整流架构中讨论过,由于采用低导通电阻的MOSFET 取代了功耗较大的开关二极管,可有效改善效率指标。

    另一种重要的控制架构是针对轻载工作或较宽的负载范围设计的,即跳脉冲模式,也称为脉冲频率调制(PFM)。与单纯的PWM 开关操作(在重载和轻载时均采用固定的开关频率)不同,跳脉冲模式下转换器工作在跳跃的开关周期,可以节省不必要的开关操作,进而提高效率。

    跳脉冲模式下,在一段较长时间内电感放电,将能量从电感传递给负载,以维持输出电压。当然,随着负载吸收电流,输出电压也会跌落。当电压跌落到设置门限时,将开启一个新的开关周期,为电感充电并补充输出电压。

    需要注意的是跳脉冲模式会产生与负载相关的输出噪声,这些噪声由于分布在不同频率(与固定频率的PWM 控制架构不同),很难滤除。

    先进的SMPS IC 会合理利用两者的优势:重载时采用恒定PWM 频率;轻载时采用跳脉冲模式以提高效率,图1 所示IC即提供了这样的工作模式。

    当负载增加到一个较高的有效值时,跳脉冲波形将转换到固定PWM,在标称负载下噪声很容易滤除。在整个工作范围内,器件根据需要选择跳脉冲模式和PWM 模式,保持整体的最高效率(图8)。

    下图8中的曲线D、E、F所示效率曲线在固定PWM模式下,轻载时效率较低,但在重载时能够提供很高的转换效率(高达98%)。如果设置在轻载下保持固定PWM 工作模式,IC将不会按照负载情况更改工作模式。这种情况下能够使纹波保持在固定频率,但浪费了一定功率。重载时,维持PWM 开关操作所需的额外功率很小,远远低于输出功率。

    另一方面,跳脉冲“空闲”模式下的效率曲线(图8中的A、B、C)能够在轻载时保持在较高水平,因为开关只在负载需要时开启。对7V输入曲线,在1mA负载的空闲模式下能够获得高于60%的效率。

    上图,图8:降压转换器在PWM 和空闲(跳脉冲)模式下效率曲线,注意:轻载时,空闲模式下的效率高于PWM模式。

  • 优化SMPS

    开关电源因其高效率指标得到广泛应用,但其效率仍然受SMPS电路的一些固有损耗的制约。设计开关电源时,需要仔细研究造成SMPS损耗的来源,合理选择SMPS IC,从而充分利用器件的优势,为了在保持尽可能低的电路成本,甚至不增加电路成本的前提下获得高效的SMPS,工程师需要做出全面的选择。

5、无源元件损耗

    我们已经了解MOSFET和二极管会导致SMPS损耗。采用高品质的开关器件能够大大提升效率,但它们并不是唯一能够优化电源效率的元件。

    图1详细介绍了一个典型的降压型转换器IC的基本电路。集成了两个同步整流MOSFET,低RDS(ON) MOSFET,效率很高。这个电路中,开关元件集成在IC内部,已经为具体应用预先选择了元器件。然而,为了进一步提高效率,设计人员还需关注无源元件—外部电感和电容,了解它们对功耗的影响。

6、电感功耗阻性损耗

    电感功耗包括线圈损耗和磁芯损耗两个基本因素,线圈损耗归结于线圈的直流电阻(DCR),磁芯损耗归结于电感的磁特性。

    DCR 定义为以下电阻公式:

    式中,ρ为线圈材料的电阻系数,l为线圈长度,A为线圈横截面积。

    DCR将随着线圈长度的增大而增大,随着线圈横截面积的增大而减小。可以利用该原则判断标准电感,确定所要求的不同电感值和尺寸。对一个固定的电感值,电感尺寸较小时,为了保持相同匝数必须减小线圈的横截面积,因此导致DCR增大;对于给定的电感尺寸,小电感值通常对应于小的DCR,因为较少的线圈数减少了线圈长度,可以使用线径较粗的导线。

    已知DCR和平均电感电流(具体取决于SMPS 拓扑),电感的电阻损耗(PL(DCR))可以用下式估算:

PL(DCR) = LAVG2× DCR

    这里,IL(AVG)是流过电感的平均直流电流。对于降压转换器,平均电感电流是直流输出电流。尽管DCR的大小直接影响电感电阻的功耗,该功耗与电感电流的平方成正比,因此,减小DCR 是必要的。

    另外,还需要注意的是:利用电感的平均电流计算PL(DCR) (如上述公式)时,得到的结果略低于实际损耗,因为实际电感电流为三角波。本文前面介绍的MOSFET 传导损耗计算中,利用对电感电流的波形进行积分可以获得更准确的结果。更准确。当然也更复杂的计算公式如下:

PL(DCR) = (IP3 - IV3)/3 × DCR

    式中IP 和IV 为电感电流波形的峰值和谷值。

7、磁芯损耗

    磁芯损耗并不像传导损耗那样容易估算,很难估测。它由磁滞、涡流损耗组成,直接影响铁芯的交变磁通。SMPS 中,尽管平均直流电流流过电感,由于通过电感的开关电压的变化产生的纹波电流导致磁芯周期性的磁通变化。

    磁滞损耗源于每个交流周期中磁芯偶极子的重新排列所消耗的功率,可以将其看作磁场极性变化时偶极子相互摩擦产生的“摩擦”损耗,正比于频率和磁通密度。

    相反,涡流损耗则是磁芯中的时变磁通量引入的。由法拉第定律可知:交变磁通产生交变电压。因此,这个交变电压会产生局部电流,在磁芯电阻上产生I2R 损耗。

    磁芯材料对磁芯损耗的影响很大。SMPS 电源中普遍使用的电感是铁粉磁芯,铁镍钼磁粉芯(MPP)的损耗最低,铁粉芯成本最低,但磁芯损耗较大。

    磁芯损耗可以通过计算磁芯磁通密度(B)的最大变化量估算,然后查看电感或铁芯制造商提供的磁通密度和磁芯损耗(和频率)图表。峰值磁通密度可以通过几种方式计算,公式可以在电感数据资料中的磁芯损耗曲线中找到。

    相应地,如果磁芯面积和线圈数已知,可利用下式估计峰值磁通: 

    这里,B 是峰值磁通密度(高斯),L 是线圈电感(亨),ΔI 是电感纹波电流峰峰值(安培),A 是磁芯横截面积(cm2),N 是线圈匝数。

    随着互联网的普及,可以方便地从网上下载资料、搜索器件信息,一些制造商提供了交互式电感功耗的计算软件,帮助设计者估计功耗。使用这些工具能够快捷、准确地估计应用电路中的功率损耗。例如,Coilcraft 提供的在线电感磁芯损耗和铜耗计算公式,简单输入一些数据即可得到所选电感的磁芯损耗和铜耗。

8、电容损耗

    与理想的电容模型相反,电容元件的实际物理特性导致了几种损耗。电容在SMPS 电路中主要起稳压、滤除输入/输出噪声的作用(图1),电容的这些损耗降低了开关电源的效率。这些损耗主要表现在三个方面:等效串联电阻损耗、漏电流损耗和电介质损耗。

    电容的阻性损耗显而易见。既然电流在每个开关周期流入、流出电容,电容固有的电阻(RC)将造成一定功耗。漏电流损耗是由于电容绝缘材料的电阻(RL)导致较小电流流过电容而产生的功率损耗。电介质损耗比较复杂,由于电容两端施加了交流电压,电容电场发生变化,从而使电介质分子极化造成功率损耗。

    上图,图9:电容损耗模型一般简化为一个等效串联电阻(ESR)。

    所有三种损耗都体现在电容的典型损耗模型中(图9 左边部分),用电阻代表每项损耗。与电容储能相关的每项损耗的功率用功耗系数(DF)表示,或损耗角正切(δ)。每项损耗的DF 可以通过由电容阻抗的实部与虚部比得到,可以将每项损耗分别插入模型中。

    为简化损耗模型,图9中的接触电阻损耗、漏电流损耗和电介质损耗集中等为一个等效串联电阻(ESR)。ESR 定义为电容阻抗中消耗有功功率的部分。

    推算电容阻抗模型、计算ESR (结果的实部)时,ESR 是频率的函数。这种相关性可以在下面简化的ESR等式中得到证明:

    式中,DFR、DFL 和DFD 是接触电阻、漏电流和电介质损耗的功耗系数。

    利用这个等式,我们可以观察到随着信号频率的增加,漏电流损耗和电介质损耗都有所减小,直到接触电阻损耗从一个较高频点开始占主导地位。在该频点(式中没有包括该参数)以上,ESR 因为高频交流电流的趋肤效应趋于增大。

    许多电容制造商提供ESR 曲线图表示ESR 与频率的关系。例如,TDK 为其大多数电容产品提供了ESR 曲线,参考这些与开关频率对应曲线图,得到ESR 值。

    然而,如果没有ESR 曲线图,可以通过电容数据资料中的DF 规格粗略估算ESR。DF 是电容的整体DF (包括所有损耗),也可以按照下式估算ESR: 

    无论采用哪种方法来得到ESR 值,直觉告诉我们,高ESR 会降低开关电源效率,既然输入和输出电容在每个开关周期通过ESR 充电、放电。这导致I2× RESR 功率损耗。这个损耗(PCAP(ESR))可以按照下式计算:

PCAP(ESR) = ICAP(RMS)2 × RESR

    式中,ICAP(RMS)是流经电容的交流电流有效值RMS。对降压电路的输出电容,可以采用电感纹波电流的有效值RMS。输入滤波电容的RMS 电流的计算比较复杂,可以按照下式得到一个合理的估算值:

ICIN(RMS) = IOUT/VIN × [VOUT (VIN - VOUT)]1/2

    显然,为减小电容功率损耗,应选择低ESR 电容,有助于SMPS 电源降低纹波电流。ESR 是产生输出电压纹波的主要原因,因此选择低ESR 的电容不仅仅单纯提高效率,还能得到其它好处。

    一般来说,不同类型电介质的电容具有不同的ESR 等级。对于特定的容量和额定电压,铝电解电容和钽电容就比陶瓷电容具有更高的ESR 值。聚酯和聚丙烯电容的ESR 值介于它们之间,但这些电容尺寸较大,SMPS 中很少使用。whaosoft aiot http://143ai.com

    对于给定类型的电容,较大容量、较低的fS 能够提供较低的ESR。大尺寸电容通常也会降低ESR,但电解电容会带来较大的等效串联电感。陶瓷电容被视为比较好的折中选择,此外,电容值一定的条件下,较低的电容额定电压也有助于减小ESR。

二、单片机工作电压5V的来历

5V来自于TTL电平。5为True,0为False,之后用了压降更低的PN节,衍生出了3.3这个电平。

    12V和24V来自于汽车电瓶,早年乘用车又12V和24V两个系统,现在一般小型车12V,商用车24V,再究其由来应该是铅酸电池。

    所以3.3V和5V一般出现在信号电路或者单片机等VCC供电,而12V/24V一般出现在低压动力电,例如主板、显卡、轴流风机、监控器。硬件决定系统基础,如果锂电池早点应用的话估计还会有3.7/7.4这个系统。

    为什么很多单片机的工作电压是5v?

    因为大多数芯片都是5V的TTL电平,要做到电平兼容,电平匹配,避免要电平转换操作,所有很多单片机的工作电压都是5V。早期(196x)的晶体管电路(TTL)单管的压降是0.7V。一个电路里经常有多个晶体管串联。比如4管串联,电源至少保证0.7x4=2.8v才能保证电路正常工作。所以最早有3V 5V等标准。后来LM7805(197x)电源IC出来以后,5V成了事实标准。

    TTL指的是TTL电平,0~5V之间,小于0.2V输出低电平,高于3.4V输出高电平。全称Transistor-Transistor Logic,即BJT-BJT逻辑门电路,是数字电子技术中常用的一种逻辑门电路,应用较早,技术已比较成熟。TTL主要有BJT(Bipolar Junction Transistor 即双极结型晶体管,晶体三极管)和电阻构成,具有速度快的特点。最早的TTL门电路是74系列,后来出现了74H系列,74L系列,74LS,74AS,74ALS等系列。

    但是由于TTL功耗大等缺点,正逐渐被CMOS电路取代,TL输出高电平》2.4V,输出低电平《0.4V。在室温下,一般输出高电平是3.5V,输出低电平是0.2V。最小输入高电平和低电平:输入高电平》=2.0V,输入低电平《=0.8V,噪声容限是0.4V。

    为什么很多都是5V,而且有大量电源芯片支持的也是5V。

    电压浮动为5%,而电压标准,在A/D当中使用,标准应该是5.12V。

    因为512 是2的N次方,这样A/D 的每一个字都是一个整数,当作为无符号计算的时候,更简单,但是没见到哪个成品用这个电压的,大部分都是5V,为什么不用呢?

    因为做5.12的标准电压成本会成倍增长。5V与5.12V精度差别在百倍,小数点后0.12V,基本很难做到高精度标准电压,市场通用电压为5V,上浮一定百分比。

    2008年11月发布的STC12系列单片机数据手册中,STC12C系列的单片机电压范围是3.3~5.5V;STC12L系列的单片机电压范围是2.2~3.6V。如果选择STC12C系列的单片机,只要单片机的工作频率不是太高,使用3.7V供电是没有任何顾虑的,官方声称单片机的抗干扰能力可以达到4000V,但实际应用说法不一。

    大多数单片机都是 TTL 电平,各自的高低电平定义不一样;

    当电源电压为5V时:51,AVR单片机是5V;

    当电源电压为3.3V时:51,AVR单片机高电平是3.3V;

    ARM如LPC2138,电源电压只能为3.3V,IO输出高电平为3.3V;延庆川北小区45孙老师 收卖废品破烂垃圾炒股 废品孙 再回收

    但IO口可承受5V电压现在单片机工作电压主要有两种:一种工作在3.3V 一种工作在5V。

三、STM32单片机的知识点2

本文将以STM32F10x为例,对标准库开发进行概览。主要分为三块内容:

  • STM32系统结构

  • 寄存器

  • 通过点灯案例,详解如何基于标准库构建STM32工程

STM32系统结构

    上图,STM32f10xxx系统结构。

内核IP

    从结构框图上看,Cortex-M3内部有若干个总线接口,以使CM3能同时取址和访内(访问内存),它们是:指令存储区总线(两条)、系统总线、私有外设总线。有两条代码存储区总线负责对代码存储区(即 FLASH 外设)的访问,分别是 I-Code 总线和 D-Code 总线。

    I-Code用于取指,D-Code用于查表等操作,它们按最佳执行速度进行优化。

    系统总线(System)用于访问内存和外设,覆盖的区域包括SRAM,片上外设,片外RAM,片外扩展设备,以及系统级存储区的部分空间。

    私有外设总线负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。

    还有一个DMA总线,从字面上看,DMA是data memory access的意思,是一种连接内核和外设的桥梁,它可以访问外设、内存,传输不受CPU的控制,并且是双向通信。简而言之,这个家伙就是一个速度很快的且不受老大控制的数据搬运工。

处理器外设(内核之外的外设)

    从结构框图上看,STM32的外设有串口、定时器、IO口、FSMC、SDIO、SPI、I2C等,这些外设按照速度的不同,分别挂载到AHB、APB2、APB1这三条总线上。

寄存器

    什么是寄存器?寄存器是内置于各个IP外设中,是一种用于配置外设功能的存储器,并且有想对应的地址。一切库的封装始于映射。

    是不是看的眼都花了,如果进行寄存器开发,就需要怼地址以及对寄存器进行字节赋值,不仅效率低而且容易出错。

    库的存在就是为了解决这类问题,将代码语义化。语义化思想不仅仅是嵌入式有的,前端代码也在追求语义特性。

从点灯开始学习STM32

内核库文件分析

cor_cm3.h

    这个头文件实现了:

  • 内核结构体寄存器定义。 

  • 内核寄存器内存映射。 

  • 内存寄存器位定义。跟处理器相关的头文件stm32f10x.h实现的功能一样,一个是针对内核的寄存器,一个是针对内核之外,即处理器的寄存器。

misc.h

    内核应用函数库头文件,对应stm32f10x_xxx.h。

misc.c

    内核应用函数库文件,对应stm32f10x_xxx.c。在CM3这个内核里面还有一些功能组件,如NVIC、SCB、ITM、MPU、CoreDebug,CM3带有非常丰富的功能组件,但是芯片厂商在设计MCU的时候有一些并不是非要不可的,是可裁剪的,比如MPU、ITM等在STM32里面就没有。

    其中NVIC在每一个CM3内核的单片机中都会有,但都会被裁剪,只能是CM3 NVIC的一个子集。在NVIC里面还有一个SysTick,是一个系统定时器,可以提供时基,一般为操作系统定时器所用。misc.h和mics.c这两个文件提供了操作这些组件的函数,并可以在CM3内核单片机直接移植。

处理器外设库文件分析

startup_stm32f10x_hd.s

    这个是由汇编编写的启动文件,是STM32上电启动的第一个程序,启动文件主要实现了

  • 初始化堆栈指针 SP;

  • 设置 PC 指针=Reset_Handler ;

  • 设置向量表的地址,并 初始化向量表,向量表里面放的是 STM32 所有中断函数的入口地址

  • 调用库函数 SystemInit,把系统时钟配置成 72M,SystemInit 在库文件 stytem_stm32f10x.c 中定义;

  • 跳转到标号_main,最终去到 C 的世界。

system_stm32f10x.c

    这个文件的作用是里面实现了各种常用的系统时钟设置函数,有72M,56M,48, 36,24,8M,我们使用的是是把系统时钟设置成72M。

Stm32f10x.h

    这个头文件非常重要,这个头文件实现了:

  • 处理器外设寄存器的结构体定义。

  • 处理器外设的内存映射。

  • 处理器外设寄存器的位定义。

    关于 1 和 2 我们在用寄存器点亮 LED 的时候有讲解。

    其中 3:处理器外设寄存器的位定义,这个非常重要,具体是什么意思?

    我们知道一个寄存器有很多个位,每个位写 1 或者写 0 的功能都是不一样的,处理器外设寄存器的位定义就是把外设的每个寄存器的每一个位写 1 的 16 进制数定义成一个宏,宏名即用该位的名称表示,如果我们操作寄存器要开启某一个功能的话,就不用自己亲自去算这个值是多少,可以直接到这个头文件里面找。

    我们以片上外设 ADC 为例,假设我们要启动 ADC 开始转换,根据手册我们知道是要控制 ADC_CR2 寄存器的位 0:ADON,即往位 0 写 1,即:

ADC->CR2=0x00000001;

    这是一般的操作方法。现在这个头文件里面有关于 ADON 位的位定义:

#define ADC_CR2_ADON ((uint32_t)0x00000001)

有了这个位定义,我们刚刚的代码就变成了:

ADC->CR2=ADC_CR2_ADON

stm32f10x_xxx.h

    外设 xxx 应用函数库头文件,这里面主要定义了实现外设某一功能的结构体,比如通用定时器有很多功能,有定时功能,有输出比较功能,有输入捕捉功能,而通用定时器有非常多的寄存器要实现某一个功能。

    比如定时功能,我们根本不知道具体要操作哪些寄存器,这个头文件就为我们打包好了要实现某一个功能的寄存器,是以机构体的形式定义的,比如通用定时器要实现一个定时的功能,我们只需要初始化 TIM_TimeBaseInitTypeDef 这个结构体里面的成员即可,里面的成员就是定时所需要操作的寄存器。

    有了这个头文件,我们就知道要实现某个功能需要操作哪些寄存器,然后再回手册中精度这些寄存器的说明即可。

stm32f10x_xxx.c

    stm32f10x_xxx.c:外设 xxx 应用函数库,这里面写好了操作 xxx 外设的所有常用的函数,我们使用库编程的时候,使用的最多的就是这里的函数。

SystemInit

    工程中新建main.c 。

    在此文件中编写main函数后直接编译会报错:

Undefined symbol SystemInit (referred from startup_stm32f10x_hd.o).

    错误提示说SystemInit没有定义。从分析启动文件startup_stm32f10x_hd.s时我们知道。

;Reset handlerReset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT __main;IMPORT SystemInit;LDR R0, =SystemInitBLX R0LDR R0, =__mainBX R0ENDP
汇编中;分号是注释的意思

    第五行第六行代码Reset_Handler调用了SystemInit该函数用来初始化系统时钟,而该函数是在库文件system_stm32f10x.c中实现的。我们重新写一个这样的函数也可以,把功能完整实现一遍,但是为了简单起见,我们在main文件里面定义一个SystemInit空函数,为的是骗过编译器,把这个错误去掉。

    关于配置系统时钟之后会出文章RCC时钟树详细介绍,主要配置时钟控制寄存器(RCC_CR)和时钟配置寄存器(RCC_CFGR)这两个寄存器,但最好是直接使用CubeMX直接生成,因为它的配置过程有些冗长。

    如果我们用的是库,那么有个库函数SystemInit,会帮我们把系统时钟设置成72M。

    现在我们没有使用库,那现在时钟是多少?答案是8M,当外部HSE没有开启或者出现故障的时候,系统时钟由内部低速时钟LSI提供,现在我们是没有开启HSE,所以系统默认的时钟是LSI=8M。

库封装层级

    如图,达到第四层级便是我们所熟知的固件库或HAL库的效果。当然库的编写还需要考虑许多问题,不止于这些内容。我们需要的是了解库封装的大概过程。

    将库封装等级分为四级来介绍是为了有层次感,就像打怪升级一样,进行认知理解的升级。

    我们都知道,操作GPIO输出分三大步:

时钟控制:

    STM32 外设很多,为了降低功耗,每个外设都对应着一个时钟,在系统复位的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。

    STM32 的所有外设的时钟由一个专门的外设来管理,叫RCC(reset and clockcontrol),RCC 在STM32 参考手册的第六章。

    STM32 的外设因为速率的不同,分别挂载到三条总系上:AHB、APB2、APB1,AHB为高速总线,APB2 次之,APB1 再次之。所以的IO 口都挂载到APB2 总线上,属于高速外设。

模式配置:

    这个由端口配置寄存器来控制。端口配置寄存器分为高低两个,每4bit 控制一个IO 口,所以端口配置低寄存器:CRL 控制这IO 口的低8 位,端口配置高寄存器:CRH控制这IO 口的高8bit。

    在4 位一组的控制位中,CNFy[1:0] 用来控制端口的输入输出,MODEy[1:0]用来控制输出模式的速率,又称驱动电路的响应速度,注意此处速率与程序无关,GPIO引脚速度、翻转速度、输出速度区别输入有4种模式,输出有4种模式,我们在控制LED 的时候选择通用推挽输出。

    输出速率有三种模式:2M、10M、50M,这里我们选择2M。

电平控制:

    STM32的IO口比较复杂,如果要输出1和0,则要通过控制:端口输出数据寄存器ODR来实现,ODR 是:Output data register的简写,在STM32里面,其寄存器的命名名称都是英文的简写,很容易记住。

    从手册上我们知道ODR是一个32位的寄存器,低16位有效,高16位保留。低16位对应着IO0~IO16,只要往相应的位置写入0或者1就可以输出低或者高电平。

    第一层级:基地址宏定义 

    时钟控制:

    在STM32中,每个外设都有一个起始地址,叫做外设基地址,外设的寄存器就以这个基地址为标准按照顺序排列,且每个寄存器32位,(后面作为结构体里面的成员正好内存对齐)。

查表看到时钟由APB2外设时钟使能寄存器(RCC_APB2ENR)来控制,其中PB端口的时钟由该寄存器的位3写1使能。我们可以通过基地址+偏移量0x18,算出RCC_APB2ENR的地址为:0x40021018。那么使能PB口的时钟代码则如下所示:

 #define RCC_APB2ENR *(volatile unsigned long *)0x40021018
 // 开启端口B 时钟 RCC_APB2ENR |= 1<<3;

    模式配置:

    同RCC_APB2ENR一样,GPIOB的起始地址是:0X4001 0C00,我们也可以算出GPIO_CRL的地址为:0x40010C00。那么设置PB0为通用推挽输出,输出速率为2M的代码则如下所示:

    同上,从手册中我们看到ODR寄存器的地址偏移是:0CH,可以算出GPIOB_ODR寄存器的地址是:0X4001 0C00 + 0X0C = 0X4001 0C0C。现在我们就可以定义GPIOB_ODR这个寄存器了,代码如下:​​​​​​​

#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
//PB0 输出低电平GPIOB_ODR = 0<<0;

    第一层级:基地址宏定义完成用STM32控制一个LED的完整代码:

#define RCC_APB2ENR *(volatile unsigned long *)0x40021018#define GPIOB_CRL *(volatile unsigned long *)0x40010C00#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void){
   
    // 开启端口B 的时钟 RCC_APB2ENR |= 1<<3;
 // 配置PB0 为通用推挽输出模式,速率为2M GPIOB_CRL = (2<<0) | (0<<2);
 // PB0 输出低电平,点亮LED GPIOB_ODR = 0<<0;}
void SystemInit(void){
   
   }

    第二层级:基地址宏定义+结构体封装

外设寄存器结构体封装

    上面我们在操作寄存器的时候,操作的是寄存器的绝对地址,如果每个寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占32个或者16个字节,这种方式跟结构体里面的成员类似。

    所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可。

    下面我们先定义一个GPIO寄存器结构体,结构体里面的成员是GPIO的寄存器,成员的顺序按照寄存器的偏移地址从低到高排列,成员类型跟寄存器类型一样。

typedef struct {
   
    volatile uint32_t CRL; volatile uint32_t CRH; volatile uint32_t IDR; volatile uint32_t ODR; volatile uint32_t BSRR; volatile uint32_t BRR; volatile uint32_t LCKR;} GPIO_TypeDef;

    在《STM32 中文参考手册》8.2 寄存器描述章节,我们可以找到结构体里面的7个寄存器描述。在点亮LED的时候我们只用了CRL和ODR这两个寄存器,至于其他寄存器的功能大家可以自行看手册了解。

    在GPIO结构体里面我们用了两个数据类型,一个是uint32_t,表示无符号的32位整型,因为GPIO的寄存器都是32位的。这个类型声明在标准头文件stdint.h 里面使用typedef对unsigned int重命名,我们在程序上只要包含这个头文件即可。

    另外一个是volatile作用就是告诉编译器这里的变量会变化不因优化而省略此指令,必须每次都直接读写其值,这样就能确保每次读或者写寄存器都真正执行到位。

外设封装

    STM32F1系列的GPIO端口分A~G,即GPIOA、GPIOB。。。。。。GPIOG。每个端口都含有GPIO_TypeDef结构体里面的寄存器,我们可以根据手册各个端口的基地址把GPIO的各个端口定义成一个GPIO_TypeDef类型指针,然后我们就可以根据端口名(实际上现在是结构体指针了)来操作各个端口的寄存器,代码实现如下:

#define GPIOA ((GPIO_TypeDef *) 0X4001 0800)#define GPIOB ((GPIO_TypeDef *) 0X4001 0C00)#define GPIOC ((GPIO_TypeDef *) 0X4001 1000)#define GPIOD ((GPIO_TypeDef *) 0X4001 1400)#define GPIOE ((GPIO_TypeDef *) 0X4001 1800)#define GPIOF ((GPIO_TypeDef *) 0X4001 1C00)#define GPIOG ((GPIO_TypeDef *) 0X4001 2000)

外设内存映射

    讲到基地址的时候我们再引人一个知识点:Cortex-M3存储器系统,这个知识点在《Cortex-M3权威指南》第5章里面讲到。CM3的地址空间是4GB,如下图所示:

    我们这里要讲的是片上外设,就是我们所说的寄存器的根据地,其大小总共有512MB,512MB是其极限空间,并不是每个单片机都用得完,实际上各个MCU厂商都只是用了一部分而已。STM32F1系列用到了:0x4000 0000 ~0x5003 FFFF。现在我们说的STM32的寄存器就是位于这个区域。

APB1、APB2、AHB 总线基地址

    现在我们说的STM32的寄存器就是位于这个区域,这里面ST设计了三条总线:AHB、APB2和APB1,其中AHB和APB2是高速总线,APB1是低速总线。不同的外设根据速度不同分别挂载到这三条总线上。

    从下往上依次是:APB1、APB2、AHB,每个总线对应的地址分别是:APB1:0x40000000,APB2:0x4001 0000,AHB:0x4001 8000。

    这三条总线的基地址我们是从《STM32 中文参考手册》2.3小节—存储器映像得到的:APB1的基地址是TIM2定时器的起始地址,APB2的基地址是AFIO的起始地址,AHB的基地址是SDIO的起始地址。其中APB1地址又叫做外设基地址,是所有外设的基地址,叫做PERIPH_BASE。

    现在我们把这三条总线地址用宏定义出来,以后我们在定义其他外设基地址的时候,只需要在这三条总线的基址上加上偏移地址即可,代码如下:

#define PERIPH_BASE ((uint32_t)0x40000000)#define APB1PERIPH_BASE PERIPH_BASE#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)

GPIO 端口基地址

    因为GPIO挂载到APB2总线上,那么现在我们就可以根据APB2的基址算出各个GPIO端口的基地址,用宏定义实现代码如下:

#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)

    第二层级:基地址宏定义+结构体封装完成用STM32控制一个LED的完整代码:​​​​​​​

#incle <stdint.h>#define __IO volatile
typedef struct {
   
    __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR;} GPIO_TypeDef;
typedef struct {
   
    __IO uint32_t CR; __IO uint32_t CFGR; __IO uint32_t CIR; __IO uint32_t APB2RSTR; __IO uint32_t APB1RSTR; __IO uint32_t AHBENR; __IO uint32_t APB2ENR; __IO uint32_t APB1ENR; __IO uint32_t BDCR; __IO uint32_t CSR;} RCC_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)#define RCC ((RCC_TypeDef *) RCC_BASE)

#define RCC_APB2ENR *(volatile unsigned long *)0x40021018#define GPIOB_CRL *(volatile unsigned long *)0x40010C00#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void){
   
    // 开启端口B 的时钟 RCC->APB2ENR |= 1<<3;
 // 配置PB0 为通用推挽输出模式,速率为2M GPIOB->CRL = (2<<0) | (0<<2);
 // PB0 输出低电平,点亮LED GPIOB->ODR = 0<<0;}
void SystemInit(void){
   
   }

    第二层级变化:

    ①、定义一个外设(GPIO)寄存器结构体,结构体的成员包含该外设的所有寄存器,成员的排列顺序跟寄存器偏移地址一样,成员的数据类型跟寄存器的一样。

    ②外设内存映射,即把地址跟外设建立起一一对应的关系。

    ③外设声明,即把外设的名字定义成一个外设寄存器结构体类型的指针。

    ④通过结构体操作寄存器,实现点亮LED。

    第三层级:基地址宏定义+结构体封装+“位封装”(每一位的对应字节封装)

上面我们在控制GPIO输出内容的时候控制的是ODR(Output data register)寄存器,ODR是一个16位的寄存器,必须以字的形式控制其实我们还可以控制BSRR和BRR这两个寄存器来控制IO的电平,下面我们简单介绍下BRR寄存器的功能,BSRR自行看手册研究。

    位清除寄存器BRR只能实现位清0操作,是一个32位寄存器,低16位有效,写0没影响,写1清0。现在我们要使PB0输出低电平,点亮LED,则只要往BRR的BR0位写1即可,其他位为0,代码如下:

GPIOB->BRR = 0X0001;

    这时PB0就输出了低电平,LED就被点亮了。

    如果要PB2输出低电平,则是:

GPIOB->BRR = 0X0004;

    如果要PB3/4/5/6。。。。。。这些IO输出低电平呢?

    道理是一样的,只要往BRR的相应位置赋不同的值即可。因为BRR是一个16位的寄存器,位数比较多,赋值的时候容易出错,而且从赋值的16进制数字我们很难清楚的知道控制的是哪个IO。

    这时,我们是否可以把BRR的每个位置1都用宏定义来实现,如GPIO_Pin_0就表示0X0001,GPIO_Pin_2就表示0X0004。只要我们定义一次,以后都可以使用,而且还见名知意。“位封装”(每一位的对应字节封装) 代码如下:​​​​​​​

#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */

    这时PB0就输出了低电平的代码就变成了:

GPIOB->BRR = GPIO_Pin_0;

    如果同时让PB0/PB15输出低电平,用或运算,代码:

GPIOB->BRR = GPIO_Pin_0|GPIO_Pin_15;

    为了不使main函数看起来冗余,上述库封装 的代码不应该放在main里面,因为其是跟GPIO相关的,我们可以把这些宏放在一个单独的头文件里面。

    在工程目录下新建stm32f10x_gpio.h,把封装代码放里面,然后把这个文件添加到工程里面。这时我们只需要在main.c里面包含这个头文件即可。

    第四层级:基地址宏定义+结构体封装+“位封装”+函数封装

    我们点亮LED的时候,控制的是PB0这个IO,如果LED接到的是其他IO,我们就需要把GPIOB修改成其他的端口,其实这样修改起来也很快很方便。

    但是为了提高程序的可读性和可移植性,我们是否可以编写一个专门的函数用来复位GPIO的某个位,这个函数有两个形参,一个是GPIOX(X=A...G),另外一个是GPIO_Pin(0...15),函数的主体则是根据形参GPIOX 和GPIO_Pin来控制BRR寄存器,代码如下:

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){
   
    GPIOx->BRR = GPIO_Pin;}

    这时,PB0输出低电平,点亮LED的代码就变成了:

GPIO_ResetBits(GPIOB,GPIO_Pin_0);

    同理, 我们可以控制BSRR这个寄存器来实现关闭LED,代码如下:

// GPIO 端口置位函数void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){
   
    GPIOx->BSRR = GPIO_Pin;}

    这时,PB0输出高电平,关闭LED的代码就变成了:

GPIO_SetBits(GPIOB,GPIO_Pin_0);

    同样,因为这个函数是控制GPIO的函数,我们可以新建一个专门的文件来放跟gpio有关的函数。相关文章:STM32中GPIO工作原理详解

    在工程目录下新建stm32f10x_gpio.c,把GPIO相关的函数放里面。这时我们是否发现刚刚新建了一个头文件stm32f10x_gpio.h,这两个文件存放的都是跟外设GPIO相关的。

    C文件里面的函数会用到h头文件里面的定义,这两个文件是相辅相成的,故我们在stm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h这个头文件。别忘了把stm32f10x.h这个头文件也包含进去,因为有关寄存器的所有定义都在这个头文件里面。

    如果我们写其他外设的函数,我们也应该跟GPIO一样,新建两个文件专门来存函数,比如RCC这个外设我们可以新建stm32f10x_rcc.c和stm32f10x_rcc.h。其他外依葫芦画瓢即可。

实例编写

    以上,是对库封住过程的概述,下面我们正在地使用库函数编写LED程序。

①管理库的头文件

    当我们开始调用库函数写代码的时候,有些库我们不需要,在编译的时候可以不编译,可以通过一个总的头文件stm32f10x_conf.h来控制,该头文件主要代码如下:

    这里面包含了全部外设的头文件,点亮一个LED我们只需要RCC和GPIO 这两个外设的库函数即可,其中RCC控制的是时钟,GPIO控制的具体的IO口。所以其他外设库函数的头文件我们注释掉,当我们需要的时候就把相应头文件的注释去掉即可。

    stm32f10x_conf.h这个头文件在stm32f10x.h这个头文件的最后面被包含,在第8296行:

#ifdef USE_STDPERIPH_DRIVER#include "stm32f10x_conf.h"#endif

    代码的意思是,如果定义了USE_STDPERIPH_DRIVER这个宏的话,就包含stm32f10x_conf.h这个头文件。

    我们在新建工程的时候,在魔术棒选项卡C/C++中,我们定义了USE_STDPERIPH_DRIVER 这个宏,所以stm32f10x_conf.h 这个头文件就被stm32f10x.h包含了,我们在写程序的时候只需要调用一个头文件:stm32f10x.h即可。

②编写LED初始化函数

    经过寄存器点亮LED的操作,我们知道操作一个GPIO输出的编程要点大概如下:

1、开启GPIO的端口时钟

2、选择要具体控制的IO口,即pin

3、选择IO口输出的速率,即speed

4、选择IO口输出的模式,即mode

5、输出高/低电平

    STM32的时钟功能非常丰富,配置灵活,为了降低功耗,每个外设的时钟都可以独自的关闭和开启。STM32中跟时钟有关的功能都由RCC这个外设控制,RCC中有三个寄存器控制着所以外设时钟的开启和关闭:RCC_APHENR、RCC_APB2ENR和RCC_APB1ENR,AHB、APB2和APB1代表着三条总线,所有的外设都是挂载到这三条总线上,GPIO属于高速的外设,挂载到APB2总线上,所以其时钟有RCC_APB2ENR控制。

GPIO 时钟控制

    固件库函数:RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE)函数的原型为:

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph,                              FunctionalState NewState){
   
   /* Check the parameters */ assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph)); assert_param(IS_FUNCTIONAL_STATE(NewState));if (NewState != DISABLE)  {
   
     RCC->APB2ENR |= RCC_APB2Periph; } else {
   
     RCC->APB2ENR &= ~RCC_APB2Periph; }}

    当程序编译一次之后,把光标定位到函数/变量/宏定义处,按键盘的F12或鼠标右键的Go to definition of,就可以找到原型。固件库的底层操作的就是RCC外设的APB2ENR这个寄存器,宏RCC_APB2Periph_GPIOB的原型是:0x00000008,即(1<<3),还原成存器操作就是:RCC->APB2ENR |= 1<<<3。相比固件库操作,寄存器操作的代码可读性就很差,只有才查阅寄存器配置才知道具体代码的功能,而固件库操作恰好相反,见名知意。

GPIO 端口配置

    GPIO的pin,速度,模式,都由GPIO的端口配置寄存器来控制,其中IO0~IO7由端口配置低寄存器CRL控制,IO8~IO15由端口配置高寄存器CRH配置。固件库把端口配置的pin,速度和模式封装成一个结构体:

typedef struct{
   
   uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode;} GPIO_InitTypeDef;

    pin可以是GPIO_Pin_0~GPIO_Pin_15或者是GPIO_Pin_All,这些都是库预先定义好的宏。speed也被封装成一个结构体:

typedef enum{
   
    GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz} GPIOSpeed_TypeDef;

    速度可以是10M,2M或者50M,这个由端口配置寄存器的MODE位控制,速度是针对IO口输出的时候而言,在输入的时候可以不用设置。mode也被封装成一个结构体:​​​​​​​

typedef enum{
   
    GPIO_Mode_AIN = 0x0, // 模拟输入 GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入(复位后的状态) GPIO_Mode_IPD = 0x28, // 下拉输入 GPIO_Mode_IPU = 0x48, // 上拉输入 GPIO_Mode_Out_OD = 0x14, // 通用开漏输出 GPIO_Mode_Out_PP = 0x10, // 通用推挽输出 GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 GPIO_Mode_AF_PP = 0x18 // 复用推挽输出} GPIOMode_TypeDef;

    IO口的模式有8种,输入输出各4种,由端口配置寄存器的CNF配置。平时用的最多的就是通用推挽输出,可以输出高低电平,驱动能力大,一般用于接数字器件。

    最终用固件库实现就变成这样:

// 定义一个GPIO_InitTypeDef 类型的结构体GPIO_InitTypeDef GPIO_InitStructure;
// 选择要控制的IO 口GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 设置引脚为推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 设置引脚速率为50MHzGPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
/*调用库函数,初始化GPIOB0*/GPIO_Init(GPIOB, &GPIO_InitStructure);

    倘若同一端口下不同引脚有不同的模式配置,每次对每个引脚配置完成后都要调用GPIO初始化函数,代码如下:

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ;                      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          //上拉输入GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;                     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO 输出控制

    GPIO输出控制,可以通过端口数据输出寄存器ODR、端口位设置/清除寄存器BSRR和端口位清除寄存器BRR这三个来控制。端口输出寄存器ODR是一个32位的寄存器,低16位有效,对应着IO0~IO15,只能以字的形式操作,

// PB0 输出高电平,点亮LEDGPIOB->ODR = 1<<0;

    端口位清除寄存器BRR是一个32位的寄存器,低十六位有效,对应着IO0~IO15,只能以字的形式操作,可以单独对某一个位操作,写1清0。

// PB0 输出低电平,点亮LEDGPIO_ResetBits(GPIOB, GPIO_Pin_0);

    BSRR是一个32位的寄存器,低16位用于置位,写1有效,高16位用于复位,写1有效,相当于BRR寄存器。高16位我们一般不用,而是操作BRR这个寄存器,所以BSRR这个寄存器一般用来置位操作。

// PB0 输出高电平,熄灭LEDGPIO_SetBits(GPIOB, GPIO_Pin_0);

    综上:固件库LED GPIO初始化函数。

void LED_GPIO_Config(void){
   
   // 定义一个GPIO_InitTypeDef 类型的结构体 GPIO_InitTypeDef GPIO_InitStructure;
// 开启GPIOB 的时钟 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
// 选择要控制的IO 口 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 设置引脚为推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 设置引脚速率为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIOB0*/ GPIO_Init(GPIOB, &GPIO_InitStructure);
// 关闭LED GPIO_SetBits(GPIOB, GPIO_Pin_0);}

主函数

#include "stm32f10x.h"

void SOFT_Delay(__IO uint32_t nCount);void LED_GPIO_Config(void);
int main(void){
   
   // 程序来到main 函数之前,启动文件:statup_stm32f10x_hd.s 已经调用// SystemInit()函数把系统时钟初始化成72MHZ// SystemInit()在system_stm32f10x.c 中定义// 如果用户想修改系统时钟,可自行编写程序修改
 LED_GPIO_Config();
while ( 1 )  {
   
   // 点亮LED  GPIO_ResetBits(GPIOB, GPIO_Pin_0);  Time_Delay(0x0FFFFF);
// 熄灭LED  GPIO_SetBits(GPIOB, GPIO_Pin_0);  Time_Delay(0x0FFFFF); }}// 简陋的软件延时函数void Time_Delay(volatile uint32_t Count){
   
   for (; Count != 0; Count--);}

    注意void Time_Delay(volatile uint32_t Count)只是一个简陋的软件延时函数,如果小伙伴们有兴趣可以看一看MultiTimer,它是一个软件定时器扩展模块,可无限扩展所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。

猜你喜欢

转载自blog.csdn.net/qq_29788741/article/details/126130221