昨天忘了发 今天发俩个~~  弄了俩天雷达 终于要发给部队了哈哈

一、三点式LC正弦波振荡器电路

最近突然对正弦波振荡器来了兴趣,但是看到三点式振荡器之后疑问就来了,主要是其中对谐振条件:X1+X2+X3=0的解释,查了好多资料都没有详细的解释,貌似都是把这个条件当做已知来使用,至于它怎么来的则没有好一些的解释。还有三点式振荡器的选频网络和反馈的接法,越看越头晕。在经过了几天的搬砖之后终于有了结果,这里写一下我的见解,如有错误之处欢迎指正。

正弦波振荡器原理

嵌入式分享合集80_开关电源

如图所示,假设放大器在 Ui 的作用下能稳定的输出正弦波,当把开关S切换到2时,有 

嵌入式分享合集80_嵌入式硬件_02

所以得到 

嵌入式分享合集80_嵌入式硬件_03

 

写得再具体点就是 

嵌入式分享合集80_开关电源_04

 

即整个系统是关于ω 的函数,当ω =ω 0 时系统稳定。

频率稳定条件

前面讨论的平衡条件包含两个方面,一个是系统(包括放大器和反馈网络)的总增益为1,另一个是整个系统的相角为0(即相位移动为2nπ)。

频率稳定过程

嵌入式分享合集80_嵌入式硬件_05

如图,当震荡频率等于 f0 时反馈回路的增益最大,且移相为0,即每次反馈后的电压Uf与原输入电压 Ui 同相。并有 

 

嵌入式分享合集80_开关电源_06

由于正弦电压的角频率是瞬时相位对时间的导数值,所以当电路受到外界干扰导致ω>ω0 时,反馈回路的移相为负值,阻止了电压的超前趋势,反过来当 ω<ω0 时也是相同的道理,由此频率被稳定在ω0。

嵌入式分享合集80_开关电源_07

 振幅稳定条件相对好理解一些,如图,当输出电压升高,电路增益减小,结果就是输出电压回落;当输出电压减小,电路增益增加,结果是电压回升。总结果就是输出电压稳定在V0。 

起振条件

初始时刻有∣AF∣>1,电路受到外界干扰,外界的干扰通常是频带很宽幅度很小的噪声,但由于此时电路增益较大,输出电压不断增大,当输出电压达到 V0 时便稳定下来。

讨论完正弦波振荡器原理之后我们讨论下LC反馈网络

LC串并联谐振

这里我们研究谐振电路的目的在于找出三点振荡器 X1 + X2 + X3 = 0这个条件的来源

谐振的定义:当电路在某个频率下成纯电阻性,即电压和电流之间的相位差为0

首先是LC串联谐振,电路图如下

嵌入式分享合集80_赋值运算符_08

有X = Xc + Xl

按照谐振的定义,自然有X = 0,即 Xc + Xl = 0,电路在谐振时电阻为0。

然后是并联谐振

嵌入式分享合集80_同轴电缆_09

有 

嵌入式分享合集80_开关电源_10

所以得到

嵌入式分享合集80_嵌入式硬件_11

 当电路发生谐振时电导G = 0,即 Xc + Xl = 0,此时电路的电阻近乎无穷大。

我们看到无论是串联谐振还是并联谐振,均有 Xc + Xl = 0;我们离目标非常近了,下面我们就去看看具体的三点式震荡电路。

三点式振荡器的反馈

刚看课本上给出的电容式三点式振荡电路图时非常困惑不知道它为什么要这样接,我甚至看不出反馈回路在哪。在看了许多资料和电路图的变形之后,终于找到了一个满意的。

嵌入式分享合集80_嵌入式硬件_12

这里我没有画运算放大器的其他部分,只画了谐振回路和反馈回路。

在我看的资料上是这样描述的:信号电流被馈如到并联的LC电路,在此电路中,两个电容器构成了一个分压器网络,反馈到放大器的电压取自分压网络

是不是感觉突然明白了什么,哈哈哈。同样的,电感三点式振荡器的反馈电压来自于电感构成的分压电路。其实还差一点点,我们接着分析。

三点式振荡器谐振条件的推导

其实到这里,我们要的结论已经呼之欲出了。

我们来看一个电容三点式振荡器的电路图

嵌入式分享合集80_嵌入式硬件_13

 由前面谐振电路的推导,我们知道 Xc + Xl = 0,而三点式震荡电路的谐振条件是

嵌入式分享合集80_赋值运算符_14

这里的话其实很简单,就是 Xc = Xc3 + Xc4 两个电容串联,总的阻抗就是两个电容阻抗之和。至此,振荡平衡条件的由来我们已经搞清楚了,以后就能放心地使用它了。

等等!

有点不对,给出的电容分压反馈接法和三点式振荡器的接法不一样!

还记得刚开始讨论的正弦电路的平衡条件吗

