51c嵌入式~合集2

我自己的原文哦~    https://blog.51cto.com/whaosoft/13663720

一、EEPROM和Flash

 存储器分为两大类:RAM和ROM,本文主要讨论ROM。ROM最初不能编程,出厂什么内容就永远什么内容,不灵活。

    后来出现了PROM,可以自己写入一次,要是写错了,只能换一片,自认倒霉。人类文明不断进步,终于出现了可多次擦除写入的EPROM,每次擦除要把芯片拿到紫外线上照一下,想一下你往单片机上下了一个程序之后发现有个地方需要加一句话,为此你要把单片机放紫外灯下照半小时,然后才能再下一次,这么折腾一天也改不了几次。

    历史的车轮不断前进,伟大的EEPROM出现了,拯救了一大批程序员,终于可以随意的修改rom中的内容了。

    EEPROM的全称是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory。是相对于紫外擦除的rom来讲的。但是今天已经存在多种EEPROM的变种,变成了一类存储器的统称。

狭义的EEPROM

    这种rom的特点是可以随机访问和修改任何一个字节,可以往每个bit中写入0或者1。这是最传统的一种EEPROM,掉电后数据不丢失,可以保存100年,可以擦写100w次。具有较高的可靠性,但是电路复杂/成本也高。因此目前的EEPROM都是几十千字节到几百千字节的,绝少有超过512K的。

    例如我们常见的24C02:

广义的EEPROM

    flash属于广义的EEPROM,因为它也是电擦除的rom。但是为了区别于一般的按字节为单位的擦写的EEPROM,我们都叫它flash。

    flash做的改进就是擦除时不再以字节为单位,而是以块为单位,一次简化了电路,数据密度更高,降低了成本。上M的rom一般都是flash。如W25Q128JVSIQ:

flash分为nor flash和nand flash

nor flash:

    nor flash数据线和地址线分开,可以实现ram一样的随机寻址功能,可以读取任何一个字节。但是擦除仍要按块来擦。依然W25Q128JVSIQ

nand flash:

    nand flash同样是按块擦除,但是数据线和地址线复用,不能利用地址线随机寻址。读取只能按页来读取。(nandflash按块来擦除,按页来读,nor flash没有页),例如:W29N01HVSINA

由于nand flash引脚上复用,因此读取速度比nor flash慢一点,但是擦除和写入速度比nor flash快很多。nand flash内部电路更简单,因此数据密度大,体积小,成本也低。因此大容量的flash都是nand型的。小容量的2~12M的flash多是nor型的。

    使用寿命上,nor flash的擦除次数是nand的数倍。而且nand flash可以标记坏块,从而使软件跳过坏块。nor flash 一旦损坏便无法再用。

    因为nor flash可以进行字节寻址,所以程序可以在nor flash中运行。嵌入式系统多用一个小容量的nor flash存储引导代码,用一个大容量的nand flash存放文件系统和内核。

二、三极管实现LED闪光灯经典电路

 很多时候,我们是知其然,不知所以然。如果刨根问到底的话,可能这个问题会持续耽误完成这件事情的整个过程。但是不去深入了解的话,又没有掌握它的真理。

    所以我们需要在某个阶段去完成某件事情,不至于把这件事情搞砸。我们必须要有所突破,才能有所收获。

  今日分享的是用三极管实现一个闪光经典电路的设计方案,原理图设计如下图所示:

从这个原理图,我们知道用到了几个元器件:一个50K的滑动变阻器,一个电阻,一个电解电容,一个NPN型的8050三极管和一个LED灯。这个电路实现灯会一闪一闪的闪灯电路,会给我们一个直觉的视角。

    这个电路最特别最重要的就是这个三极管,我们发现很奇怪,三极管只接了发射极和集电极,基极没有接任何电路。那么发射极接电源的正极,集电极接电源的负极,刚开始我们可能以为这个电路没有办法工作。

    我们来分析一下这个电路,首先电位器电阻和电解电容肯定组成一个对电容的充电和放电电路。电源上电时,通过对电解电容充电,直到电解电容的电压跟输入电压接近,就保持电压的稳定了。那么这个时候,三极管将E-B反接就成为一个稳压二极管了,那么稳压二极管就会将电压稳定在一个值输出给LED供电了。

    那么此时,电解电容就给LED放点,放电后,前面的电位器和电阻又组成一个充电电路给电解电容充电。就这样循环下去,使得LED灯一闪一闪的。

    我们将用三极管实现一个闪灯经典电路设计方案的PCB Layout图设计如下:

  总结:通过利用三极管的PN结,利用三极管EB反向结组成一个稳压二极管来对LED供电。所以说这个电路非常经典,也给大家带来思考。

三、STM32代码远程升级之IAP编程

