一、不同的电平信号的MCU怎么通信

“电平转换”电路

 先说一说这个电路的用途:当两个MCU在不同的工作电压下工作(如MCU1 工作电压5V;MCU2 工作电压3.3V),那么MCU1 与MCU2之间怎样进行串口通信呢?相关文章:STM32与51单片机串口通信实例。很明显是不能将对应的TX、RX引脚直接相连的,否测可能造成较低工作电压的MCU烧毁!

    下面的“电平双向转换电路”就可以实现不同VDD(芯片工作电压)的MCU之间进行串口通信。

51c嵌入式~合集2_引脚

    该电路的核心在于电路中的MOS场效应管(2N7002)。他和三极管的功能很相似,可做开关使用,即可控制电路的通和断。不过比起三极管,MOS管有挺多优势,后面将会详细讲起。下图是MOS管实物3D图和电路图。简单的讲,要让他当做开关,只要让Vgs(导通电压)达到一定值,引脚D、S就会导通,Vgs没有达到这个值就截止。

51c嵌入式~合集2_三极管_02

    那么如何将2N7002应用到上面电路中呢,又起着什么作用呢?下面我们来分析一下。

51c嵌入式~合集2_三极管_03

    如果沿着a、b两条线,将电路切断。那么MCU1的TX引脚被上拉为5V,MCU2的RX引脚也被上拉为3.3V。2N7002的S、D引脚(对应图中的2、3引脚)截止就相当于a、b两条线,将电路切断。也就是说,此电路在2N7002截止的时候是可以做到,给两个MCU引脚输送对应的工作电压。 

    下面进一步分析:

    数据传输方向MCU1-->MCU2。

51c嵌入式~合集2_数据传输_04

1. MCU1 TX发送高电平(5V),MCU2 RX配置为串口接收引脚,此时2N7002的S、D引脚(对应图中的2、3引脚)截止,2N7002里面的二极管3-->2方向不通。那么MCU2 RX被VCC2上拉为3.3V。

2. MCU1 TX发送低电平(0V),此时2N7002的S、D引脚依然截止,但是2N7002里面的二极管2-->3方向通,即VCC2、R2、2N7002里的二极管、MCU1 TX组成一个回路。2N7002的2引脚被拉低,此时MCU2 RX为0V。该电路从MCU1到MCU2方向,数据传输,达到了电平转换的效果。

接下来分析

数据传输方向MCU2-->MCU1

51c嵌入式~合集2_数据传输_05

1. MCU2 TX发送高电平(3.3V),此时Vgs(图中1、2引脚电压差)电压差约等于0,2N7002截止,2N7002里面的二极管3-->2方向不通,此时MCU1 RX引脚被VCC1上拉为5V。

2. MCU2 TX发送低电平(0V),此时Vgs(图中1、2引脚电压差)电压差约等于3.3V,2N7002导通,2N7002里面的二极管3-->2方向不通,VCC1、R1、2N7002里的二极管、MCU2 TX组成一个回路。2N7002的3引脚被拉低,此时MCU1 RX为0V。

    该电路从MCU2到MCU1方向,数据传输,达到了电平转换的效果。

    到此,该电路就分析完了,这是一个双向的串口电平转换电路。

MOS的优势:

1、场效应管的源极S、栅极G、漏极D分别对应于三极管的发射极e、基极b、集电极c,它们的作用相似,图一所示是N沟道MOS管和NPN型晶体三极管引脚,图二所示是P沟道MOS管和PNP型晶体三极管引脚对应图。相关文章:MOS管基本认识。

51c嵌入式~合集2_引脚_06

2、场效应管是电压控制电流器件,由VGS控制ID,普通的晶体三极管是电流控制电流器件,由IB控制IC。MOS管道放大系数是(跨导gm)当栅极电压改变一伏时能引起漏极电流变化多少安培。晶体三极管是电流放大系数(贝塔β)当基极电流改变一毫安时能引起集电极电流变化多少。

3、场效应管栅极和其它电极是绝缘的,不产生电流;而三极管工作时基极电流IB决定集电极电流IC。因此场效应管的输入电阻比三极管的输入电阻高的多。

4、场效应管只有多数载流子参与导电;三极管有多数载流子和少数载流子两种载流子参与导电,因少数载流子浓度受温度、辐射等因素影响较大,所以场效应管比三极管的温度稳定性好。

5、场效应管在源极未与衬底连在一起时,源极和漏极可以互换使用,且特性变化不大,而三极管的集电极与发射极互换使用时,其特性差异很大,b 值将减小很多。

6、场效应管的噪声系数很小,在低噪声放大电路的输入级及要求信噪比较高的电路中要选用场效应管。

7、场效应管和普通晶体三极管均可组成各种放大电路和开关电路,但是场效应管制造工艺简单,并且又具有普通晶体三极管不能比拟的优秀特性,在各种电路及应用中正逐步的取代普通晶体三极管,目前的大规模和超大规模集成电路中,已经广泛的采用场效应管。

8、输入阻抗高,驱动功率小:由于栅源之间是二氧化硅(SiO2)绝缘层,栅源之间的直流电阻基本上就是SiO2绝缘电阻,一般达100MΩ左右,交流输入阻抗基本上就是输入电容的容抗。由于输入阻抗高,对激励信号不会产生压降,有电压就可以驱动,所以驱动功率极小(灵敏度高)。一般的晶体三极管必需有基极电压Vb,再产生基极电流Ib,才能驱动集电极电流的产生。晶体三极管的驱动是需要功率的(Vb×Ib)。

9、开关速度快:MOSFET的开关速度和输入的容性特性的有很大关系,由于输入容性特性的存在,使开关的速度变慢,但是在作为开关运用时,可降低驱动电路内阻,加快开关速度(输入采用了后述的“灌流电路”驱动,加快了容性的充放电的时间)。MOSFET只靠多子导电,不存在少子储存效应,因而关断过程非常迅速,开关时间在10—100ns之间,工作频率可达100kHz以上,普通的晶体三极管由于少数载流子的存储效应,使开关总有滞后现象,影响开关速度的提高(目前采用MOS管的开关电源其工作频率可以轻易的做到100K/S~150K/S,这对于普通的大功率晶体三极管来说是难以想象的)。

10、无二次击穿:由于普通的功率晶体三极管具有当温度上升就会导致集电极电流上升(正的温度~电流特性)的现象,而集电极电流的上升又会导致温度进一步的上升,温度进一步的上升,更进一步的导致集电极电流的上升这一恶性循环。而晶体三极管的耐压VCEO随管温度升高是逐步下降,这就形成了管温继续上升、耐压继续下降最终导致晶体三极管的击穿,这是一种导致电视机开关电源管和行输出管损坏率占95%的破环性的热电击穿现象,也称为二次击穿现象。MOS管具有和普通晶体三极管相反的温度~电流特性,即当管温度(或环境温度)上升时,沟道电流IDS反而下降。例如;一只IDS=10A的MOS FET开关管,当VGS控制电压不变时,在250C温度下IDS=3A,当芯片温度升高为1000C时,IDS降低到2A,这种因温度上升而导致沟道电流IDS下降的负温度电流特性,使之不会产生恶性循环而热击穿。也就是MOS管没有二次击穿现象,可见采用MOS管作为开关管,其开关管的损坏率大幅度的降低,近两年电视机开关电源采用MOS管代替过去的普通晶体三极管后,开关管损坏率大大降低也是一个极好的证明。

11、MOS管导通后其导通特性呈纯阻性:普通晶体三极管在饱和导通是,几乎是直通,有一个极低的压降,称为饱和压降,既然有一个压降,那么也就是;普通晶体三极管在饱和导通后等效是一个阻值极小的电阻,但是这个等效的电阻是一个非线性的电阻(电阻上的电压和流过的电流不能符合欧姆定律),而MOS管作为开关管应用,在饱和导通后也存在一个阻值极小的电阻,但是这个电阻等效一个线性电阻,其电阻的阻值和两端的电压降和流过的电流符合欧姆定律的关系,电流大压降就大,电流小压降就小,导通后既然等效是一个线性元件,线性元件就可以并联应用,当这样两个电阻并联在一起,就有一个自动电流平衡的作用,所以MOS管在一个管子功率不够的时候,可以多管并联应用,且不必另外增加平衡措施(非线性器件是不能直接并联应用的)。



二、光耦电路使用中注意

电器应用中常用的隔离器件有光耦、继电器、变压器。

光耦属于流控型元件,以光为媒介传输信号:电→光→电,输入端是发光二极管,输出端是光敏半导体。光耦的核心应用是隔离作用,常用于输入与输出之间无共地的系统。所以输入与输出之间的耐压可达上千伏特。