嵌入式分享合集80_同轴电缆_15

要求系统的相位偏移为0。

而上述的电容分压电路,如果从两个电容之间取出

嵌入式分享合集80_同轴电缆_16

 可以看到谐振时反馈电压和输入电压是同相的

 

嵌入式分享合集80_同轴电缆_17

而下面这个电路用的是共射极放大器,输入和输出反向,即相位差为π

 

嵌入式分享合集80_赋值运算符_18

如果按照之前的方法接入反馈电压的话总的相位差就不为2nπ,而是π,就不满足平衡条件,所以把地接到两个电容之间,使得原来的反馈电压变为地,从原来的地引出反馈电压,这样电路总的移相就是0。

以上就是我学习三点式震荡电路的心得,如果有错误或者描述不恰当之处,欢迎提出意见和建议。

二、开关电源纹波、噪声的产生原因及测量方法

本文简单地介绍开关电源产生纹波和噪声的原因和测量方法、测量装置、测量标准及减小纹波和噪声的措施。

纹波和噪声产生的原因

    开关电源输出的不是纯正的直流电压,里面有些交流成分,这就是纹波和噪声造成的。纹波是输出直流电压的波动,与开关电源的开关动作有关。每一个开、关过程,电能从输入端被“泵到”输出端,形成一个充电和放电的过程,从而造成输出电压的波动,波动频率与开关的频率相同。纹波电压是纹波的波峰与波谷之间的峰峰值,其大小与开关电源的输入电容和输出电容的容量及品质有关。

    噪声的产生原因有两种,一种是开关电源自身产生的;另一种是外界电磁场的干扰(EMI),它能通过辐射进入开关电源或者通过电源线输入开关电源。

    开关电源自身产生的噪声是一种高频的脉冲串,由发生在开关导通与截止瞬间产生的尖脉冲所造成,也称为开关噪声。噪声脉冲串的频率比开关频率高得多,噪声电压是其峰峰值。噪声电压的振幅很大程度上与开关电源的拓扑、电路中的寄生状态及PCB的设计有关。

    利用示波器可以看到纹波和噪声的波形,如图1所示。纹波的频率与开关管频率相同,而噪声的频率是开关管的两倍。纹波电压的峰峰值和噪声电压的峰峰值之和就是纹波和噪声电压,其单位是mVp-p。

嵌入式分享合集80_赋值运算符_19

纹波和噪声的测量方法

    纹波和噪声电压是开关电源的主要性能参数之一,因此如何精准测量是一个十分重要问题。目前测量纹波和噪声电压是利用宽频带示波器来测量的方法,它能精准地测出纹波和噪声电压值。

    由于开关电源的品种繁多(有不同的拓扑、工作频率、输出功率、不同的技术要求等),但是各生产厂家都采用示波器测量法,仅测量装置上不完全相同,因此各厂对不同开关电源的测量都有自己的标准,即企业标准。

    用示波器测量纹波和噪声的装置的框图如图2所示。它由被测开关电源、负载、示波器及测量连线组成。有的测量装置中还焊上电感或电容、电阻等元件。

嵌入式分享合集80_嵌入式硬件_20

上图2 示波器测量框图。

    从图2来看,似乎与其他测波形电路没有什么区别,但实际上要求不同。测纹波和噪声电压的要求如下:

  ● 要防止环境的电磁场干扰(EMI)侵入,使输出的噪声电压不受EMI的影响;

  ● 要防止负载电路中可能产生的EMI干扰;

  ● 对小型开关型模块电源,由于内部无输出电容或输出电容较小,所以在测量时要加上适当的输出电容。

    为满足第1条要求,测量连线应尽量短,并采用双绞线(消除共模噪声干扰)或同轴电缆;一般的示波器探头不能用,需用专用示波器探头;并且测量点应在电源输出端上,若测量点在负载上则会造成极大的测量误差。为满足第2点,负载应采用阻性假负载。

    经常有这样的情况发生,用户买回的开关电源或模块电源,在测量纹波和噪声这一性能指标时,发现与产品技术规格上的指标不符,大大地超过技术规格上的性能指标要求,这往往是用户的测量装置不合适,测量的方法(测量点的选择)不合适或采用通用的测量探头所致。

几种测量装置

1 双绞线测量装置

    双绞线测量装置如图3所示。采用300mm(12英寸)长、#16AWG线规组成的双绞线与被测开关电源的+OUT及-OUT连接,在+OUT与-OUT之间接上阻性假负载。在双绞线末端接一个4TμF电解电容(钽电容)后输入带宽为50MHz(有的企业标准为20MHz)的示波器。在测量点连接时,一端要接在+OUT上,另一端接到地平面端。

 

嵌入式分享合集80_开关电源_21

上图3 双绞线测量装置。

    这里要注意的是,双绞线接地线的末端要尽量的短,夹在探头的地线环上。