IAP是什么

    有时项目上需要远程升级单片机程序,此时需要接触到IAP编程。

    IAP即为In Application Programming,解释为在应用中编程,用户自己的程序在运行过程中对User Flash的部分区域进行烧写。即是一种对单片机Flash擦写的一种编程方案。

    通常情况下,一片STM32单片机的Flash只有一个用户程序,而IAP编程则是将单片机的Flash分成至少两大区域,一部分叫做bootloader区,一部分叫做app用户代码区,还可留出一部分区域为代码备份区。

IAP的应用场所

    通常情况下我们给STM32单片机烧录更新程序时是通过SWD、J-link或者通过设置BOOT引脚后,使用串口进行程序下载,这样的方式直接一次性将程序文件下载到单片机的Flash中,比较适合绝大部分的应用。

    但是当产品投入实际应用时,封装完成后在后期的使用过程中遇到某些程序上的bug或者是根据客户需求需要增加一些功能的时候,使用传统代码烧录的方法就可能需要拆除封装,而使用IAP编程在bootloader区提前写入与外部通信的接口用于升级单片机代码,使得我们不用对已完成包装的产品进行拆除既可以更新代码,这样既节约了成本,也更加方便快捷。

IAP编程的流程

    IAP编程将Flash区分成的两个区域,bootloader区和app用户代码区具有截然不同的功能。bootloader区,主要实现接收程序文件,并将该程序写于特定位置的Flash区域。而这里接收外部程序文件,就需要实时和外部通信了。

    STM32单片机与外部通信大多是通过自身的串口接收和发送数据,不过STM32单片机的串口可以外接多种通讯接口,例如422、485、GPRS及ESP8266等。即我们可以通过串口外接蓝牙模块、WiFi模块或者是其他网络模块,就可以实现远程的文件传送更新单片机程序了。

    app用户代码区则是主要实现我们所需要的功能操作,除此之外app用户代码区还需要实时检查代码运行情况,通过判断更新程序的标志位来判断是否需要升级程序。若是需要升级程序则进入bootloader区进行代码更新;若不需要则继续运行功能函数代码即可。

    因此IAP编程下的单片机运行流程如下图:

根据运行流程,我们可以总结出简单几条bootloader设计过程中需要注意的地方:

  • 精简、程序尽可能精简。在单片机Flash有限的情况下,bootloader代码占用Flash的空间越小,则APP程序代码就可占用更多,实现更多功能函数。
  • 标志位不受复位的影响。
  • Bootloader中尽量不使用中断。

 四、Modbus总结

现在国产的各种品牌PLC,比如台达、汇川、信捷等,这些PLC都是支持Modbus协议,也就是说,学会了Modbus协议,我们可以很轻松与这些PLC实现数据通信。

Modbus协议能够成为工业领域应用最广泛的协议,它必须具备以下几个特点:

1、免费:这个是最大的前提,任何产品都是一样,只有通过免费才能获取到前期最大的使用量。

2、简单:Modbus协议帧格式简单紧凑,用户容易理解,厂商容易集成。

3、接口:Modbus协议只是一种规约,属于应用层的协议,因此不仅可以应用在串口(485/232/422),也可以在以太网、光纤、蓝牙、无线上传输。

存储区分类

我一般介绍Modbus协议的时候,喜欢站在Modbus规约制定者的角度,结合一些事物来对比说明,这样对很多人来说,可能会更加容易理解。

假设没有Modbus协议,我们想要制定一个协议,我们首先要明确,协议的目的是为了数据传输,因此,为了更好地存储不同的数据类型,我们会将布尔和非布尔的数据分开存储,因此,就有了线圈和寄存器的概念。

线圈和寄存器,这个经常被很多人诟病,认为不应该这么翻译,感觉不容易理解。从电气角度来看,在电气控制回路中,一般都是靠接触器或中间继电器来实现控制,接触器或中继最终靠的是线圈的得电和失电来控制触点闭合和断开,因此用线圈表示布尔量;而寄存器在计算机中,就是用来存储数据的,因此非布尔的数据放在寄存器里。

这个可以跟PLC的存储区来进行对比,西门子的I/Q/M都是线圈,V/T/C/DB都是寄存器,三菱的X/Y都是线圈,D/W/H都是寄存器,欧姆龙的CIO是线圈,D/W/H是寄存器。

以西门子为例,虽然I和Q都表示线圈,但是他们的分工是不同的,I表示输入,Q表示输出,输入意味着该存储区里的值必须由外部设备接入,是只读的,输出表示输出结果给外部设备,是可读可写的。

因此,Modbus的线圈和寄存器应该也按照只读、读写来进一步细分,因此这就形成了Modbus的存储区,如下表所示:

序号

读写

存储类型

存储区名称

1

只读

线圈

输入线圈

2

读写

线圈

输出线圈

3

只读

寄存器

输入寄存器

4

读写

寄存器

保持寄存器

存储区代号