很多通讯模块也是光耦隔离的,更容易实现各个系统之间的连接,完全不用考虑是否共地。

如图1为光耦控制继电器(小功率),为使光耦能有效驱动继电器,那么输出端的阻抗应较小,所以输入端的电流应较大,具体原因见下面分析。

51c嵌入式~合集2_数据传输_07

图1:光耦控制继电器

如图2为开关信号经过光耦隔离输入至单片机,图中24V与3.3V不是共地的,且在控制系统中数字电压3.3V驱动能力有限,所以通常用开关电源的24V或12V作为开关信号的电源。

51c嵌入式~合集2_三极管_08

图2:输入输出隔离

以上两种普通的应用看似简单,但要正确使用光耦,就必须掌握光耦的输入和输出到底是什么关系?

光耦分为线性光耦和非线性光耦,实际常规应用中线性光耦较多,因为线性光耦可以替代非线性光耦,现在以线性光耦(PS2561A)做以下实验,换种角度了解TA的魅力。

如图3所示,调节光耦输入电流IF,测量输出的CE阻抗。

51c嵌入式~合集2_数据传输_09

图3:输入电流IF与输出CE阻抗关系实验

51c嵌入式~合集2_数据传输_10

左边为输入电流IF,右边为输出CE阻抗

如图4所示,光耦输入与输出的限流电阻都是1k,且输入电压都相同,于是调节稳压源的电压值,可以得到光耦输入电流IF与输出电流IC的关系。

51c嵌入式~合集2_引脚_11

图4:输入电流IF与输出电流IC的关系实验

51c嵌入式~合集2_三极管_12

左边为输入电流IF,右边为输出电流IC

如图5得到的实验数据,输出电流IC与输入电流IF曲线趋势基本一致,CE阻抗小于1k左右呈线性变化。且最低阻抗大于100Ω。

51c嵌入式~合集2_数据传输_13

图5:实验数据

所以使用线性光耦传递开关信号时,需要合理匹配输入电阻的大小,图1中输入电阻360Ω,光耦输入正向压降1V左右,所输入电流IC为(5-1)/360≈11mA,光耦输出CE阻抗200Ω多点,而继电器HFD2线圈阻抗2880Ω,此时可正常驱动继电器,若IC电流变小,则CE阻抗变大后会导致不能正常驱动继电器。

线性光耦主要用于模拟信号的传递,输出相当于一个可变电阻。在开关电源中很常见,利用光藕做反馈,把高压和低压隔离。常用的有PC817、PS2561、PS2801。如前面例子也常用于开关信号。

图7为图6中开关电源内部的线性光耦,开关电源的输出电压经过线性光耦隔离并反馈到控制芯片达到实时调节输出电压的目的。

51c嵌入式~合集2_数据传输_14

图6:光耦在开关电源中的应用

51c嵌入式~合集2_三极管_15

图7:开关电源内部的光耦

非线性光耦主要用于开关信号(或数字信号)的传递,常用的4N系列的有4N25、4N26以及TIL117;另外还有高速光耦,如6N136、6N137、PS9714、PS9715等。多用于通讯隔离以及PWM波控制(可有效降低电磁干扰),判断是不是高速光耦,看数据手册是否注明 High speed(1Mbps、10Mbps)。

要点:

①光耦的核心应用是隔离作用;

②相同电压下线性光耦输入电阻与输出电阻相同时,输出电流IC基本与输入电流IF一致;即使输入与输出电压不同,也可以匹配输出与输入的电阻来实现;

③用于开关信号线性光耦和非线性光耦都可以,反过来线性光耦电路中不能用非线性光耦代替。

④非线性光耦要比线性光耦响应速度快,类似于比较器比运算放大器响应速度快一样。



三、MOS管烧了,可能是这些原因

今天给大家讲一下关于MOS管烧毁的原因,文字比较多点,不容易读,希望大家可以认真看完。

MOS 管可能会遭受与其他功率器件相同的故障,例如过电压(半导体的雪崩击穿)、过电流(键合线或者衬底熔化)、过热(半导体材料由于高温而分解)。

更具体的故障包括栅极和管芯其余部分之前的极薄氧化物击穿,这可能发生在相对于漏极或者源极的任何过量栅极电压中,可能是在低至10V-15V 时发生,电路设计必须将其限制在安全水平。

还有可能是功率过载,超过绝对最大额定值和散热不足,都会导致MOS管发生故障。

接下来就来看看所有可能导致失效的原因。

 过电压 

MOS管对过压的耐受性非常小,即使超出额定电压仅几纳秒,也可能导致设备损坏。

MOS管的额定电压应保守地考虑预期的电压水平,并应特别注意抑制任何电压尖峰或振铃。

 长时间电流过载 

由于导通电阻相对较高,高平均电流会在MOS管中引起相当大的热耗散。

如果电流非常高且散热不良,则MOS管可能会因温升过高而损坏。

MOS管可以直接并联以共享高负载电流。

 瞬态电流过载 

持续时间短、大电流过载会导致MOS管器件逐渐损坏,但是在故障发生前MOS管的温度几乎没有明显升高,不太能察觉出来。(也可以看下面分析的直通和反向恢复部分)

 击穿(交叉传导) 

如果两个相对MOS管的控制信号重叠,则可能会出现两个MOS管同时导通的情况,这会使电源短路,也就是击穿条件。

如果发生这种情况,每次发生开关转换时,电源去耦电容都会通过两个器件快速放电,这会导致通过两个开关设备的电流脉冲非常短但非常强。

通过允许开关转换之间的死区时间(在此期间两个MOS管均不导通),可以最大限度地减少发生击穿的机会,这允许一个MOS管在另一个MOS管打开之前关闭。

 没有续流电流路径 

当通过任何电感负载(例如特斯拉线圈)切换电流时,电流关闭时会产生反电动势。在两个开关设备都没有承载负载电流时,必须为此电流提供续流路径。

该电流通常通过与每个开关器件反并联连接的续流二极管安全地引导回电源轨道。

当MOS管用作开关器件时,工程师可以简单获得MOS管固有体二极管形式的续流二极管,这解决了一个问题,但创造了一个全新的问题......

 MOS管体二极管的缓慢反向恢复 

诸如特斯拉线圈之类的高 Q 谐振电路能够在其电感和自电容中存储大量能量。

在某些调谐条件下,当一个MOS管关闭而另一个器件打开时,这会导致电流“续流”通过 MOS管的内部体二极管。

这个原本不是什么问题,但当对面的MOS管试图开启时,内部体二极管的缓慢关断(或反向恢复)就会出现问题。

与MOS管 自身的性能相比,MOS管 体二极管通常具有较长的反向恢复时间。如果一个 MOS管的体二极管在对立器件开启时导通,则类似于上述击穿情况发生“短路”。

这个问题通常可以通过在每个MOS管周围添加两个二极管来缓解。

首先,肖特基二极管与MOS管源极串联,肖特基二极管可防止MOS管体二极管被续流电流正向偏置。其次,高速(快速恢复)二极管并联到MOS管/肖特基对,以便续流电流完全绕过MOS管和肖特基二极管。

这确保了MOS管体二极管永远不会被驱动导通,续流电流由快恢复二极管处理,快恢复二极管较少出现“击穿”问题。

 过度的栅极驱动 

如果用太高的电压驱动MOS管栅极,则栅极氧化物绝缘层可能会被击穿,从而导致MOS管无法使用。

超过 +/- 15 V的栅极-源极电压可能会损坏栅极绝缘并导致故障,应注意确保栅极驱动信号没有任何可能超过最大允许栅极电压的窄电压尖峰。

 栅极驱动不足(不完全开启) 

MOS管只能切换大量功率,因为它们被设计为在开启时消耗最少的功率。工程师应该确保MOS管硬开启,以最大限度地减少传导期间的耗散。

如果MOS管未完全开启,则设备在传导过程中将具有高电阻,并且会以热量的形式消耗大量功率,10到15伏之间的栅极电压可确保大多数MOS管完全开启。

 缓慢的开关转换 

在稳定的开启和关闭状态期间耗散的能量很少,但在过渡期间耗散了大量的能量。因此,应该尽可能快地在状态之间切换以最小化切换期间的功耗。由于MOS管栅极呈现电容性,因此需要相当大的电流脉冲才能在几十纳秒内对栅极进行充电和放电,峰值栅极电流可以高达一个安培。

 杂散振荡 