2 平行线测量装置

    平行线测量装置如图4所示。图4中,C1是多层陶瓷电容(MLCC),容量为1μF,C2是钽电解电容,容量是10μF。两条平行铜箔带的电压降之和小于输出电压值的2%。该测量方法的优点是与实际工作环境比较接近,缺点是较容易捡拾EMI干扰。

 

嵌入式分享合集80_赋值运算符_22

  上图4 平行线测量装置。

3 专用示波器探头

    图5所示为一种专用示波器探头直接与波测电源靠接。专用示波器探头上有个地线环,其探头的尖端接触电源输出正极,地线环接触电源的负极(GND),接触要可靠。

 

嵌入式分享合集80_赋值运算符_23

上图5 示波器探头的接法。

    这里顺便提出,不能采用示波器的通用探头,因为通用示波器探头的地线不屏蔽且较长,容易捡拾外界电磁场的干扰,造成较大的噪声输出,虚线面积越大,受干扰的影响越大,如图6所示。

 

嵌入式分享合集80_开关电源_24

    上图6 通用探头易造成干扰。

4 同轴电缆测量装置

    这里介绍两种同轴电缆测量装置。图7是在被测电源的输出端接R、C电路后经输入同轴电缆(50Ω)后接示波器的AC输入端;图8是同轴电缆直接接电源输出端,在同轴电缆的两端串接1个0.68μF陶瓷电容及1个47Ω/1w碳膜电阻后接入示波器。T形BNC连接器和电容电阻的连接如图9所示。

 

嵌入式分享合集80_开关电源_25

   上图7 同轴电缆测量装置1。

嵌入式分享合集80_嵌入式硬件_26

  上图8 同轴电缆测量装置2。

嵌入式分享合集80_赋值运算符_27

 上图9 T形BNC连接器和电容电阻的连接。

纹波和噪声的测量标准

    以上介绍了多种测量装置,同一个被测电源若采用不同的测量装置,其测量的结果是不相同的,若能采用一样的标准测量装置来测,则测量的结果才有可比性。近年来出台了几个测量纹波和噪声的标准,本文将介绍一种基于JEITA-RC9131A测量标准的测量装置,如图10所示。

嵌入式分享合集80_嵌入式硬件_28

上图10 基于JEITA-RC9131A测量标准的测量装置。

    该标准规定在被测电源输出正、负端小于150mm处并联两个电容C2及C3,C2为22μF电解电容,C3为0.47μF薄膜电容。在这两个电容的连接端接负载及不超过1.5m长的50Ω同轴电缆,同轴电缆的另一端连接一个50Ω的电阻R和串接一个4700pF的电容C1后接入示波器,示波器的带宽为100MHz。同轴电缆的两端连接线应尽可能地短,以防止捡拾辐射的噪声。另外,连接负载的线若越长,则测出的纹波和噪声电压越大,在这情况下有必要连接C2及C3。若示波器探头的地线太长,则纹波和噪声的测量不可能精确。

    另外,测试应在温室条件下,被测电源应输入正常的电压,输出额定电压及额定负载电流。

不正确与正确测量的比较

1探头的选择

嵌入式分享合集80_开关电源_29

上图11 AAT1121电路测量波形。 

 

嵌入式分享合集80_开关电源_30

 上图12 用普通示波器探头测得的波形。 

 

嵌入式分享合集80_同轴电缆_31

上图13 用专用测量探头测得的波。

2 探头与测试点的接触是否良好

    以1W DC/DC电源模块IF0505RN-1W为例,采用专用探头靠测法,排除外界EMI噪声干扰,探头接触良好时,测出的纹波和噪声电压为4.8mVp-p,如图14所示。若触头接触不良时,则测出的纹波和噪声电压为8.4mVp-p,如图15所示。

嵌入式分享合集80_开关电源_32

   上图14 电源模块IF0505RN-1W测试波形(接触良好)。

 

嵌入式分享合集80_赋值运算符_33

    上图15 电源模块IF0505RN-1W测试波形(接触不良)。

    这里顺便再用普通示波器探头测试一下,其测试结果是纹波和噪声电压为48mVp-p,如图16所示。 

 

嵌入式分享合集80_赋值运算符_34

  上图16 电源模块IF0505RN-1W测试波形(普通探头)。

减小纹波和噪声电压的措施

嵌入式分享合集80_嵌入式硬件_35

     上图17 开关电源整流波形

嵌入式分享合集80_开关电源_36

   上图18 开关电源PFC电路。

    开关电源或模块的输出纹波和噪声电压的大小与其电源的拓扑,各部分电路的设计及PCB设计有关。例如,采用多相输出结构,可有效地降低纹波输出。现在的开关电源的开关频率越来越高;低的是几十kHz,一般是几百kHz,而高的可达1MHz以上。因此产生的纹波电压及噪声电压的频率都很高,要减小纹波和噪声最简单的办法是在电源电路中加无源低通滤波器。