然而,上面表格里的存储区名称是一个全称,开发和使用中使用全称会比较麻烦,因此需要给他们取个别名,就像西门子的I/Q/M一样,这些都是西门子给存储区取的一个代号,所以Modbus也要给这些存储区取一个代号,干脆直接用数字吧,于是,就有了下面的规定:

存储区名称

存储区代号

输入线圈

1区

输出线圈

0区

输入寄存器

3区

保持寄存器

4区

这个其实就跟我们的姓名和小名一样,姓名是正式场合使用,日常场合,我们一般可以使用小名。

存储区范围

无论是什么存储区,都会有一个范围的限制,就像西门子的M区可能最大到8192,三菱的X区最大到2048,Modbus的每个存储区也应该规定一个范围,不能无限制使用。

Modbus是这么规定的,每个存储区的最大范围是65536,这个范围是很大的。

我们再以三菱的X区为例,如果最大范围是2048,那么意味着我们只能访问X0-X2047这些地址,我们这里说的X0、X2047,就是我们常说的PLC地址,那么这个地址是怎么组成的呢?它是由存储区编号加上一个地址索引组成,我们把这样的PLC地址,理解为绝对地址,后面的地址索引,理解为相对地址。

所谓绝对地址,就是我们仅仅通过一个地址名称,就能知道是什么存储区的第几个数据,而这个第几个,就是我们说的相对地址,因此绝对地址是唯一的,相对地址,每个存储区都有。

那么对于Modbus来说,我们的绝对地址和相对地址是怎么样的呢?

我们仍然遵从公式:绝对地址=区号+相对地址。

但是也会有一些不一样的地方,以保持型寄存器为例,第一个绝对地址是400001,这个地方不是400000,这个是由Modbus规约决定的,其它存储区也是类似的。
因此,Modbus存储区范围如下图所示:

正如上文所说,65536这个范围是很大的,但在实际使用中,我们一般用不了这么多地址,一般情况下,10000以内就已经足够我们使用了,因此,为了方便起见,我们有一种短的地址模型,如下图所示:

功能码

功能码这个概念,我们可以这么去理解,先回到我们的初衷,协议的目的是为了数据传输,也就是为了读取数据和写入数据,我们已经确定好4个存储区,存储不同的数据类型,那么接下来我们就要对这些存储区进行读写,那么可能会产生很多种不同的行为,比如读取输入线圈存储区、读取输出线圈存储区,这就是两种不同的行为,同样的,如果用读取输入线圈存储区、读取输出线圈存储区,会比较麻烦,那么我们干脆给每种形成指定一个代号,那么这种代号就是功能码。

我们再来探讨一下,究竟有多少种不同的行为呢?

读取和写入是2种行为,存储区有4个,但是我们知道输入线圈和输入寄存器是只读的,因此不能进行写入,除去这2种的话,应该会产生6种不同的行为,如下图所示:

行为序号

具体行为

1

读取输入线圈

2

读取输出线圈

3

读取输入寄存器

4

读取保持寄存器

5

写入输出线圈

6

写入保持寄存器

然而,Modbus规约将写入输出线圈和写入保持寄存器这2种行为,又进一步做了细分,包括写入单个和写入多个,因此原来的6种行为就变成了8种行为,同时给每种行为设置一个代号,就形成了下图所示的功能码列表:

功能码

功能说明

0x01

读取输出线圈

0x02

读取输入线圈

0x03

读取保持寄存器

0x04

读取输入寄存器

0x05

写入单个线圈

0x06

写入单个寄存器

0x0F

写入多个线圈

0x10

写入多个寄存器

Modbus规约中的功能码其实不止这8个,还有一些功能码是用于诊断或异常码,但是一般很少使用,这8种功能码是最主要的核心功能码。

协议分类

Modbus严格来说,是一个标准化的规约,而不是一个具体协议。我们常说的设备A和设备B之间通过Modbus协议来通信,这句话其实是不严谨的。

Modbus规约上有三种不同的协议,分别是ModbusRtu、ModbusAscii、ModbusTcp。

一般来说,ModbusRtu和ModbusAscii是运行在串口上的协议,ModbusTcp是运行是以太网上的协议,但是这并非绝对的,我们也可以将ModbusRtu、ModbusAscii运行在以太网或光纤上使用,同样的,在串口网络里,我们也可以使用ModbusTcp的协议,因为协议只是一种规范,并不限制通信介质。

报文格式

前面我们说了Modbus有三种不同的协议,分别是ModbusRtu、ModbusAscii、ModbusTcp,那么这三种协议的报文格式也是不同的,下面分别对这三种协议的报文格式进行说明:

1. ModbusRtu的报文格式如下:

第一部分:从站地址,占1个字节

第二部分:功能码,占1个字节

第三部分:数据部分,占N个字节

第四部分:校验部分,CRC校验,占2个字节

2. ModbusAscii的报文格式如下:

第一部分:开始字符(:)

第二部分:从站地址,占2个字节

第三部分:功能码,占2个字节

第四部分:数据部分,占N个字节

第五部分:校验部分,LRC校验,占2个字节

第六部分:结束字符(CR LF)

3. ModbusTcp的报文格式如下:

第一部分:事务处理标识符,占2个字节

第二部分:协议标识符,占2个字节

第三部分:长度,占2个字节

第四部分:单元标识符,占1个字节

第五部分:功能码,占1个字节

第六部分:数据部分,占N个字节

调试软件

也有很多调试软件可以进行仿真调试,因此我们可以在不购买任何硬件的情况下,就把Modbus协议学好。

Modbus 学习必须要配合相关的调试软件,可以达到事半功倍的效果,Modbus

学习必备的三大神器分别是 ModbusPoll、ModbusSlave 及 VSPD,ModbusPoll 软件主要用于仿真 Modbus主站或 Modbus 客户端,ModbusSlave 软件主要用于仿真 Modbus 从站或 Modbus 服务器,而 VSPD 全称 Configure Virtual Serial Port Driver,是用来给电脑创建虚拟串口使用的。

即使我们想要结合硬件,支持Modbus协议的设备也有很多,各种品牌PLC、各种品牌的仪表、各种温湿度传感器、流量计等都可以很好地支持Modbus协议。

五、Modbus通信协议

 本文旨在让您对Modbus有一个很直观的了解,总结关于Modbus相关的知识,非常适合初学的同学,同时如有错误,欢迎修改意见和建议。

什么是协议

    在了解什么是Modbus之前,我们先来看下什么是协议

    协议是一个汉语词汇,读音为xié yì,意思是共同计议,协商;经过谈判、协商而制定的共同承认、共同遵守的文件。

    简单地说,在我们的单片机之间互相通信,以及单片机和上位机通信中,规定了不同的内容规范,这个规范是通信的双方都需要遵守的,这样就可以实现两者的通信。

    而这个协议规范可以有很多种,来适应不同的设备以及通信要求等,我们常见的就有IIC SPI UART串口通信协议等等。而Modbus也是一个串行通信协议。

图片

什么是RS-485 RS-232

    我们在看Modbus的时候,经常会看到485串口,232串口,这些是什么呢?

    RS232,RS485是一种电平标准。

    数据在通信双方之间传输,本质是传输物理的电平 比方说传输5V的电压 -1V的电压信号,这些物理信号在传输过程中会受到很多干扰,比方说你传输一个5V的电压,到了接收端可能就变成了4.8V,并且通信的双方高低电平的参考电压可能不同。

    那么这个时候就需要一个电平标准,来判断多少V的电压是高电平 1,多少V的电压是低电平 0 这就诞生了 RS-485 RS-232

    RS232:是电子工业协会(Electronic Industries Association,EIA) 制定的异步传输标准接口,同时对应着电平标准和通信协议(时序),其电平标准:+3V~+15V对应0,-3V~-15V对应1。

  • 全双工
  • 逻辑1:-15V–5V
  • 逻辑0:+3V–+15V

图片

    RS485:RS485是一种串口接口标准,为了长距离传输采用差分方式传输,传输的是差分信号,抗干扰能力比RS232强很多。两线压差为-2~-6V表示0,两线压差为+2~+6V表示1。

  • 半双工
  • 逻辑1:+2V~+6V
  • 逻辑0:-2V~ -6V

    注意485的电平指的是485-A和485-B两根传输线,两线间的电压差。而不是传输线上的电压。

图片

    也就是RS-485电平标准确定传输过来的数据是0还是1,在此基础上,这些字节数据根据modbus通信协议来进行数据的交互传输。

    硬件层协议:解决0和1的可靠传输,常有RS232、RS485、CAN、IIC、SPI …

    软件层协议:解决传输目的,常有Modbus、TCP/IP、CANopen …

图片

Modbus协议说明

    Modbus诞生于1979年 莫迪康公司 后来被施耐德电气公司收购。Modbus提供通用语言用于彼此通信的设备和设备。

    Modbus已经成为工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式。Modbus作为目前工业领域应用最广泛的协议

    最简单的说,Modbus就是一个总线通信协议,像IIC SPI这种,但是他不依赖于硬件总线。

    Modbus之所以使用广泛,是有他的优点的:

  • Modbus协议标准开放、公开发表且无版权要求
  • Modbus协议支持多种电气接口,包括RS232、RS485、TCP/IP等,还可以在各种介质上传输,如双绞线、光纤、红外、无线等
  • Modbus协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络

    举一个简单的例子,我们常用的IIC通信协议,需要在物理上连接iic总线,然后加上拉电阻,规定好物理层的高低电平。