MOS管 能够在极短的时间内切换大量电流,输入也具有相对较高的阻抗,这会导致稳定性问题。在某些条件下,由于周围电路中的杂散电感和电容,高压MOS管会以非常高的频率振荡。(频率通常在低 MHz),但这样是非常不受欢迎的,因为它是由于线性操作而发生的,并且代表了高耗散条件。

这种情况可以通过最小化MOS管周围的杂散电感和电容来防止杂散振荡,还应使用低阻抗栅极驱动电路来防止杂散信号耦合到器件的栅极。

 “米勒”效应 

MOS管在其栅极和漏极端子之间具有相当大的“米勒电容”。在低压或慢速开关应用中,这种栅漏电容很少引起关注,但是当高压快速开关时,它可能会引起问题。

当底部器件的漏极电压由于顶部MOS管的导通而迅速上升时,就会出现潜在问题。

这种高电压上升率通过米勒电容电容耦合到MOS管的栅极,会导致底部MOS管的栅极电压上升,从而导致MOS管也开启,就会存在击穿情况,即使不是立即发生,也可以肯定MOS管故障。

米勒效应可以通过使用低阻抗栅极驱动器来最小化,该驱动器在关闭状态时将栅极电压钳位到 0 伏,这减少了从漏极耦合的任何尖峰的影响。在关断状态下向栅极施加负电压可以获得进一步的保护。例如,向栅极施加 -10 V电压将需要超过12V的噪声,以冒开启本应关闭的MOS管的风险。

 对控制器的辐射干扰 

想象一下,将 1pF 的电容从你的火花特斯拉线圈的顶部连接到固态控制器中的每个敏感点的效果,存在的数百千伏射频可以毫无问题地驱动大量电流通过微型电容器直接进入控制电路。

如果控制器没有放置在屏蔽外壳中,这就是实际会发生的情况。

控制电路的高阻抗点几乎不需要杂散电容即可导致异常操作,但运行不正常的控制器可能会尝试同时打开两个相反的MOS管 ,控制电子设备的有效射频屏蔽至关重要。

分离电源和控制电路也是非常理想的,电源开关电路中存在的快速变化的电流和电压仍然具有辐射显着干扰的能力。

 对控制器的传导干扰 

大电流的快速切换会导致电源轨上的电压骤降和瞬态尖峰。如果电源和控制电子设备共用一个或多个电源轨,则可能会对控制电路产生干扰。

良好的去耦和中性点接地是应该用来减少传导干扰影响的技术。作用于驱动MOS管的变压器耦合在防止电噪声传导回控制器方面非常有效。

 静电损坏 

安装MOS管或IGBT器件时,应采取防静电处理措施,以防止栅氧化层损坏。

 高驻波比 

这里要着重说一下,来自一位专业射频工程师的解释。

在脉冲系统中,VSWR不像在CW系统中那么大,但仍然是一个问题。

在CW系统中,典型的发射器设计用于50欧姆的电阻输出阻抗。工程师通过某种传输线连接到负载,希望负载和线路也是50欧姆,并且电力沿电线很好地流动。

但如果负载阻抗不是50欧姆,那么一定量的功率会从阻抗不连续处反射回来。但反射功率会导致几个潜在问题:

1、发射器看起来像一个负载并吸收了所有的功率,这不是一个好的现象。

例如,你的放大器效率为80%,你输入的功率1KW,通常情况下,设备的功耗为200W,最终的功耗为800W,如果所有800W的功耗都被反射回来,忽然之间,这些设备就需要消耗全部的功耗。

2、前向波和反射波的组合会在传输线中产生驻波,在相距1/2波长的点处会变得非常高,从而导致击穿或者其他不良情况,这本质上是表现负载阻抗不是预期的结果。

如果你有一个射频电源在几十兆赫兹,你可以装配一个开放的平行线传输线,在脉冲系统中,你可能会遇到沿线路传播的脉脉冲、阻抗不连续性、反射回以及与发送的下一个脉冲相加的问题。

反射脉冲是相同极性还是不同极性取决于距离和相对阻抗。

如果你有几个不匹配,可能会得到很多来回移动的脉冲,这些脉冲会加强或者取消。这个对于商业配电来说是一个真正的大问题,因为沿线路的传播时间是线路频率周期的很大一部分,当断路器打开和关闭以及雷击时会引起问题。

以上就是关于MOS管烧毁的原因分析。



四、大功率电源中MOSFET功耗的计算

功率MOSFET是便携式设备中大功率开关电源的主要组成部分。此外,对于散热量极低的笔记本电脑来说,这些MOSFET是最难确定的元件。

本文给出了计算MOSFET功耗以及确定其工作温度的步骤,并通过多相、同步整流、降压型CPU核电源中一个30A单相的分布计算示例,详细说明了上述概念。

也许,今天的便携式电源设计者所面临的最严峻挑战就是为当今的高性能CPU提供电源。CPU的电源电流最近每两年就翻一番。事实上,今天的便携式核电源电流需求会高达60A或更多,电压介于0.9V和1.75V之间。但是,尽管电流需求在稳步增长,留给电源的空间却并没有增加—这个现实已达到了热设计的极限甚至超出。

如此高电流的电源通常被分割为两个或更多相,每一相提供15A到30A。这种方式使元件的选择更容易。例如,一个60A电源变成了两个30A电源。但是,这种方法并没有额外增加板上空间,对于热设计方面的挑战基本上没有多大帮助。

在设计大电流电源时,MOSFET是最难确定的元件。这一点在笔记本电脑中尤其显著,这样的环境中,散热器、风扇、热管和其它散热手段通常都留给了CPU。这样,电源设计常常要面临狭小的空间、静止的气流以及来自于附近其它元件的热量等不利因素的挑战。而且,除了电源下面少量的印制板铜膜外,没有任何其它手段可以用来协助耗散功率。

在挑选MOSFET时,首先是要选择有足够的电流处理能力,并具有足够的散热通道的器件。最后还要量化地考虑必要的热耗和保证足够的散热路径。本文将一步一步地说明如何计算这些MOSFET的功率耗散,并确定它们的工作温度。然后,通过分

析一个多相、同步整流、降压型CPU核电源中某一个30A单相的设计实例,进一步阐明这些概念。

 计算MOSFET的耗散功率 

为了确定一个MOSFET是否适合于某特定应用,你必须计算一下其功率耗散,它主要包含阻性和开关损耗两部分:

PDDEVICE TOTAL = PDRESISTIVE + PDSWITCHING

由于MOSFET的功率耗散很大程度上依赖于它的导通电阻(RDS(ON)),计算RDS(ON)看上去是一个很好的出发点。但是MOSFET的RDS(ON)与它的结温(TJ)有关。话说回来,TJ又依赖于MOSFET的功率耗散以及MOSFET的热阻(ΘJA)。这样,似乎很难找到一个着眼点。由于功率耗散的计算涉及到若干个相互依赖的因素,我们可以采用一种迭代过程获得我们所需要的结果(图1)。

51c嵌入式~合集2_引脚_16

图1. 该流程图展示了选择各MOSFET (同步整流器和开关MOSFET)的迭代过程。在这个过程中,各MOSFET的结温为假设值,两个MOSFET的功率耗散和允许环境温度通过计算得出。当允许的环境温度达到或略高于我们所期望的机箱内最高温度时(机箱内安装了电源及其所驱动的电路),这个过程就结束了。

迭代过程始于为每个MOSFET假定一个结温,然后,计算每个MOSFET各自的功率耗散和允许的环境温度。当允许的环境气温达到或略高于电源及其所驱动的电路所在的机壳的期望最高温度时,这个过程便结束了。

有些人总试图使这个计算所得的环境温度尽可能高,但通常这并不是一个好主意。这样作就要求采用更昂贵的MOSFET,在MOSFET下铺设更多的铜膜,或者要求采用一个更大、更快速的风扇产生气流—所有这些都不是我们所期望的。

从某种意义上讲,先假定一个MOSFET结温,然后再计算环境温度,这是一种逆向的考虑方法。毕竟环境温度决定了MOSFET的结温—而不是相反。不过,从一个假定的结温开始计算要比从环境温度开始容易一些。

对于开关MOSFET和同步整流器,我们可以选择一个最大允许的管芯结温(TJ(HOT))作为迭代过程的出发点。多数MOSFET的数据资料只规定了+25°C下的最大RDS(ON),不过最近有些MOSFET文档也给出了+125°C下的最大值。MOSFET的RDS(ON)随着温度而增加,典型温度系数在0.35%/°C至0.5%/°C之间(图2)。

51c嵌入式~合集2_三极管_17