1减少EMI的措施

    可以采用金属外壳做屏蔽减小外界电磁场辐射干扰。为减少从电源线输入的电磁干扰,在电源输入端加EMI滤波器,如图19所示(EMI滤波器也称为电源滤波器)。  

嵌入式分享合集80_赋值运算符_37

   上图19 开关电源加EMI滤波。

2 在输出端采用高频性能好、ESR低的电容

    采用高分子聚合物固态电解质的铝或钽电解电容作输出电容是最佳的,其特点是尺寸小而电容量大,高频下ESR阻抗低,允许纹波电流大。它最适用于高效率、低电压、大电流降压式DC/DC转换器及DC/DC模块电源作输出电容。例如,一种高分子聚合物钽固态电解电容为68μF,其在20℃、100kHz时的等效串联电阻(ESR)最大值为25mΩ,最大的允许纹波电流(在100kHz时)为2400mArms,其尺寸为:7.3mm(长)×4.3mm(宽)×1.8mm(高),其型号为10TPE68M(贴片或封装)。

    纹波电压ΔVOUT为:

    ΔVOUT=ΔIOUT×ESR (1)

    若ΔIOUT=0.5A,ESR=25mΩ,则ΔVOUT=12.5mV。

    若采用普通的铝电解电容作输出电容,额定电压10V、额定电容量100μF,在20℃、120Hz时的等效串联电阻为5.0Ω,最大纹波电流为70mA。它只能工作于10kHz左右,无法在高频(100kHz以上的频率)下工作,再增加电容量也无效,因为超过10kHz时,它已成电感特性了。

    某些开关频率在100kHz到几百kHz之间的电源,采用多层陶电容(MLCC)或钽电解电容作输出电容的效果也不错,其价位要比高分子聚合物固态电解质电容要低得多。

3 采用与产品系统的频率同步

    为减小输出噪声,电源的开关频率应与系统中的频率同步,即开关电源采用外同步输入系统的频率,使开关的频率与系统的频率相同。

4 避免多个模块电源之间相互干扰

    在同一块PCB上可能有多个模块电源一起工作。若模块电源是不屏蔽的、并且靠的很近,则可能相互干扰使输出噪声电压增加。为避免这种相互干扰可采用屏蔽措施或将其适当远离,减少其相互影响的干扰。

    例如,用两个K7805-500开关型模块组成±5V输出电源时,若两个模块靠的很近,输出电容C4、C2未采用低ESR电容,且焊接处离输出端较远,则有可能输出的纹波和噪声电压受到相互干扰而增加,如图20所示。

    如果在同一块PCB上有能产生噪声干扰的电路,则在设计PCB时要采取相似的措施以减少干扰电路对开关电源的相互干扰影响。

 

嵌入式分享合集80_同轴电缆_38

   上图20 K7805-500并联。

5 增加LC滤波器

    为减小模块电源的纹波和噪声,可以在DC/DC模块的输入和输出端加LC滤波器,如图21所示。图21左图是单输出,图21右图是双输出。

 

嵌入式分享合集80_同轴电缆_39

  上图21 在DC/DC模块中加入LC滤波器。

    在表1及表2中列出1W DC/DC模块的VIN端和VOUT端在不同输出电压时的电容值。要注意的是,电容量不能过大而造起动问题,LC的谐振频率必须与开关频率要错开以避免相互干扰,L采用μH极的,其直流电阻要低,以免影响输出电压精度。

 

嵌入式分享合集80_开关电源_40

6 增加LDO

    在开关电源或模块电源输出后再加一个低压差线性稳压器(LDO)能大幅度地降低输出噪声,以满足对噪声特别有要求的电路需要(见图22),输出噪声可达μV级。

 

嵌入式分享合集80_嵌入式硬件_41

  上图22 在电源中加入LDO。

    由于LDO的压差(输入与输出电压的差值)仅几百mV,则在开关电源的输出略高于LDO几百mV就可以输出标准电压了,并且其损耗也不大。

7 增加有源EMI滤波器及有源输出纹波衰减器

    有源EMI滤波器可在150kHz~30MHz间衰减共模和差模噪声,并且对衰减低频噪声特别有效。在250kHz时,可衰减60dB共模噪声及80dB差模噪声,在满载时效率可达99%。

    输出纹波衰减器可在1~500kHz范围内减低电源输出纹波和噪声30dB以上,并且能改善动态响应及减小输出电容。       

 

三、嵌入式开发中的C语言特性