图片

   而 Modbus协议是一种应用层报文传输协议,协议本身并没有定义物理层,定义了控制器能够认识和使用的消息结构,不管它们是经过何种网络进行通信的。所以能够适应多种电气接口,因此使用非常广泛。

Modebus通信过程

    注意Modbus是一主多从的通信协议。

    Modbus通信中只有一个设备可以发送请求。其他从设备接收主机发送的数据来进行响应,从机是任何外围设备,如I/O传感器,阀门,网络驱动器,或其他测量类型的设备。从站处理信息和使用Modbus将其数据发送给主站。

    也就是说,不能Modbus同步进行通信,主机在同一时间内只能向一个从机发送请求,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。

    从机不会自己发送消息给主站,只能回复从主机发送的消息请求。

图片

 并且,Modbus并没有忙机制判断,比方说主机给从机发送命令, 从机没有收到或者正在处理其他东西,这时候就不能响应主机,因为modbus的总线只是传输数据,没有其他仲裁机制,所以需要通过软件的方式来判断是否正常接收。

举例

    现在,我们来探讨Modbus数据传输的方式,可以简单地理解成打电话。并且是单向通信的打电话。

    主机发送数据,首先需要从机的电话号码(区分每个从机,每个地址必须唯一),告诉从机打电话要干什么事情,然后是需要发送的内容,最后再问问从机,我说的话你都听清楚了没有呀,没有听错吧?

    然后从机这里,得到了主机打过来的电话,从机回复主机需要的内容,主机得到从机数据,这样就是一个主机到从机的通信过程

    就好比老师和你打电话,老师拨通了你的电话号,然后老师跟你说,小王呀,我这里需要你给我发东西,发的内容是上周的一周总结,你说好的,然后打开你电脑的文件夹,把你的周报发给老师,这就是一个通信过程。

Modbus存储区

    既然从机存储数据,那么肯定要有一个存储区,那就需要文件操作,我们都知道这文件可以分为只读(-r)和读写(-wr)两种类型。

    并且存储的数据类型可以分为 :布尔量 和 16位寄存器。

    布尔量比如IO口的电平高低,灯的开关状态等。

    16位寄存器比如 传感器的温度数据,存储的密码等。

    Modbus协议规定了4个存储区 分别是0 1 3 4区 其中1区和4区是可读可写,1区和3区是只读。

图片

    并且Modbus还给每个区都划分了地址范围 主机向从机获取数据时,只需要告诉从机数据的起始地址,还有获取多少字节的数据,从机就可以发送数据给主机

    Modbus数据模型规定了具体的地址范围,每一个从机,都有实际的物理存储,跟modbus的存储区相对应,主机读写从机的存储区,实际上就是对从机设备对应的实际存储空间进行读写。

图片

Modbus协议类型

    在上面我们已经说明了Modbus可以在各种介质上传输,那么他的传输模式也分为三种。包括ASCII、RTU(远程终端控制系统)、TCP三种报文类型

    串行端口存在多个版本的Modbus协议,而最常见的是下面四种:

  • Modbus-Rtu
  • Modbus-Ascii
  • Modbus-Tcp
  • ModbusPlus

    Modbus RTU是一种紧凑的,十六进制表示数据的方式,Modbus ASCII是一种采用Ascii码表示数据,并且每个8Bit 字节都作为两个ASCII字符发送的表示方式。

    RTU格式后续的命令/数据带有循环冗余校验的校验和,而ASCII格式采用纵向冗余校验的校验和。

    Modbus协议使用串口传输时可以选择RTU或ASCII模式,并规定了消息、数据结构、命令和应答方式并需要对数据进行校验。ASCII 模式采用LRC校验,RTU模式采用16 位CRC校验。通过以太网传输时使用TCP,这种模式不使用校验,因为TCP协议是一个面向连接的可靠协议。

图片

    当然常用的就是RTU模式,ASCII一般很少。

    举一个简单的例子,如果我们需要发送一个数字10 那么RTU模式下,只需要发送0x0A 总线上传输数据形式为:0000 1010

    而ASCII码模式则将数据1和0转为’1’和’0’,需要发送0x31(1) 0x30(0)两个字节数据。总线上传输数据形式为:0011 0001 0011 0000

Modbus-RTU协议

Modbus报文帧结构

    一个报文就是一帧数据,一个数据帧就一个报文:指的是一串完整的指令数据,本质就是一串数据。

    Modbus报文是指主机发送给从机的一帧数据,其中包含着从机的地址,主机想执行的操作,校验码等内容

    Modbus协议在串行链路上的报文格式如下所示:

图片

图片

  • 从机地址: 每个从机都有唯一地址,占用一个字节,范围0-255,其中有效范围是1-247,其中255是广播地址(广播就是对所有从机发送应答)
  • 功能码: 占用一个字节,功能码的意义就是,知道这个指令是干啥的,比如你可以查询从机的数据,也可以修改从机的数据,所以不同功能码对应不同功能.
  • 数据: 根据功能码不同,有不同功能,比方说功能码是查询从机的数据,这里就是查询数据的地址和查询字节数等。
  • 校验: 在数据传输过程中可能数据会发生错误,CRC检验检测接收的数据是否正确