图2. 典型功率MOSFET的导通电阻的温度系数在0.35%每度(绿线)至0.5%每度(红线)之间

如果拿不准,可以用一个较差的温度系数和MOSFET的+25°C规格(或+125°C规格,如果有的话)近似估算在选定的TJ(HOT)下的最大RDS(ON)

RDS(ON)HOT = RDS(ON)SPEC [1 + 0.005 × (TJ(HOT) - TSPEC)]

其中,RDS(ON)SPEC是计算所用的MOSFET导通电阻,TSPEC是规定RDS(ON)SPEC时的温度。利用计算出的RDS(ON)HOT,可以确定同步整流器和开关MOSFET的功率消耗,具体做法如下所述。

在下面的章节中,我们将讨论如何计算各个MOSFET在给定的管芯温度下的功率消耗,以及完成迭代过程的后续步骤(整个过程详述于图1)。

 同步整流器的功耗 

除最轻负载以外,各种情况下同步整流器MOSFET的漏-源电压在打开和关闭过程中都会被续流二极管钳位。因此,同步整流器几乎没有开关损耗,它的功率消耗很容易计算。只需要考虑阻性损耗即可。

最坏情况下的损耗发生在同步整流器工作在最大占空比时,也就是当输入电压达到最大时。利用同步整流器的RDS(ON)HOT和工作占空比,通过欧姆定律,我们可以近似计算出它的功率消耗:

PDSYNCHRONOUS RECTIFIER = [ILOAD² × RDS(ON)HOT] × [1 - (VOUT/VINMAX)]

 开关MOSFET的功耗 

开关MOSFET的阻性损耗计算和同步整流器非常相似,也要利用它的占空比(不同于前者)和RDS(ON)HOT

PDRESISTIVE = [ILOAD² × RDS(ON)HOT] × (VOUT/VIN)

开关MOSFET的开关损耗计算起来比较困难,因为它依赖于许多难以量化并且通常没有规格的因素,这些因素同时影响到打开和关闭过程。我们可以首先用以下粗略的近似公式对某个MOSFET进行评价,然后通过实验对其性能进行验证:

PDSWITCHING = (CRSS × VIN² × fSW × ILOAD)/IGATE

其中CRSS是MOSFET的反向传输电容(数据资料中的一个参数),fSW为开关频率,IGATE是MOSFET的栅极驱动器在MOSFET处于临界导通(VGS位于栅极充电曲线的平坦区域)时的吸收/源出电流。

一旦基于成本因素将选择范围缩小到了特定的某一代MOSFET (不同代MOSFET 的成本差别很大),我们就可以在这一代的器件中找到一个能够使功率耗散最小的器件。这个器件应该具有均衡的阻性和开关损耗。使用更小(更快)的MOSFET所增加的阻性损耗将超过它在开关损耗方面的降低,而更大(RDS(ON)更低) 的器件所增加的开关损耗将超过它对于阻性损耗的降低。

如果VIN是变化的,需要在VIN(MAX)和VIN(MIN)下分别计算开关MOSFET的功率耗散。MOSFET功率耗散的最坏情况可能会出现在最低或最高输入电压下。该耗散功率是两种因素之和:在VIN(MIN)时达到最高的阻性耗散(占空比较高),以及在VIN(MAX)时达到最高的开关损耗(由于VIN²项的缘故)。一个好的选择应该在VIN的两种极端情况下具有大致相同的耗散,并且在整个VIN范围内保持均衡的阻性和开关损耗。

如果损耗在VIN(MIN)时明显高出,则阻性损耗起主导作用。这种情况下,可以考虑用一个更大一点的开关MOSFET (或将一个以上的多个管子相并联)以降低RDS(ON)。但如果在VIN(MAX)时损耗显著高出,则应该考虑降低开关MOSFET的尺寸(如果是多管并联的话,或者去掉一个MOSFET),以便使其开关速度更快一点。

如果阻性和开关损耗已达平衡,但总功耗仍然过高,有多种办法可以解决:

  • 改变问题的定义。例如,重新定义输入电压范围。
  • 改变开关频率以便降低开关损耗,有可能使用更大一点的、RDS(ON)更低的开关MOSFET。
  • 增加栅极驱动电流,有可能降低开关损耗。MOSFET自身的内部栅极电阻最终限制了栅极驱动电流,实际上限制了这种方法的有效性。
  • 采用一个改进技术的MOSFET,以便同时获得更快的开关速度、更低的RDS(ON)和更低的栅极电阻。

脱离某个给定的条件对MOSFET的尺寸作更精细的调整是不大可能的,因为器件的选择范围是有限的。选择的底线是MOSFET在最坏情况下的功耗必须能够被耗散掉。

 热阻 

下一步是要计算每个MOSFET周围的环境温度,在这个温度下,MOSFET结温将达到我们的假定值

(按照前面图1所示的迭代过程,确定合适的MOSFET来作为同步整流器和开关MOSFET)。为此,首先需要确定每个MOSFET结到环境的热阻(ΘJA)。

热阻的估算可能会比较困难。单一器件在一个简单PCB上的ΘJA测算相对容易一些,而要在一个系统内去预测实际电源的热性能是很困难的,那里有许多热源在争夺有限的散热通道。如果有多个MOSFET被并联使用,其整体热阻的计算方法,和计算两个以上并联电阻的等效电阻一样。

我们可以从MOSFET的ΘJA规格开始。对于单一管芯、8引脚封装的MOSFET来讲,ΘJA通常接近于62°C/W。其他类型的封装,有些带有散热片或裸露的导热片,其热阻一般会在40°C/W至50°C/W (表1)。

51c嵌入式~合集2_数据传输_18

表1. MOSFET封装的典型热阻

可以用下面的公式计算MOSFET的管芯相对于环境的温升:

TJ(RISE) = PDDEVICE TOTAL × ΘJA

接下来,计算导致管芯达到预定TJ(HOT)时的环境温度:

TAMBIENT = TJ(HOT) - TJ(RISE)

如果计算出的TAMBIENT低于机壳的最大额定环境温度(意味着机壳的最大额定环境温度将导致MOSFET的预定TJ(HOT)被突破),必须采用下列一条或更多措施:

  • 升高预定的TJ(HOT),但不要超出数据手册规定的最大值。
  • 选择更合适的MOSFET以降低MOSFET的功耗。
  • 通过增加气流或MOSFET周围的铜膜降低ΘJA

重算TAMBIENT (采用速算表可以简化计算过程,经过多次反复方可选出一个可接受的设计)。另一方面,如果计算出的TAMBIENT高出机壳的最大额定环境温度很多,可以采取下述可选步骤中的任何一条或全部:

  • 降低预定的TJ(HOT)
  • 减小专用于MOSFET散热的覆铜面积。
  • 采用更廉价的MOSFET。

最后这几个步骤是可选的,因为在此情况下MOSFET不会因过热而损坏。不过,通过这些步骤,只要保证TAMBIENT高出机壳最高温度一定裕量,我们可以降低线路板面积和成本。

上述计算过程中最大的误差源来自于ΘJA。你应该仔细阅读数据资料中有关ΘJA规格的所有注释。一般规范都假定器件安装在1in²的2oz铜膜上。铜膜耗散了大部分的功率,不同数量的铜膜ΘJA差别很大。例如,带有1in²铜膜的D-Pak封装ΘJA会达到50°C/W。但是如果只将铜膜铺设在引脚的下面,ΘJA将高出两倍(表1)。

如果将多个MOSFET并联使用,ΘJA主要取决于它们所安装的铜膜面积。两个器件的等效ΘJA可以是单个器件的一半,但必须同时加倍铜膜面积。也就是说,增加一个并联的MOSFET而不增加铜膜的话,可以使RDS(ON)减半但不会改变ΘJA很多。

最后,ΘJA规范通常都假定没有任何其它器件向铜膜的散热区传递热量。但在高电流情况下,功率通路上的每个元件,甚至是PCB引线都会产生热量。为了避免MOSFET过热,需仔细估算实际情况下的ΘJA,并采取下列措施:

  • 仔细研究选定MOSFET现有的热性能方面的信息。
  • 考察是否有足够的空间,以便设置更多的铜膜、散热器和其它器件。
  • 确定是否有可能增加气流。
  • 观察一下在假定的散热路径上,是否有其它显著散热的器件。
  • 估计一下来自周围元件或空间的过剩热量或冷量。

 设计实例 

图3所示的CPU核电源提供1.5V/60A输出。两个工作于300kHz的相同的30A功率级总共提供60A输出电流。MAX1544 IC驱动两级电路,采用180°错相工作方式。该电源的输入范围7V至24V,机壳的最大额定环境温度为+60°C。