我怎么感觉这个我发过呢

  本文面向的,正是使用单片机、ARM7、Cortex-M3这类微控制器的编程人员。

    C语言诡异且有种种陷阱和缺陷,需要程序员多年历练才能达到较为完善的地步。

    总是有大批的初学者,前仆后继的倒在这些陷阱和缺陷上,民用设备、工业设备甚至是航天设备都不例外。本文将结合具体例子再次审视它们,希望引起足够重视。深入理解C语言特性,是编写优质嵌入式C程序的基础。

    由于篇幅限制,后续再推送编译器、防御性编程、测试和编程思想这几个方面的内容,来讨论如何编写优质嵌入式C程序。

1 处处都是陷阱

1.1 无心之过

1) “=”和”==”

    将比较运算符”==”误写成赋值运算符”=”,可能是绝大多数人都遇到过的,比如下面代码:

if(x=5)
{
//其它代码   
}
  • 1.
  • 2.
  • 3.
  • 4.

    代码的本意是比较变量x是否等于常量5,但是误将”==”写成了”=”,if语句恒为真。如果在逻辑判断表达式中出现赋值运算符,现在的大多数编译器会给出警告信息。比如keil MDK会给出警告提示:“warning: #187-D: use of "=" where"==" may have been intended”,但并非所有程序员都会注意到这类警告,因此有经验的程序员使用下面的代码来避免此类错误:

if(5==x)
 {
     //其它代码   
 }
  • 1.
  • 2.
  • 3.
  • 4.

    将常量放在变量x的左边,即使程序员误将’==’写成了’=’,编译器会产生一个任谁也不能无视的语法错误信息:不可给常量赋值!

2) 复合赋值运算符

    复合赋值运算符(+=、*=等等)虽然可以使表达式更加简洁并有可能产生更高效的机器代码,但某些复合赋值运算符也会给程序带来隐含Bug,比如”+=”容易误写成”=+”,代码如下:

tmp=+1;
  • 1.

    代码本意是想表达tmp=tmp+1,但是将复合赋值运算符”+=”误写成”=+”:将正整数常量1赋值给变量tmp。编译器会欣然接受这类代码,连警告都不会产生。

    如果你能在调试阶段就发现这个Bug,真应该庆祝一下,否则这很可能会成为一个重大隐含Bug,且不易被察觉。

    复合赋值运算符”-=”也有类似问题存在。

3) 其它容易误写

  • 使用了中文标点
  • 头文件声明语句最后忘记结束分号
  • 逻辑与&&和位与&、逻辑或||和位或|、逻辑非!和位取反~
  • 字母l和数字1、字母O和数字0

    这些误写其实容易被编译器检测出,只需要关注编译器对此的提示信息,就能很快解决。

1.2 数组下标

    数组常常也是引起程序不稳定的重要因素,C语言数组的迷惑性与数组下标从0开始密不可分,你可以定义int test[30],但是你绝不可以使用数组元素test [30],除非你自己明确知道在做什么。

1.3 容易被忽略的break关键字

1) 不能漏加的break

    switch…case语句可以很方便的实现多分支结构,但要注意在合适的位置添加break关键字。程序员往往容易漏加break从而引起顺序执行多个case语句,这也许是C的一个缺陷之处。

    对于switch…case语句,从概率论上说,绝大多数程序一次只需执行一个匹配的case语句,而每一个这样的case语句后都必须跟一个break。去复杂化大概率事件,这多少有些不合常情。

2) 不能乱加的break

    break关键字用于跳出最近的那层循环语句或者switch语句,但程序员往往不够重视这一点。

    1990年1月15日,AT&T电话网络位于纽约的一台交换机宕机并且重启,引起它邻近交换机瘫痪,由此及彼,一个连着一个,很快,114型交换机每六秒宕机重启一次,六万人九小时内不能打长途电话。

    当时的解决方式:工程师重装了以前的软件版本。。。事后的事故调查发现,这是break关键字误用造成的。《C专家编程》提供了一个简化版的问题源码:

嵌入式分享合集80_同轴电缆_42

  那个程序员希望从if语句跳出,但他却忘记了break关键字实际上跳出最近的那层循环语句或者switch语句。现在它跳出了switch语句,执行了use_modes_pointer()函数。但必要的初始化工作并未完成,为将来程序的失败埋下了伏笔。

1.4 意想不到的八进制

    将一个整形常量赋值给变量,代码如下所示:

int a=34, b=034;
  • 1.

    变量a和b相等吗?

    答案是不相等的。我们知道,16进制常量以’0x’为前缀,10进制常量不需要前缀,那么8进制呢?它与10进制和16进制表示方法都不相同,它以数字’0’为前缀,这多少有点奇葩:三种进制的表示方法完全不相同。

    如果8进制也像16进制那样以数字和字母表示前缀的话,或许更有利于减少软件Bug,毕竟你使用8进制的次数可能都不会有误使用的次数多!下面展示一个误用8进制的例子,最后一个数组元素赋值错误:

a[0]=106;       /*十进制数106*/
 a[1]=112;      /*十进制数112*/
 a[2]=052;       /*实际为十进制数42,本意为十进制52*/
  • 1.
  • 2.
  • 3.
1.5指针加减运算

    **指针的加减运算是特殊的。**下面的代码运行在32位ARM架构上,执行之后,a和p的值分别是多少?

int a=1;
int *p=(int *)0x00001000;
a=a+1;
p=p+1;
  • 1.
  • 2.
  • 3.
  • 4.

    对于a的值很容判断出结果为2,但是p的结果却是0x00001004。指针p加1后,p的值增加了4,这是为什么呢?原因是指针做加减运算时是以指针的数据类型为单位。p+1实际上是按照公式p+1*sizeof(int)来计算的。不理解这一点,在使用指针直接操作数据时极易犯错。

    某项目使用下面代码对连续RAM初始化零操作,但运行发现有些RAM并没有被真正清零。

 

嵌入式分享合集80_赋值运算符_43

 通过分析我们发现,由于pRAMaddr是一个无符号int型指针变量,所以pRAMaddr+=4代码其实使pRAMaddr偏移了4*sizeof(int)=16个字节,所以每执行一次for循环,会使变量pRAMaddr偏移16个字节空间,但只有4字节空间被初始化为零。其它的12字节数据的内容,在大多数架构处理器中都会是随机数。

1.6关键字sizeof

    不知道有多少人最初认为sizeof是一个函数。其实它是一个关键字,其作用是返回一个对象或者类型所占的内存字节数,对绝大多数编译器而言,返回值为无符号整形数据。需要注意的是,使用sizeof获取数组长度时,不要对指针应用sizeof操作符,比如下面的例子:

void ClearRAM(char array[])
{
int i ;
     for(i=0;i<sizeof(array)/sizeof(array[0]);i++)     //这里用法错误,array实际上是指针  
     {
array[i]=0x00;
     }
 }
int main(void)
{
char Fle[20];
     ClearRAM(Fle);          //只能清除数组Fle中的前四个元素  
 }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

    我们知道,对于一个数组array[20],我们使用代码sizeof(array)/sizeof(array[0])可以获得数组的元素(这里为20),但数组名和指针往往是容易混淆的,有且只有一种情况下数组名是可以当做指针的,那就是**数组名作为函数形参时,数组名被认为是指针,同时,它不能再兼任数组名。

    **注意只有这种情况下,数组名才可以当做指针,但不幸的是这种情况下容易引发风险。在ClearRAM函数内,作为形参的array[]不再是数组名了,而成了指针。sizeof(array)相当于求指针变量占用的字节数,在32位系统下,该值为4,sizeof(array)/sizeof(array[0])的运算结果也为4。所以在main函数中调用ClearRAM(Fle),也只能清除数组Fle中的前四个元素了。

1.7增量运算符’++’和减量运算符‘--‘

    增量运算符”++”和减量运算符”--“既可以做前缀也可以做后缀。**前缀和后缀的区别在于值的增加或减少这一动作发生的时间是不同的。**作为前缀是先自加或自减然后做别的运算,作为后缀时,是先做运算,之后再自加或自减。许多程序员对此认识不够,就容易埋下隐患。下面的例子可以很好的解释前缀和后缀的区别。

int a=8,b=2,y;
y=a+++--b;
  • 1.
  • 2.

    代码执行后,y的值是多少?

    这个例子并非是挖空心思设计出来专门让你绞尽脑汁的C难题(如果你觉得自己对C细节掌握很有信心,做一些C难题检验一下是个不错的选择。那么,《The C Puzzle Book》这本书一定不要错过),你甚至可以将这个难懂的语句作为不友好代码的例子。但是它也可以让你更好的理解C语言。根据运算符优先级以及编译器识别字符的贪心法原则,第二句代码可以写成更明确的形式:

y=(a++)+(--b);
  • 1.

    当赋值给变量y时,a的值为8,b的值为1,所以变量y的值为9;赋值完成后,变量a自加,a的值变为9,千万不要以为y的值为10。这条赋值语句相当于下面的两条语句:

y=a+(--b);
a=a+1;
  • 1.
  • 2.
1.8逻辑与’&&’和逻辑或’||’的陷阱

    为了提高系统效率,逻辑与和逻辑或操作的规定如下:**如果对第一个操作数求值后就可以推断出最终结果,第二个操作数就不会进行求值!**比如下面代码:

if((i>=0)&&(i++ <=max))
 {
//其它代码  
 }
  • 1.
  • 2.
  • 3.
  • 4.

    在这个代码中,只有当i>=0时,i++才会被执行。这样,i是否自增是不够明确的,这可能会埋下隐患。逻辑或与之类似。

1.9结构体的填充

    结构体可能产生填充,因为对大多数处理器而言,访问按字或者半字对齐的数据速度更快,当定义结构体时,编译器为了性能优化,可能会将它们按照半字或字对齐,这样会带来填充问题。比如以下两个个结构体:

    第一个结构体:

struct {
char  c;
short s;
int   x;
 }str_test1;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

    第二个结构体: 

struct {
char  c;
int   x;
short s;
 }str_test2;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

    这两个结构体元素都是相同的变量,只是元素换了下位置,那么这两个结构体变量占用的内存大小相同吗?

    其实这两个结构体变量占用的内存是不同的,对于Keil MDK编译器,默认情况下第一个结构体变量占用8个字节,第二个结构体占用12个字节,差别很大。第一个结构体变量在内存中的存储格式如下图所示:

嵌入式分享合集80_开关电源_44

 

    第二个结构体变量在内存中的存储格式如下图所示。对比两个图可以看出MDK编译器是是怎么将数据对齐的,这其中的填充内容是之前内存中的数据,是随机的,所以不能在结构之间逐字节比较;另外,合理的排布结构体内的元素位置,可以最大限度减少填充,节省RAM。

 

嵌入式分享合集80_嵌入式硬件_45

2 不可轻视的优先级 

    C语言有32个关键字,却有34个运算符。要记住所有运算符的优先级是困难的。稍不注意,你的代码逻辑和实际执行就会有很大出入。

    比如下面将BCD码转换为十六进制数的代码:

result=(uTimeValue>>4)*10+uTimeValue&0x0F;
  • 1.

    这里uTimeValue存放的BCD码,想要转换成16进制数据,实际运行发现,如果uTimeValue的值为0x23,按照我设定的逻辑,result的值应该是0x17,但运算结果却是0x07。经过种种排查后,才发现’+’的优先级是大于’&’的,相当于(uTimeValue>>4)*10+uTimeValue与0x0F位与,结果自然与逻辑不符。符合逻辑的代码应该是:

result=(uTimeValue>>4)*10+(uTimeValue&0x0F);
  • 1.

    不合理的#define会加重优先级问题,让问题变得更加隐蔽。

嵌入式分享合集80_赋值运算符_46

编译器在编译后将宏带入,原代码语句变为:

if(IO0PIN&(1<<11) ==(1<<11))
 {
//其它代码   
 }
  • 1.
  • 2.
  • 3.
  • 4.

    运算符'=='的优先级是大于'&'的,代码IO0PIN&(1<<11) ==(1<<11))等效为IO0PIN&0x00000001:判断端口P0.0是否为高电平,这与原意相差甚远。因此,使用宏定义的时候,最好将被定义的内容用括号括起来。

    按照常规方式使用时,可能引起误会的运算符还有很多,如下表所示。C语言的运算符当然不会只止步于数目繁多!

嵌入式分享合集80_同轴电缆_47

 有一个简便方法可以避免优先级问题:不清楚的优先级就加上”()”,但这样至少有会带来两个问题:

  • 过多的括号影响代码的可读性,包括自己和以后的维护人员
  • 别人的代码不一定用括号来解决优先级问题,但你总要读别人的代码

    无论如何,在嵌入式编程方面,该掌握的基础知识,偷巧不得。建议花一些时间,将优先级顺序以及容易出错的优先级运算符理清几遍。

隐式转换

    C语言的设计理念一直被人吐槽,因为它认为C程序员完全清楚自己在做什么,其中一个证据就是隐式转换。C语言规定,**不同类型的数据(比如char和int型数据)需要转换成同一类型后,才可进行计算。

    **如果你混合使用类型,比如用char类型数据和int类型数据做减法,C使用一个规则集合来自动(隐式的)完成类型转换。这可能很方便,但也很危险。

    这就要求我们理解这个转换规则并且能应用到程序中去!

  1. 当出现在表达式里时,有符号和无符号的char和short类型都将自动被转换为int类型,在需要的情况下,将自动被转换为unsigned int(在short和int具有相同大小时)。这称为类型提升。

    提升在算数运算中通常不会有什么大的坏处,但如果位运算符 ~ 和 << 应用在基本类型为unsigned char或unsigned short 的操作数,结果应该立即强制转换为unsigned char或者unsigned short类型(取决于操作时使用的类型)。

uint8_t  port =0x5aU;
uint8_t  result_8;
result_8= (~port) >> 4;
  • 1.
  • 2.
  • 3.

    假如我们不了解表达式里的类型提升,认为在运算过程中变量port一直是unsigned char类型的。我们来看一下运算过程:~port结果为0xa5,0xa5>>4结果为0x0a,这是我们期望的值。

    但实际上,result_8的结果却是0xfa!在ARM结构下,int类型为32位。变量port在运算前被提升为int类型:~port结果为0xffffffa5,0xa5>>4结果为0x0ffffffa,赋值给变量result_8,发生类型截断(这也是隐式的!),result_8=0xfa。经过这么诡异的隐式转换,结果跟我们期望的值,已经大相径庭!正确的表达式语句应该为:

result_8=(unsigned char) (~port) >> 4;             /*强制转换*/
  • 1.
  1. 在包含两种数据类型的任何运算里,两个值都会被转换成两种类型里较高的级别。类型级别从高到低的顺序是long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。

    这种类型提升通常都是件好事,但往往有很多程序员不能真正理解这句话,比如下面的例子(int类型表示16位)。

uint16_t  u16a = 40000;             /* 16位无符号变量*/
uint16_t  u16b= 30000;           /*16位无符号变量*/
uint32_t  u32x;                   /*32位无符号变量 */
uint32_t  u32y;
 u32x = u16a +u16b;                 /* u32x = 70000还是4464 ? */
 u32y =(uint32_t)(u16a + u16b);    /* u32y = 70000 还是4464 ? */
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

    u32x和u32y的结果都是4464(70000%65536)!不要认为表达式中有一个高类别uint32_t类型变量,编译器都会帮你把所有其他低类别都提升到uint32_t类型。正确的书写方式:

u32x = (uint32_t)u16a +(uint32_t)u16b;      
//或者:
 u32x = (uint32_t)u16a + u16b;
  • 1.
  • 2.
  • 3.

    后一种写法在本表达式中是正确的,但是在其它表达式中不一定正确,比如:

uint16_t u16a,u16b,u16c;
uint32_t  u32x;
u32x= u16a + u16b + (uint32_t)u16c;/*错误写法,u16a+ u16b仍可能溢出 */
  • 1.
  • 2.
  • 3.
  1. 在赋值语句里,计算的最后结果被转换成将要被赋予值的那个变量的类型。这一过程可能导致类型提升也可能导致类型降级。降级可能会导致问题。比如将运算结果为321的值赋值给8位char类型变量。程序必须对运算时的数据溢出做合理的处理。很多其他语言,像Pascal(C语言设计者之一曾撰文狠狠批评过Pascal语言),都不允许混合使用类型,但C语言不会限制你的自由,即便这经常引起Bug。
  2. 当作为函数的参数被传递时,char和short会被转换为int,float会被转换为double。

    当不得已混合使用类型时,一个比较好的习惯是使用类型强制转换。强制类型转换可以避免编译器隐式转换带来的错误,同时也向以后的维护人员传递一些有用信息。这有个前提:你要对强制类型转换有足够的了解!下面总结一些规则:

  • 并非所有强制类型转换都是由风险的,把一个整数值转换为一种具有相同符号的更宽类型时,是绝对安全的。
  • 精度高的类型强制转换为精度低的类型时,通过丢弃适当数量的最高有效位来获取结果,也就是说会发生数据截断,并且可能改变数据的符号位。
  • 精度低的类型强制转换为精度高的类型时,如果两种类型具有相同的符号,那么没什么问题;需要注意的是负的有符号精度低类型强制转换为无符号精度高类型时,会不直观的执行符号扩展,例如:
unsigned int bob;
signed char fred = -1;
bob=(unsigned int )fred;    /*发生符号扩展,此时bob为0xFFFFFFFF*/
  • 1.
  • 2.
  • 3.
四、交换机的基本配置与管理

实验目标

掌握交换机基本信息的配置管理。

技术原理

 交换机的管理方式基本分为两种:带内管理和带外管理。

 通过交换机的Console端口管理交换机属于带外管理;这种管理方式不占用交换机的网络端口,第一次配置交换机必须利用Console端口进行配置。

 通过Telnet、拨号等方式属于带内管理。

 交换机的命令行操作模式主要包括:

用户模式  Switch>

特权模式  Switch#

全局配置模式 Switch(config)#

端口模式  Switch(config-if)#

实验步骤:

 新建Packet Tracer拓扑图

了解交换机命令行

进入特权模式(en)

进入全局配置模式(conf t)

进入交换机端口视图模式(int f0/1)

返回到上级模式(exit)

从全局以下模式返回到特权模式(end)

帮助信息(如? 、co?、copy?)

命令简写(如 conf t)

命令自动补全(Tab)

快捷键(ctrl+c中断测试,ctrl+z退回到特权视图)

Reload重启。(在特权模式下)

修改交换机名称(hostname X)

实验设备

Switch_2960 1台;PC 1台;配置线;图1-1

嵌入式分享合集80_同轴电缆_48

 点击PC中打开 console端口敲击如下代码

Switch>en                      (代码缩写,可用tab键补全代码enable)

Switch#conf t                  (代码缩写,全称为config t)

Switch(config)#hostname X          (修改交换机名称为X,可缩写成host x)

Switch(config)#interface fa 0/1      (进入交换机端口fa 0/1视图模式,可缩写成int f0/1)

Switch(config-if)#end                     (从全局以下模式返回到特权模式,快捷键Ctrl+z)