Modbus功能码

    Modbus规定了多个功能,那么为了方便的使用这些功能,我们给每个功能都设定一个功能码,也就是指代码。

    Modbus协议同时规定了二十几种功能码,但是常用的只有8种,用于对存储区的读写,如下表所示:

图片

    当然我们用的最多的就是03和06 一个是读取数据,一个是修改数据。

CRC校验

    错误校验(CRC)域占用两个字节包含了一个16位的二进制值。CRC值由传输设备计算出来,然后附加到数据帧上,接收设备在接收数据时重新计算CRC值,然后与接收到的CRC域中的值进行比较,如果这两个值不相等,就发生了错误。

    比如主机发出01 06 00 01 00 17 98 04, 98 04 两个字节是校验位,那么从机接收到后要根据01 06 00 01 00 17 再计算CRC校验值,从机判断自己计算出来的CRC校验是否与接收的CRC校验(98 04主机计算的)相等,如果不相等那么说明数据传输有错误,这些数据就不能要。

    CRC校验流程:

1、预置一个16位寄存器为0FFFFH(全1),称之为CRC寄存器。

2 、把数据帧中的第一个字节的8位与CRC寄存器中的低字节进行异或运算,结果存回CRC寄存器。

3、将CRC寄存器向右移一位,最高位填以0,最低位移出并检测。

4 、如果最低位为0:重复第三步(下一次移位);如果最低位为1:将CRC寄存器与一个预设的固定值(0A001H)进行异或运算。

5、重复第三步和第四步直到8次移位。这样处理完了一个完整的八位。

6 、重复第2步到第5步来处理下一个八位,直到所有的字节处理结束。

7、最终CRC寄存器的值就是CRC的值。

    此外还有一种利用预设的表格计算CRC的方法,它的主要特点是计算速度快,但是表格需要较大的存储空间,该方法此处不在阐述

图片

   下面我们来看详细的发送和接收数据:

1、主机对从机读数据操作

    主机发送报文格式如下:

图片

    含义:

0x01:从机的地址

0x03:查询功能,读取从机寄存器的数据

0x00 0x01:代表读取的起始寄存器地址.说明从0x0001开始读取.

0x00 0x01:查询的寄存器数量为0x0001个 Modbus把数据存放在寄存器中,通过查询寄存器来得到不同变量的值,一个寄存器地址对应2字节数据; 寄存器地址对应着从机实际的存储地址。

    0xD5 0xCA:循环冗余校验 CRC

    从机回复报文格式如下:

图片

    含义:

0x01:从机的地址

0x03:查询功能,读取从机寄存器的数据

0x02:返回字节数为2个 一个寄存器2个字节

0x00 0x17:寄存器的值是0017

0xF8 0x4A:循环冗余校验 CRC

2、主机对从机写数据操作

    主机发送报文格式如下:

图片

    含义:

0x01:从机的地址

0x06:修改功能,修改从机寄存器的数据

0x00 0x01:代表修改的起始寄存器地址.说明修改0x0001-0x0003的存储内容

0x00 0x17:要修改的数据值为0017

0x98 0x04:循环冗余校验 CRC

    从机回复报文格式如下:

图片

    含义:

0x01:从机的地址

0x06:修改功能,修改从机寄存器的数据

0x00 0x01:代表修改的起始寄存器地址.说明是0x0000

0x00 0x17:修改的值为0017

0x98 0x04:循环冗余校验 CRC

    从机的回复和主机的发送是一样的,如果不一样说明出现了错误。

Modbus-ACSII协议

    在消息中的每个字节都作为两个ASCII字符发送。

    十六进制的0-F 分别对应ASCII字符的0…9,A…F

    也就是0x30~0x3A 0x41~0x46

   下方是ascii的报文帧

  • 1个字节起始位
  • 2个字节地址位
  • 2个字节功能位
  • n个数据位,最小的有效位先发送
  • LRC(纵向冗长检测) 注意校验方式不同
  • 结束符 \r \n