51c嵌入式~合集2_数据传输_19

图3. 该降压型开关调节器中的MOSFET经由本文所述的迭代过程选出。板级设计者通常采用该类型的开关调节器驱动今天的高性能CPU。

同步整流器由两片并联的IRF6603 MOSFET组成,组合器件的最大RDS(ON)在室温下为2.75mΩ,在+125°C (预定的TJ(HOT))下近似为4.13mΩ。在最大占空比94%,30A负载电流,以及4.13mΩ最大RDS(ON)时,这些并联MOSFET的功耗大约为3.5W。提供2in²铜膜来耗散这些功率,总体ΘJA大约为18°C/W,该热阻值取自MOSFET的数据资料。组合MOSFET的温升将接近于+63°C,因此该设计应该能够工作在最高+60°C的环境温度下。

开关MOSFET由两只IRF6604 MOSFET并联组成,组合器件的最大RDS(ON)在室温下为6.5mΩ,在+125°C (预定的TJ(HOT))下近似为9.75mΩ。组合后的CRSS为380pF。MAX1544的1Ω高边栅极驱动器可提供将近1.6A的驱动。VIN = 7V时,阻性损耗为1.63W,而开关损耗近似为0.105W。输入为VIN = 24V时,阻性损耗为0.475W 而开关损耗近似为1.23W。总损耗在各输入工作点大致相等,最坏情况(最低VIN)下的总损耗为1.74W。

28°C/W的ΘJA将产生+46°C的温升,允许工作于最高+80°C的环境温度。若环境温度高于封装的最大规定温度,设计人员应考虑减小用于MOSFET的覆铜面积,尽管该步骤不是必须的。本例中的覆铜面积只单独考虑了MOSFET的需求。如果还有其它器件向这个区域散热的话,可能还需要更多的覆铜面积。如果没有足够的空间

增加覆铜,则可以降低总功耗,传递热量到低耗散区,或者采用主动的办法将热量移走。

 结论 

热管理是大功率便携式设计中难度较大的领域之一。这种难度迫使我们有必要采用上述迭代过程。尽管该过程能够引领板级设计者靠近最终设计,但是还必须通过实验来最终确定设计流程是否足够精确。计算MOSFET的热性能,为它们提供足够的耗散途径,然后在实验室中检验这些计算,这样有助于获得一个健壮的热设计。



五、最快的通用JSON库,LJSON

1 LJSON 说明

LJSON 是一个远远快于 cJSON、大幅度快于 RapidJSON 的 C 实现的 JSON 库,他是目前最快的通用 JSON 库。

LJSON 支持 JSON 的解析、打印、编辑,提供 DOM 和 SAX 接口,I/O 支持字符串和文件,且完全支持 nativejson-benchmark 的测试用例。

LJSON 默认使用个人开发的 ldouble 算法打印浮点数,和标准库对比可能只有第15位小数的区别,是目前最快的浮点数转字符串算法;也可选择个人优化过的 grisu2 算法或 dragonbox 算法。

2 功能特点

  • 更快:打印和解析速度比 cJSON 和 RapidJSON 都要快,速度最高可比 CJSON 快19倍,比 Rapid JSON 快1倍,见测试结果
  • 更省:提供多种省内存的手段,例如内存池、文件边读边解析、边打印边写文件、SAX方式的接口,可做到内存占用是个常数
  • 更强:支持DOM和SAX风格的API,提供普通模式和内存池模式JSON的接口,支持字符串和文件作为输入输出(可扩展支持其它流),扩展支持长长整形和十六进制数字
  • 更友好:C语言实现,不依赖任何库,不含平台相关代码,只有一个头文件和库文件,和cJSON对应一致的接口,代码逻辑比任何JSON库都更清晰

3 编译运行3.1 编译方法

  • 直接编译
gcc -o ljson json.c json_test.c -O2 -ffunction-sections -fdata-sections -W -Wall
  • IMAKE 编译
make O=<编译输出目录> && make O=<编译输出目录> DESTDIR=<安装目录>
  • 交叉编译
make O=<编译输出目录> CROSS_COMPILE=<交叉编译器前缀> && make O=<编译输出目录> DESTDIR=<安装目录>
  • 选择浮点数转字符串算法 gcc -DJSON_DTOA_ALGORITHM=n, n可能为 0 / 1 / 2 / 3
  • 0: 个人实现的 ldouble 算法: 比谷歌的 grisu2 的默认实现快 117% ,比腾讯优化的 grisu2 实现快 30% ,比 sprintf 快 13.3 倍
  • 1: C标准库的 sprintf
  • 2: 个人优化的 grisu2 算法: 谷歌的 grisu2 的默认实现比 sprintf 快 5.7 倍,腾讯优化的 grisu2 实现比 sprintf 快 9.1 倍,LJSON 的优化实现比 sprintf 快 11.4 倍
  • 3: 个人优化的 dragonbox 算法: 性能和 ldouble 算法基本相差无几

3.2 运行方法

./json <json文件名> <测试序号0-7>

3.3 调试方法

  • 设置 json.c 中的变量 JSON_ERROR_PRINT_ENABLE 的值为 1 后重新编译

3.4 错误检测

  • 设置 json.c 中的变量 JSON_STRICT_PARSE_MODE 的值为 0 / 1 / 2 后重新编译
  • 设置为2时 100% 符合 nativejson-benchmark 的测试用例
  • 0: 关闭不是常见的错误检测,例如解析完成后还剩尾后字符
  • 1: 检测更多的错误,且允许 key 为空字符串
  • 2: 除去 1 开启的错误检测之外,还关闭某些不是标准的特性,例如十六进制数字,第一个json对象不是array或object

4 性能测试

注:主要是测试速度,O2 优化等级且默认选项编译,测试文件来自 nativejson-benchmark 项目

测试平台: Ambarella CV25M Board | CPU: ARM CortexA53 | OS: Linux-5.15
测试结果: LJSON 比cJSON 解析最快可达 475%,打印最快可达 2225%,LJSON 比 RapidJSON 解析最快可达 131%,打印最快可达 137%

AARCH64-Linux测试结果:

51c嵌入式~合集2_引脚_20

测试平台: PC | CPU: Intel i7-10700 | OS: Ubuntu 18.04 (VirtualBox)
测试结果: :LJSON 比cJSON 解析最快可达 560%,打印最快可达 2894%,LJSON 比 RapidJSON 解析最快可达 75%,打印最快可达 124%

x86_64-Linux测试结果:

51c嵌入式~合集2_数据传输_21

ldouble-x86_64测试结果:

51c嵌入式~合集2_引脚_22

测试平台: Nationalchip STB | CPU: CSKY | DDR3: 128MB, 533MHz | OS: ECOS
注: 老版本测试结果,新版本删除了临时buffer,且解析速度提升了两倍

ECOS测试结果:

51c嵌入式~合集2_引脚_23

5 json对象结构

使用 long long 类型支持,编译时需要设置 json.h 中的 JSON_LONG_LONG_SUPPORT 值为 1

struct json_list {
    struct json_list *next;
};                                      // 单向链表


struct json_list_head {
    struct json_list *next, *prev;
};                                      // 链表头,分别指向链表的第一个元素和最后一个元素


typedef enum {
    JSON_NULL = 0,
    JSON_BOOL,
    JSON_INT,
    JSON_HEX,
#if JSON_LONG_LONG_SUPPORT
    JSON_LINT,
    JSON_LHEX,
#endif
    JSON_DOUBLE,
    JSON_STRING,
    JSON_ARRAY,
    JSON_OBJECT
} json_type_t;                          // json对象类型


typedef struct {
    unsigned int type:4;                // json_type_t,json_string_t作为key时才有type
    unsigned int escaped:1;             // str是否包含需要转义的字符
    unsigned int alloced:1;             // str是否是malloc的,只用于SAX APIs
    unsigned int reserved:2;
    unsigned int len:24;                // str的长度
    char *str;
} json_string_t;                        // json string 对象或 type+key


typedef union {
    bool vbool;
    int vint;
    unsigned int vhex;
#if JSON_LONG_LONG_SUPPORT
    long long int vlint;
    unsigned long long int vlhex;
#endif
    double vdbl;
} json_number_t;                        // json数字对象值


#if JSON_SAX_APIS_SUPPORT
typedef enum {
    JSON_SAX_START = 0,
    JSON_SAX_FINISH
} json_sax_cmd_t;                       // 只用于SAX APIs,JSON_ARRAY或JSON_OBJECT有开始和结束
#endif


typedef union {
    json_number_t vnum;                 // json数字对象的值
    json_string_t vstr;                 // json字符串对象的值
#if JSON_SAX_APIS_SUPPORT
    json_sax_cmd_t vcmd;
#endif
    struct json_list_head head;         // json结构体/数组对象的值
} json_value_t;                         // json对象值


typedef struct {
    struct json_list list;              // json链表节点
    json_string_t jkey;                 // json对象的type和key
    json_value_t value;                 // json对象的值
} json_object;                          // json对象


typedef struct {
    unsigned int hash;                  // json key的hash,只有JSON_OBJECT的子项才有key
    json_object *json;                  // json对象的指针
} json_item_t;


typedef struct {
    unsigned int conflicted:1;          // key的hash是否有冲突
    unsigned int reserved:31;
    unsigned int total;                 // items分配的内存数目
    unsigned int count;                 // items中子项的个数
    json_item_t *items;                 // 存储子项的数组
} json_items_t;           // 存储JSON_ARRAY或JSON_OBJECT的所有子项
  • 使用单向链表管理json节点树

6 经典编辑模式接口

void json_memory_free(void *ptr);
  • json_item_total_get: 释放经典编辑模式申请的内存或打印到字符串返回的指针
int json_item_total_get(json_object *json);
  • json_item_total_get: 获取节点总数
void json_del_object(json_object *json);
json_object *json_new_object(json_type_t type);
json_object *json_create_item(json_type_t type, void *value);
json_object *json_create_item_array(json_type_t type, void *values, int count);


static inline json_object *json_create_null(void);
static inline json_object *json_create_bool(bool value);
static inline json_object *json_create_int(int value);
static inline json_object *json_create_hex(unsigned int value);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *json_create_lint(long long int value);
static inline json_object *json_create_lhex(unsigned long long int value);
#endif
static inline json_object *json_create_double(double value);
static inline json_object *json_create_string(json_string_t *value);
static inline json_object *json_create_array(void);
static inline json_object *json_create_object(void);


static inline json_object *json_create_bool_array(bool *values, int count);
static inline json_object *json_create_int_array(int *values, int count);
static inline json_object *json_create_hex_array(unsigned int *values, int count);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *json_create_lint_array(long long int *values, int count);
static inline json_object *json_create_lhex_array(unsigned long long int *values, int count);
#endif
static inline json_object *json_create_double_array(double *values, int count);
static inline json_object *json_create_string_array(json_string_t *values, int count);
  • json_del_object: 删除节点(并递归删除子节点)
  • json_new_object: 创建指定类型的空节点
  • json_create_item: 创建指定类型的有值节点
  • json_create_item_array: 快速创建指定类型的数组节点,使用要点同上
  • 要点:创建的节点使用完后需要使用json_del_object删除,但是如果把该节点加入了array或object,该节点无需再删除
void json_string_info_update(json_string_t *jstr);
unsigned int json_string_hash_code(json_string_t *jstr);
int json_string_strdup(json_string_t *src, json_string_t *dst);
static inline int json_set_key(json_object *json, json_string_t *jkey);
static inline int json_set_string_value(json_object *json, json_string_t *jstr);
  • json_string_info_update: 更新jstr的len和escaped,如果传入的len大于0,则什么都不做
  • json_string_hash_code: 获取字符串的hash值
  • json_string_strdup: 修改LJSON中的字符串
  • json_set_key: 修改节点的key(JSON_OBJECT类型下的子节点才有key)
  • json_set_string_value: 修改string类型节点的value
int json_get_number_value(json_object *json, json_type_t type, void *value);
int json_set_number_value(json_object *json, json_type_t type, void *value);


static inline bool json_get_bool_value(json_object *json);
static inline int json_get_int_value(json_object *json);
static inline unsigned int json_get_hex_value(json_object *json);
#if JSON_LONG_LONG_SUPPORT
static inline long long int json_get_lint_value(json_object *json);
static inline unsigned long long int json_get_lhex_value(json_object *json);
#endif
static inline double json_get_double_value(json_object *json);


static inline int json_set_bool_value(json_object *json, bool value);
static inline int json_set_int_value(json_object *json, int value);
static inline int json_set_hex_value(json_object *json, unsigned int value);
#if JSON_LONG_LONG_SUPPORT
static inline int json_set_lint_value(json_object *json, long long int value);
static inline int json_set_lhex_value(json_object *json, unsigned long long int value);
#endif
static inline int json_set_double_value(json_object *json, double value);
  • json_get_number_value: 获取number类型节点的value,返回值: 正值(原有类型枚举值)表示成功有强制转换,0表示成功且类型对应,-1表示失败不是number类型
  • json_set_number_value: 修改number类型节点的value,返回值说明同上
int json_get_array_size(json_object *json);
int json_get_object_size(json_object *json);
json_object *json_get_array_item(json_object *json, int seq, json_object **prev);
json_object *json_get_object_item(json_object *json, const char *key, json_object **prev);
  • json_get_array_size: 获取array类型节点的大小(有多少个一级子节点)
  • json_get_object_size: 获取object类型节点的大小(有多少个一级子节点)
  • json_get_array_item: 获取array类型节点的的第seq个子节点
  • json_get_object_item: 获取object类型节点的指定key的子节点
json_object *json_search_object_item(json_items_t *items, json_string_t *jkey, unsigned int hash);
void json_free_items(json_items_t *items);
int json_get_items(json_object *json, json_items_t *items);
  • json_search_object_item: 二分法查找items中的指定key的json对象
  • json_free_items: 释放items中分配的内存
  • json_get_items: 获取JSON_ARRAY或JSON_OBJECT中的所有一级子节点,加速访问
int json_add_item_to_array(json_object *array, json_object *item);
int json_add_item_to_object(json_object *object, json_object *item);
  • 将节点加入到array或object,加入object需要先设置item的key
  • 经典模式如果该节点加入成功,无需再调用json_del_object删除该节点
json_object *json_detach_item_from_array(json_object *json, int seq);
json_object *json_detach_item_from_object(json_object *json, const char *key);
  • 将指定的子节点从array或object取下并返回
  • 使用完成后需要使用json_del_object删除返回的子节点
  • 注:使用内存cache的json不需要调用json_del_object删除返回的子节点
int json_del_item_from_array(json_object *json, int seq);
int json_del_item_from_object(json_object *json, const char *key);
  • 将指定的子节点从array或object删除
int json_replace_item_in_array(json_object *array, int seq, json_object *new_item);
int json_replace_item_in_object(json_object *object, json_object *new_item);
  • 将array或object指定的子节点替换成new_item
  • 如果原来的子节点不存在就直接新增new_item
json_object *json_deepcopy(json_object *json);
int json_copy_item_to_array(json_object *array, json_object *item);
int json_copy_item_to_object(json_object *object, json_object *item);
  • json_deepcopy: 节点深度复制
  • json_copy_item_to_xxxx: 将节点复制并加入到array或object
  • 如果该节点加入成功,还需要再调用json_del_object删除原来传入的节点
json_object *json_add_new_item_to_array(json_object *array, json_type_t type, void* value);
json_object *json_add_new_item_to_object(json_object *object, json_type_t type, json_string_t *jkey, void* value);


static inline json_object *json_add_null_to_array(json_object *array);
static inline json_object *json_add_bool_to_array(json_object *array, bool value);
static inline json_object *json_add_int_to_array(json_object *array, int value);
static inline json_object *json_add_hex_to_array(json_object *array, unsigned int value);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *json_add_lint_to_array(json_object *array, long long int value);
static inline json_object *json_add_lhex_to_array(json_object *array, unsigned long long int value);
#endif
static inline json_object *json_add_double_to_array(json_object *array, double value);
static inline json_object *json_add_string_to_array(json_object *array, json_string_t *value);
static inline json_object *json_add_array_to_array(json_object *array);
static inline json_object *json_add_object_to_array(json_object *array);


static inline json_object *json_add_null_to_object(json_object *object, json_string_t *jkey);
static inline json_object *json_add_bool_to_object(json_object *object, json_string_t *jkey, bool value);
static inline json_object *json_add_int_to_object(json_object *object, json_string_t *jkey, int value);
static inline json_object *json_add_hex_to_object(json_object *object, json_string_t *jkey, unsigned int value);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *json_add_lint_to_object(json_object *object, json_string_t *jkey, long long int value);
static inline json_object *json_add_lhex_to_object(json_object *object, json_string_t *jkey, unsigned long long int value);
#endif
static inline json_object *json_add_double_to_object(json_object *object, json_string_t *jkey, double value);
static inline json_object *json_add_string_to_object(json_object *object, json_string_t *jkey, json_string_t *value);
static inline json_object *json_add_array_to_object(json_object *object, json_string_t *jkey);
static inline json_object *json_add_object_to_object(json_object *object, json_string_t *jkey);
  • json_add_new_item_to_array: 新建指定类型的节点,并将该节点加入array
  • json_add_new_item_to_object: 新建指定类型的节点,并将该节点加入object