图片

    可以看到数据部分更加繁琐,正常我们使用都是用RTU格式,ASCII码格式有了解即可。

    总结:

    ModbusASCII有开始字符(和结束字符(CR LF),可以作为一帧数据开始和结束的标志,而ModbusRTU没有这样的标志,需要用时间间隔来判断一帧报文的开始和结束,协议规定的时间为3.5个字符周期,就是说一帧报文开始前,必须有大于3.5个字符周期的空闲时间,一帧报文结束后,也必须要有3.5个字符周期的空闲时间否则就会出现粘包的情况。

    注意:针对3.5个字符周期,其实是一个具体时间,但是这个时间跟波特率相关。

    在串口通信中,1个字符包括1位起始位、8位数据位(一般情况)、1位校验位(或者没有)、1位停止位(一般情况下),因此1个字符包括11个位,那么3.5个字符就是38.5个位,波特率表示的含义是每秒传输的二进制位的个位,因此如果是9600波特率,3.5个字符周期=/960038.5=0.00401s1000=4.01ms

Modbus-TCP协议

    我们首先看下Modbus-TCP和Modbus-ACSII的区别。

    Modbus-TCP并不需要从从机地址,而是需要MBAP报文头。

    并且不需要差错校验,因为TCP本身就具有校验差错的能力

    MBAP报文头格式如下:

    其中事务处理表示符合协议标识符我们正常使用设置为0即可 长度为6个字节 0x0006

    简单来说,也就是Modbus-TCP是在Modbus-ACSII的基础上,去掉校验,然后加上五个字节的0和一个06。

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

“电平转换”电路

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

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

图片

​编辑

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

图片

​编辑

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

图片

​编辑

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

    下面进一步分析:

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

图片

​编辑

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

​编辑

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管基本认识。

图片

​编辑

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

图片

​编辑

图1:光耦控制继电器

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

图片

​编辑

图2:输入输出隔离

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

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

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

图片

​编辑

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

图片

​编辑

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

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

图片

​编辑

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

图片

​编辑

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

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

图片

​编辑

图5:实验数据

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

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

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

图片

​编辑

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

图片

​编辑

图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)。

图片

​编辑

图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)。

图片

​编辑

图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)。

图片

​编辑

表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。

图片

​编辑

图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测试结果:

图片

​编辑

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

x86_64-Linux测试结果:

图片

​编辑

ldouble-x86_64测试结果:

图片

​编辑

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

ECOS测试结果:

图片

​编辑

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 表示中断解析 相关内容mcu设备做测试
  • 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: 从文件边读边解析的快捷接口
十一、霍尔效应传感器工作原理

什么是霍尔效应?

霍尔效应是由带电粒子(如电子)相应电场和磁场的相互作用引起的。更为形象生动的大家可以看下面这个霍尔效应原理动画图

​编辑

霍尔效应原理动图画来源于外网

霍尔效应原理

当导电板连接到带有电池的电路时,电流开始流动。电荷载体将沿着从板的一端到另一端的线性路径。电荷载流子的运动导致磁场的产生。当磁体靠近板放置时,电荷载流子的磁场会发生畸变。这扰乱了电荷载流子的直线流动。扰乱电荷载流子流动方向的力称为洛伦兹力。

由于电荷载流子磁场的畸变,带负电的电子将偏转到板的一侧,而带正电的空穴将偏转到板的另一侧。在板的两侧之间会产生一个电位差,称为霍尔电压,可以用仪表测量。

​编辑

​编辑

霍尔效应和洛伦兹力,蓝色箭头 B 表示垂直穿过导电板的磁场

霍尔效应原理表明:当将载流导体或半导体引入垂直磁场时,可以在电流路径成直角的位置测量电压。

霍尔电压表示为 VH 由公式给出:

​编辑

霍尔电压公式

  • VH 是导电板上的霍尔电压
  • I 是流过传感器的电流
  • B 是磁场强度
  • q 是电荷
  • n 是每单位体积的电荷载流子的数量
  • d 是传感器的厚度

霍尔效应传感器原理

当传感器周围的磁通密度超过某个预设阈值时,传感器会检测到它并产生称为霍尔电压 VH 的输出电压。具体的原理如下图所示。

霍尔效应传感器基本上由一块薄薄的矩形 p 型半导体材料组成,例如砷化镓 (GaAs)、锑化铟 (InSb) 或砷化铟 (InAs),其自身通过连续电流

​编辑

霍尔效应传感器原理图

霍尔效应传感器放置在磁场中时,磁通量线对半导体材料施加一个力,使载流子、电子和空穴偏转到半导体板的任一侧。电荷载流子的这种运动是它们穿过半导体材料时所经历的磁力的结果。

当这些电子和空穴向侧面移动时,由于这些电荷载流子的积累,在半导体材料的两侧之间会产生电位差。然后,电子通过半导体材料的运动受到与其成直角的外部磁场的影响,这种影响在扁平矩形材料中更大。

霍尔效应提供有关磁极类型和磁场大小的信息。例如,南极会使设备产生电压输出,而北极则不会产生任何影响。通常,霍尔效应传感器开关设计为在不存在磁场时处于“关闭”状态(开路状态)。它们只有在受到足够强度和极性的磁场时才会“打开”(闭路条件)。

霍尔效应传感器

在最简单的形式中,传感器作为模拟传感器工作,直接返回电压。在已知磁场的情况下,可以确定其与霍尔板的距离。使用传感器组,可以推断出磁体的相对位置。