/*
 * The below APIs are also available to pool json:
 * json_item_total_get
 * json_string_info_update
 * json_get_number_value / ...
 * json_set_number_value / ...
 * json_get_array_size
 * json_get_object_size
 * json_get_array_item
 * json_get_object_item
 * json_search_object_item
 * json_free_items
 * json_get_items
 * json_add_item_to_array
 * json_add_item_to_object
 * json_detach_item_from_array
 * json_detach_item_from_object
 */
  • 编辑(一般模式)的一些API(内部没有调用malloc/free)也可以用于内存池
  • 注意: pool模式时,json_detach_item_from_array/object返回的json节点不能使用json_del_object删除

7 内存池结构

typedef struct {
    struct json_list list;              // 链表节点
    size_t size;                        // 内存大小
    char *ptr;                          // 首部地址
    char *cur;                          // 当前地址
} json_mem_node_t;


typedef struct {
    struct json_list_head head;         // json_mem_node_t挂载节点
    size_t mem_size;                    // 默认分配块内存大小
    json_mem_node_t *cur_node;          // 当前使用的内存节点
} json_mem_mgr_t;


typedef struct {
    json_mem_mgr_t obj_mgr;             // 对象节点的内存管理
    json_mem_mgr_t key_mgr;             // 字符串key的内存管理
    json_mem_mgr_t str_mgr;             // 字符串value的内存管理
} json_mem_t;
  • 内存池原理是先分配一个大内存,然后从大内存中分配小内存
  • 内存池只能统一释放申请

8 内存池编辑模式接口

void pjson_memory_free(json_mem_t *mem);
void pjson_memory_init(json_mem_t *mem);
+int pjson_memory_statistics(json_mem_mgr_t *mgr);
  • pjson_memory_free: 释放json内存池管理的所有内存
  • pjson_memory_init: 初始化json内存池管理结构
  • pjson_memory_statistics: 统计内存池分配的内存
  • 注:编辑模式初始化内存池后可修改mem_size
  • 注:使用内存池前需要使用pjson_memory_init初始化内存池入口,全部使用完成后使用pjson_memory_free释放
  • 注:绝对不要调用存在malloc, free之类的api,例如json_new_object和json_del_object等
json_object *pjson_new_object(json_type_t type, json_mem_t *mem);
json_object *pjson_create_item(json_type_t type, void *value, json_mem_t *mem);
json_object *pjson_create_item_array(json_type_t item_type, void *values, int count, json_mem_t *mem);


static inline json_object *pjson_create_null(json_mem_t *mem);
static inline json_object *pjson_create_bool(bool value, json_mem_t *mem);
static inline json_object *pjson_create_int(int value, json_mem_t *mem);
static inline json_object *pjson_create_hex(unsigned int value, json_mem_t *mem);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *pjson_create_lint(long long int value, json_mem_t *mem);
static inline json_object *pjson_create_lhex(unsigned long long int value, json_mem_t *mem);
#endif
static inline json_object *pjson_create_double(double value, json_mem_t *mem);
static inline json_object *pjson_create_string(json_string_t *value, json_mem_t *mem);
static inline json_object *pjson_create_array(json_mem_t *mem);
static inline json_object *pjson_create_object(json_mem_t *mem);


static inline json_object *pjson_create_bool_array(bool *values, int count, json_mem_t *mem);
static inline json_object *pjson_create_int_array(int *values, int count, json_mem_t *mem);
static inline json_object *pjson_create_hex_array(unsigned int *values, int count, json_mem_t *mem);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *pjson_create_lint_array(long long int *values, int count, json_mem_t *mem);
static inline json_object *pjson_create_lhex_array(unsigned long long int *values, int count, json_mem_t *mem);
#endif
static inline json_object *pjson_create_double_array(double *values, int count, json_mem_t *mem);
static inline json_object *pjson_create_string_array(json_string_t *values, int count, json_mem_t *mem);
  • pjson_new_object: 在内存池中创建指定类型的空节点
  • pjson_create_item: 在内存池中创建指定类型的有值节点
  • pjson_create_item_array: 在内存池中创建(子节点指定类型)的array节点
int pjson_string_strdup(json_string_t *src, json_string_t *dst, json_mem_mgr_t *mgr);
static inline int pjson_set_key(json_object *json, json_string_t *jkey, json_mem_t *mem);
static inline int pjson_set_string_value(json_object *json, json_string_t *jstr, json_mem_t *mem);
  • pjson_string_strdup: 修改JSON中的字符串,该字符串在内存池中分配
  • pjson_set_key: 修改json节点的key,该key在内存池中分配
  • pjson_set_string_value: 修改 JSON_STRING 类型json节点的值,该值在内存池中分配
int pjson_replace_item_in_array(json_object *array, int seq, json_object *new_item);
int pjson_replace_item_in_object(json_object *object, json_object *new_item);
  • 将array或object指定的子节点替换成new_item
  • 如果原来的子节点不存在就直接新增new_item
json_object *pjson_deepcopy(json_object *json, json_mem_t *mem);
int pjson_copy_item_to_array(json_object *array, json_object *item, json_mem_t *mem);
int pjson_copy_item_to_object(json_object *object, json_object *item, json_mem_t *mem);
  • pjson_deepcopy: 节点深度复制
  • pjson_copy_item_to_xxxx: 将节点复制并加入到array或object
json_object *pjson_add_new_item_to_array(json_object *array, json_type_t type, void *value, json_mem_t *mem);
json_object *pjson_add_new_item_to_object(json_object *object, json_type_t type, json_string_t *jkey, void *value, json_mem_t *mem);


static inline json_object *pjson_add_null_to_array(json_object *array, json_mem_t *mem);
static inline json_object *pjson_add_bool_to_array(json_object *array, bool value, json_mem_t *mem);
static inline json_object *pjson_add_int_to_array(json_object *array, int value, json_mem_t *mem);
static inline json_object *pjson_add_hex_to_array(json_object *array, unsigned int value, json_mem_t *mem);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *pjson_add_lint_to_array(json_object *array, long long int value, json_mem_t *mem);
static inline json_object *pjson_add_lhex_to_array(json_object *array, unsigned long long int value, json_mem_t *mem);
#endif
static inline json_object *pjson_add_double_to_array(json_object *array, double value, json_mem_t *mem);
static inline json_object *pjson_add_string_to_array(json_object *array, json_string_t *value, json_mem_t *mem);
static inline json_object *pjson_add_array_to_array(json_object *array, json_mem_t *mem);
static inline json_object *pjson_add_object_to_array(json_object *array, json_mem_t *mem);


static inline json_object *pjson_add_null_to_object(json_object *object, json_string_t *jkey, json_mem_t *mem);
static inline json_object *pjson_add_bool_to_object(json_object *object, json_string_t *jkey, bool value, json_mem_t *mem);
static inline json_object *pjson_add_int_to_object(json_object *object, json_string_t *jkey, int value, json_mem_t *mem);
static inline json_object *pjson_add_hex_to_object(json_object *object, json_string_t *jkey, unsigned int value, json_mem_t *mem);
#if JSON_LONG_LONG_SUPPORT
static inline json_object *pjson_add_lint_to_object(json_object *object, json_string_t *jkey, long long int value, json_mem_t *mem);
static inline json_object *pjson_add_lhex_to_object(json_object *object, json_string_t *jkey, unsigned long long int value, json_mem_t *mem);
#endif
static inline json_object *pjson_add_double_to_object(json_object *object, json_string_t *jkey, double value, json_mem_t *mem);
static inline json_object *pjson_add_string_to_object(json_object *object, json_string_t *jkey, json_string_t *value, json_mem_t *mem);
static inline json_object *pjson_add_array_to_object(json_object *object, json_string_t *jkey, json_mem_t *mem);
static inline json_object *pjson_add_object_to_object(json_object *object, json_string_t *jkey, json_mem_t *mem);
  • pjson_add_new_item_to_array: 在内存池中创建指定类型的子节点,并加入到array
  • pjson_add_new_item_to_object: 在内存池中创建指定类型的子节点,并加入到object

9 DOM打印/DOM解析