通常,霍尔效应传感器与允许设备以数字(开/关)模式运行的电路相结合,并且在此配置中可能被称为开关。下图为包含两个磁铁的轮子经过霍尔效应传感器,可以明显的看到灯的变化。

图片

​编辑

包含两个磁铁的轮子经过霍尔效应传感器

霍尔效应传感器

大多数霍尔效应器件不能直接切换大型电气负载,因为它们的输出驱动能力非常小,大约为 10 到 20mA。对于大电流负载,在输出中添加一个集电极开路(电流吸收)NPN 晶体管。如下图所示:

该晶体管在其饱和区域中作为 NPN 灌电流开关工作,只要施加的磁通密度高于“ON”预设点的磁通密度,就会将输出端子短接到地。

输出开关晶体管可以是发射极开路晶体管、集电极开路晶体管配置或两者都提供推挽输出类型配置,该配置可以吸收足够的电流以直接驱动许多负载,包括继电器、电机、LED 和灯。

​编辑

典型的霍尔效应开关图

霍尔效应传感器提供线性或数字输出。线性(模拟)传感器的输出信号直接取自运算放大器的输出,输出电压与通过霍尔传感器的磁场成正比。该输出霍尔电压为:

​编辑

霍尔电压公式图

  • V H是以伏特为单位的霍尔电压
  • R H是霍尔效应系数
  • I是流过传感器的电流,单位为安培
  • t是传感器的厚度,单位为 mm
  • B是特斯拉的磁通量密度

线性或模拟传感器提供连续的电压输出,该输出随强磁场增加而随着弱磁场减少。在线性输出霍尔效应传感器中,随着磁场强度的增加,来自放大器的输出信号也会增加,直到它开始因施加电源的限制而饱和。

磁场的任何额外增加都不会对输出产生影响,但会使其更加饱和。

霍尔传感器测量方法--磁场的运动路径

霍尔效应传感器由磁场激活,在许多应用中,该设备可以通过连接到移动轴或设备的单个永磁体来操作。有许多不同类型的磁铁运动,例如“正面”、“侧身”、“推拉”或“推-推”等感应运动。

使用每种类型的配置,以确保最大灵敏度,磁通线必须始终垂直于设备的感应区域,并且必须具有正确的极性。

此外,为了确保线性,需要高场强磁铁,以便为所需的运动产生较大的场强变化。检测磁场有多种可能的运动路径,以下是使用单个磁体的两种更常见的传感配置:正面检测侧向检测

1、霍尔传感器测量方法--正面检测

顾名思义,“正面检测”要求磁场垂直于霍尔效应传感设备,并且为了检测,它直接朝向有源面接近传感器。一种“正面”的方法。

​编辑

这种正面方法会产生一个输出信号VH,它在线性器件中表示磁场强度,即磁通量密度,它是距霍尔效应传感器的距离的函数。距离越近,磁场越强,输出电压越大,反之亦然。

线性器件还可以区分正磁场和负磁场。非线性装置可以在远离磁铁的预设气隙距离处触发输出“ON”,以指示位置检测。

2、霍尔传感器测量方法--侧身检测

第二种传感配置是“横向检测”。这需要在霍尔效应元件的表面上横向移动磁铁。

当磁场在固定气隙距离内穿过霍尔元件的表面时,侧向或滑过检测对于检测磁场的存在很有用,例如,计算旋转磁铁或电机的旋转速度。

​编辑

根据磁场通过传感器零场中心线时的位置,可以产生表示正输出和负输出的线性输出电压。这允许定向运动检测,它可以是垂直的也可以是水平的。

霍尔传感器--位置检测器

根据设备的类型(无论是数字的还是线性的),有许多不同的方法可以将霍尔效应传感器连接到电气和电子电路。一个非常简单且易于构建的实例如下图:

​编辑

位置检测器

当不存在磁场(0 )时,正面位置检测器将“关闭”。当永磁体南极(正高斯)垂直移动到霍尔效应传感器的有效区域时,设备将“打开”并点亮 LED。一旦切换“ON”,霍尔效应传感器将保持“ON”。

霍尔传感器优缺点

优点

霍尔效应传感器可以用作电子开关

  • 这种开关的成本低于机械开关,而且更可靠
  • 它的工作频率最高可达 100 kHz。
  • 不会受到触点反弹的影响,因为使用了具有滞后功能的固态开关而不是机械触点。
  • 由于传感器采用密封包装,因此不会受到环境污染物的影响。因此,它可以在恶劣的条件下使用

对于线性传感器(用于磁场强度测量),霍尔效应传感器:

  • 可以测量范围广泛的磁场
  • 可以测量北极或南极磁场
  • 可以是平的

缺点

霍尔效应传感器提供的测量精度远低于磁通门磁力计或基于磁阻的传感器。此外,霍尔效应传感器漂移显着,需要补偿。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值