typedef struct {
    size_t str_len;                     // 打印到字符串时返回生成的字符串长度(strlen)
    size_t plus_size;                   // 打印生成的字符串的realloc的增量大小 / write buffer的缓冲区大小
    size_t item_size;                   // 每个json对象生成字符串的预估的平均长度
    int item_total;                     // json对象节点的总数
    bool format_flag;                   // 字符串是否进行格式化
    const char *path;                   // 文件保存路径
} json_print_choice_t;
  • plus_size: 经典模式下打印字符串realloc的增量,或write buffer的缓冲区大小,最小值/默认值为 JSON_PRINT_SIZE_PLUS_DEF
  • item_size: 每个json对象生成字符串的预估的平均长度,最小值/默认值为 JSON_UNFORMAT_ITEM_SIZE_DEF 和 JSON_FORMAT_ITEM_SIZE_DEF
  • item_total: json对象节点的总数如果此值未设置,将自动计算总数;否则取默认值JSON_PRINT_NUM_PLUS_DEF
  • format_flag: 格式化打印选项,false: 压缩打印;true: 格式化打印
  • path: 如果path不为空,将直接边打印边输出到文件;否则是打印到一个大的完整字符串
char *json_print_common(json_object *json, json_print_choice_t *choice);


static inline char *json_print_format(json_object *json, int item_total, size_t *length);
static inline char *json_print_unformat(json_object *json, int item_total, size_t *length);
static inline char *json_fprint_format(json_object *json, int item_total, const char *path);
static inline char *json_fprint_unformat(json_object *json, int item_total, const char *path);
  • json_print_common: 打印通用接口
  • json_print_format: 格式化打印成字符串的简写接口,需要 json_memory_free释放返回的字符串
  • json_print_unformat: 类似json_print_format,只是非格式化打印
  • json_fprint_format: 格式化直接边打印边输出到文件的简写接口,成功返回"ok"字符串,不需要 json_memory_free释放返回的字符串
  • json_fprint_unformat: 类似json_fprint_format,只是非格式化打印
typedef struct {
    size_t mem_size;                    // 内存池每个内存块的大小
    size_t read_size;                   // json读缓冲的初始大小
    size_t str_len;                     // 要解析的字符串长度
    bool reuse_flag;                    // 是否复用原始json字符串,原始json字符串会被修改
    json_mem_t *mem;                    // 内存池管理结构
    const char *path;                   // 要解析的json文件的路径
    char *str;                          // 要解析的json字符串的指针
} json_parse_choice_t;
  • mem_size: 内存池每个内存块的大小,最小值为 (str_len / JSON_PARSE_NUM_DIV_DEF) 的值
  • read_size: json读缓冲的初始大小,最小值 JSON_PARSE_READ_SIZE_DEF
  • str_len: 要解析的字符串长度 strlen(str),使用内存池时该参数有效,如果为0,json_parse_common会自己计算一次
  • path: 要解析的json文件,str 和 path 有且只有一个有值
  • str: 要解析的json字符串,str 和 path 有且只有一个有值
json_object *json_parse_common(json_parse_choice_t *choice);
static inline json_object *json_parse_str(char *str, size_t str_len);
static inline json_object *json_fast_parse_str(char *str, size_t str_len, json_mem_t *mem);
static inline json_object *json_reuse_parse_str(char *str, size_t str_len, json_mem_t *mem);
static inline json_object *json_parse_file(const char *path);
static inline json_object *json_fast_parse_file(const char *path, json_mem_t *mem);
  • json_parse_common: 解析通用接口
  • json_parse_str: 类似cJSON的经典字符串解析的简写接口,用完后需要json_del_object释放返回的管理结构
  • json_fast_parse_str: 使用内存池的字符串解析的简写接口,使用前必须使用pjson_memory_init初始化mem,用完后需要pjson_memory_free释放
  • json_reuse_parse_str: 使用内存池极速解析并复用原始字符串,会修改传入的字符串,使用过程中不要释放原始的str , 速度最快,占用内存最少
  • json_parse_file: 类似json_parse_str,只是从文件边读边解析
  • json_fast_parse_file: 类似json_parse_str, 只是边读文件边解析

10 SAX打印/SAX解析

使用 SAX APIs 编译时需要设置 json.h 中的 JSON_SAX_APIS_SUPPORT 值为 1

typedef void* json_sax_print_hd;
  • json_sax_print_hd: 实际是json_sax_print_t指针
json_sax_print_hd json_sax_print_start(json_print_choice_t *choice);
static inline json_sax_print_hd json_sax_print_format_start(int item_total);
static inline json_sax_print_hd json_sax_print_unformat_start(int item_total);
static inline json_sax_print_hd json_sax_fprint_format_start(int item_total, const char *path);
static inline json_sax_print_hd json_sax_fprint_unformat_start(int item_total, const char *path);
  • json_sax_print_start: sax打印时必须先调用此函数,进行资源初始化并获取句柄 json_sax_print_hd
  • 如果打印到字符串,最好给一个item_total值,用于计算生成字符串的增量
int json_sax_print_value(json_sax_print_hd handle, json_type_t type, json_string_t *jkey, const void *value);
static inline int json_sax_print_null(json_sax_print_hd handle, json_string_t *jkey);
static inline int json_sax_print_bool(json_sax_print_hd handle, json_string_t *jkey, bool value);
static inline int json_sax_print_int(json_sax_print_hd handle, json_string_t *jkey, int value);
static inline int json_sax_print_hex(json_sax_print_hd handle, json_string_t *jkey, unsigned int value);
#if JSON_LONG_LONG_SUPPORT
static inline int json_sax_print_lint(json_sax_print_hd handle, json_string_t *jkey, long long int value);
static inline int json_sax_print_lhex(json_sax_print_hd handle, json_string_t *jkey, unsigned long long int value);
#endif
static inline int json_sax_print_double(json_sax_print_hd handle, json_string_t *jkey, double value);
static inline int json_sax_print_string(json_sax_print_hd handle, json_string_t *jkey, json_string_t *value);
static inline int json_sax_print_array(json_sax_print_hd handle, json_string_t *jkey, json_sax_cmd_t value);
static inline int json_sax_print_object(json_sax_print_hd handle, json_string_t *jkey, json_sax_cmd_t value);
  • json_sax_print_value: sax 条目通用打印接口,如果要打印节点的父节点是object,key必须有值;其它情况下key填不填值均可
  • array 和 object 要打印两次,一次值是 JSON_SAX_START 表示开始,一次值是 JSON_SAX_FINISH 表示完成
  • 传入key时可以先不用json_saxstr_update 计算长度
char *json_sax_print_finish(json_sax_print_hd handle, size_t *length);
  • json_sax_print_finish: sax打印完成必须调用此函数,释放中间资源并返回字符串
  • 打印成字符串时,该函数返回打印的字符串, 需要 json_memory_free释放返回的字符串
  • 直接边打印边输出到文件时,成功返回"ok"字符串,不需要 json_memory_free释放返回的字符串
typedef enum {
    JSON_SAX_PARSE_CONTINUE = 0,
    JSON_SAX_PARSE_STOP
} json_sax_ret_t;


typedef struct {
    int total;
    int index;
    json_string_t *array;
    json_value_t value;
} json_sax_parser_t;
typedef json_sax_ret_t (*json_sax_cb_t)(json_sax_parser_t *parser);


typedef struct {
    char *str;
    const char *path;
    size_t read_size;
    json_sax_cb_t cb;
} json_sax_parse_choice_t;
  • json_sax_ret_t: JSON_SAX_PARSE_CONTINUE 表示SAX解析器继续解析,JSON_SAX_PARSE_STOP 表示中断解析
  • json_sax_cb_t: 调用者自己填写的回调函数,必须有值,返回 JSON_SAX_PARSE_STOP 表示中断解析并返回
  • json_sax_parser_t: 传给回调函数的值
  • array 是 array/object 类型+key 的层次结构,total表示当前分配了多少层次,index表示当前用了多少层次,即当前层为 array[index]
  • value 是当前层的值
  • json_sax_parse_choice_t: 参考 json_parse_choice_t 说明
int json_sax_parse_common(json_sax_parse_choice_t *choice);
static inline int json_sax_parse_str(char *str, size_t str_len, json_sax_cb_t cb);
static inline int json_sax_parse_file(const char *path, json_sax_cb_t cb);
  • json_sax_parse_common: sax解析通用接口,因为不需要保存json树结构,所以不需要使用内存池
  • json_sax_parse_str: 从字符串解析的快捷接口
  • json_sax_parse_file: 从文件边读边解析的快捷接口

开发板商城 天皓智联 相关内容mcu设备做测试