单片机

在传感器使用中,我们常常需要对传感器数据进行各种整理,让应用获得更好的效果,以下介绍几种常用的简单处理方法:

加权平滑:平滑和均衡传感器数据,减小偶然数据突变的影响。
抽取突变:去除静态和缓慢变化的数据背景,强调瞬间变化。
简单移动平均线:保留数据流最近的K个数据,取平均值。

下面,具体介绍一下这3种处理方法。

1、加权平滑

使用算法如下:

(新值) = (旧值)*(1 - a) + X * a其中a为设置的权值,X为最新数据,程序实现如下:

float ALPHA = 0.1f;
public void onSensorChanged(SensorEvent event){
x = event.values[0];
y = event.values[1];
z = event.values[2];
mLowPassX = lowPass(x,mLowPassX);
mLowPassY = lowPass(x,mLowPassY);
mLowPassZ = lowPass(x,mLowPassZ);
}
private float lowPass(float current,float last){
return last * (1.0f - ALPHA) + current * ALPHA;
}

2、抽取突变

此算法采用上面加权平滑的逆算法,实现代码如下:

public void onSensorChanged(SensorEvent event){
final float ALPHA = 0.8;gravity[0] = ALPHA * gravity[0] + (1-ALPHA) * event.values[0];
gravity[1] = ALPHA * gravity[1] + (1-ALPHA) * event.values[1];
gravity[2] = ALPHA * gravity[2] + (1-ALPHA) * event.values[2];filteredValues[0] = event.values[0] - gravity[0];
filteredValues[1] = event.values[1] - gravity[1];
filteredValues[2] = event.values[2] - gravity[2];
}

3、简单移动平均线

这个算法,保留传感器数据流中最近的K个数据,返回它们的平均值。k表示平均“窗口”的大小,实现代码如下:

public class MovingAverage{
private float circularBuffer[]; //保存传感器最近的K个数据
private float avg; //返回到传感器平均值
private float sum; //数值中传感器数据的和
private float circularIndex; //传感器数据数组节点位置
private int count;public MovingAverage(int k){
circularBuffer = new float[k];
count= 0;
circularIndex = 0;
avg = 0;
sum = 0;
}
public float getValue(){
return arg;
}
public long getCount(){
return count;
}
private void primeBuffer(float val){
for(int i=0;i<circularbuffer.length;++i){
 circularBuffer[i] = val;
sum += val;
}
}
private int nextIndex(int curIndex){
if(curIndex + 1 >= circularBuffer.length){
return 0;
}
return curIndex + 1;
}
public void pushValue(float x){
if(0 == count++){
primeBuffer(x);
}
float lastValue = circularBuffer[circularIndex];
circularBuffer[circularIndex] = x; //更新窗口中传感器数据
sum -= lastValue; //更新窗口中传感器数据和
sum += x;
avg = sum / circularBuffer.length; //计算得传感器平均值
circularIndex = nextIndex(circularIndex);
}
}

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 22

1、8bit MCU 通用I/O 结构图

端口模块如下图(79系列图):

“中颖8bit

2、相关设置寄存器及注意事项:

端口控制寄存器

“中颖8bit

“中颖8bit

PxCR寄存器控制I/O输入输出状态设置。

当寄存器设置成输入模式时,Px寄存器读取的是端口电平状态。

当寄存器设置成输出模式时,Px寄存器读取的是数据寄存器的值。

如有未使用到的I/O,需要设置输出固定电平以免I/O浮动电平带来的漏电流。

端口上拉电阻控制寄存器

“中颖8bit

“中颖8bit

端口数据寄存器

“中颖8bit

“中颖8bit

79系列单片机 Px 寄存器都在位寻址区(例如:80H,88H等),都可以进行位寻址操作。

在初始化设施PxCR(输出状态)前,请优先设置Px寄存器,避免WDT,,OVL等复位带来的端口电平变化。

当PxCR寄存器设置成输入状态时,操作读取对应Px,是读取对应引脚电平状态。

当PxCR寄存器设置成输出状态时,根据指令来分别对应 读取的是寄存器还是引脚电平。

端口寄存器读-改-写指令举例:

ANL P0, #立即数 ; P0->立即数&(与)P0->P0

ORL P0, A ; P0->A|(或)P0->P0

INC P0 ; P0->P0+1->P0

CPL P0.0 ; P0.0->P0.0~(取反)->P0.0

引脚电平读取指令举例:

MOV A,P0 ; P0->A

MOV R0, P0 ; P0->R0

不管端口是否共享为其它功能,对端口写操作都是针对端口数据寄存器。

当第二功能有冲突时,按照端口共享表格中的优先级来决定输出功能。

通常I/O的优先级是低于其他功能的。

以下图为例,当P0.6引脚上BUZ功能和LED功能同时选中时,引脚输出LED波形。

“中颖8bit

当允许端口复用为其它功能时,用户可以修改PxCR﹑PxPCR,但在复用的其它功能被禁止前,这些操作不会影响端口状态。

当允许端口复用为其它功能时,任何对端口的读写操作只会影响到数据寄存器的值,端口引脚值保持不变,直到复用的其它功能关闭。

3、 I/O开漏模式介绍:

“中颖8bit

在I/O章节有选择N沟道开漏功能的寄存器时,可以实现I/O的N沟道开漏功能(注意上图红线处,IO管脚的电压不得超过VDD+0.3V电压)

如果I/O章节没有该选项的寄存器,但是又有TWI通讯功能,那么在TWI功能开启时,引脚自动切换成N沟道开沟。关闭TWI功能,自动切换回普通I/O。

芯片的电源输入端建议加去耦电路,防止VDD端出现瞬间的高压引入导致的电路损坏。

4、施密特及TTL功能介绍:

施密特功能介绍:

施密特输入特性是输入高电平阈值为0.8VDD,输入低电平阈值为0.2VDD。

VDD=5V举例,输入高电平>=4V,端口读取的电平为高,输入低电平<=1V,端口读取的电平为低。相对应的引脚是否具有施密特功能,请查询电气特性章节(输入高电压2和输入低电压2中注明有施密特功能的引脚,例如INT0-4,T3-T5等)。普通I/O不具有施密特功能。

TTL功能介绍:

TTL电平输入特性是

1) 输入高电平阈值为0.25VDD+0.8,输入低电平阈值为0.15VDD(VDD=2.7V~4.5V)

以VDD=3.3V举例,输入高电平>=1.625V,端口读取的电平为高,输入低电平<=0.495V,端口读取的电平为低。

2)输入高电平阈值为2.0V,输入低电平阈值为0.8V(VDD=4.5V~5.5V)

以VDD=5 V举例,输入高电平>=2V,端口读取的电平为高,输入低电平<=0.8V,端口读取的电平为低。

选择TTL电平功能可与VDD电压为3.3V的WIFI模块直接通过以Uart或者TWI的方式通讯,又或者直接接收外部中断信号(INT0-4),不需要外加电平转换电路。(芯片是否有TTL功能请查询I/O章节及电气特性章节)

端口输入模式选择寄存器如下(TTL和CMOS选择)

“中颖8bit

“中颖8bit

*:CPU在任何情况下,读取端口数据寄存器(P0,P1……),其输入高电平阈值为0.7VDD,输入低电平阈值为0.3VDD(CMOS逻辑,无施密特);该控制位控制的是其他功能输入的逻辑电平状态,例如:INT0 - 4,RXD,SDA等数字电平输入。

注意:TTL电平特性,详情请见规格书电气特性章节。

来源:中颖电子
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 177

Microchip Technology Inc.(资深应用工程技术顾问Bob Martin)

当前,在从搅拌机到牙刷的一切设备都连接到云端的狂热浪潮中,物联网领域正由低成本的集成32位单片机RF模块控制,这些模块为少量传感器输入提供小尺寸解决方案。

Wi-Fi®、NB IoT和Bluetooth®的通信协议栈非常适合32位领域,同时还能提高计算能力以确保RF通道安全。但是,随着传感器通道数量的增加或更多偏远地点所需的功耗降低,会增加系统设计的复杂性,此时按如下方式添加额外的8位MCU可以增加价值,如图1所示:

真正的5V IO支持和传感器聚合

工业环境仍以5V电源生态系统为主,虽然有完全支持5V电压的32位MCU,但大多数集成32位MCU/RF为仅支持3.3V电源域的器件。在5V电源域中,允许通过GPIO更高效的8位MCU直接连接到5V电源传感器、开关触点和执行器,而无需添加多个电平转换器或调整模拟电压输入来满足3.3V电压要求。

现在,只需对8位MCU和32位MCU/RF模块之间的通信通道进行电平转换/调整操作。在32位MCU模块具有5V耐压输入的某些情况下,可能根本不需要电平转换,也许只需要一些串联电阻隔离。对于还需要电流隔离的情况,通过减少需要保护系统RF部分的专用IC的数量可节省更多成本。

远程安装通常需要更高的容错能力,这可能会导致使用多个传感器或执行器控制来减轻现场故障带来的影响。冗余传感器接口连接意味着,引脚有限的32位MCU/RF模块上存在更多输入/输出引脚分配问题。8位MCU往往会提供巨大的接口引脚密度,从而允许在前端的传感器阵列中添加一些智能容错功能。它不需要利用机器学习算法来确定三个温度传感器中是否有一个发生故障。这些类型的决策可以通过更快的事件响应在本地做出。

“工业传感器集成
工业传感器集成 © xiaoliangge - stock.adobe.com

图1——8位/32位系统分区

“物联网领域里的8位单片机:用传统芯片简化高级架构接口"

系统分区

使用外部8位MCU与大多数传感器接口,可以轻松地将已知的工作模拟/数字前端快速接入不同的RF模块后端。集成32位MCU/RF模块通常随附大量示例应用程序,这些应用程序展示出连接到云是举手之劳,无需考虑供应商。应用程序示例中可能未明确说明如何与标准I2C或SPI总线之外的传感器或执行器接口。经过验证的已知传感器/控制前端具有一致且定义明确的接口,通过最大限度地简化移植过程,还可以更灵活地选择合适的RF模块。一旦新RF模块上的新物理层支持两个MCU之间的协议层,新系统的集成工作便已基本完成。现在,可以将开发工作的重点放在新RF通道的正确实现上。

具有容错热插拔接口的松耦合系统是工业或远程环境设置中的一项有益特性。有时,整体系统交换无法避免,但最理想的选择是尽量减少对已知可靠系统的整体更改。这种松耦合还可让受信任的已知RF平台支持扩展的系统需求,而无需从头开始。保留您信任的部分,改进有所不足的部分。

“系统分区和架构
系统分区和架构 © myboys.me - stock.adobe.com

智能电源管理

遗憾的是,转向更小型IC栅极技术需要在速度和静态电流泄漏之间做出权衡。新制程节点中的栅极氧化层厚度即将达到以原子数而非纳米数计算的最佳厚度。8位MCU领域由更大的制程工艺主导,这些工艺可实现更出色的静态泄漏率。由于最佳低功耗管理技术从定义上来说就是同时切断电源,因此添加智能低功耗管理器件可以改善低功耗运行。一些8位MCU器件的工作电流运行在标准32.768 kHz晶振下,而此晶振会在32位RF模块上泄漏电流。这种方法现在增加了基于精确时间的电源管理系统,还拥有为电池充电和监视电池运行状况的能力。32位RF模块(特别是基于Wi-Fi的单元)的有功电流可以达到数百毫安。如果电池组电量即将耗尽,可能无法维持连接到网络所需的启动和传输电流。

基于8位MCU的电源管理系统现在可以使用特殊的唤醒命令来唤醒主RF模块,此命令可降低所需的电流需量,从而使RF模块以最佳相序保持在线。现在,这种特殊唤醒用例可以使用降低TX功率的方法来最终建立到网络的连接。8位MCU电源管理系统可以定期监视峰值启动电流和电压下降,并在每个唤醒周期提交这些数据。适当的云机器学习引擎可以利用这些数据来更好地分析电池系统并预测故障。

“低功耗远程应用
低功耗远程应用 © aquatarkus - stock.adobe.com

编程模型/MCU复杂性

在过去几年中,32位MCU/RF模块的编程难度显著降低。其中一些模块提供基于Arduino的支持,这肯定有助于加快开发速度,但当涉及到更多客户传感器、电源管理或其他外设接口时,编程难度会提高。Arduino支持代码十分庞大,但在许多情况下并不完整,并且在专业领域仍然存在一些信任问题。此外,IC供应商本身也提供支持,但归根结底,无法避免在裸金属层集成32位RF模块带来的额外复杂性。所有基于32位的控制寄存器对于一些控制位或状态位来说似乎都太大了,尽管转向32位时确实会发生这种情况,但在目前,并非所有人都能在像0x23AA123C这样的外设控制值中直观地挑出错误的位。

8位MCU编程模型以8位区块的形式呈现常见的接口,有时会扩展到16位以便用于定时器寄存器。除了能够更轻松地调试位域外,8位MCU上的外设集往往更易于理解,因为它们不需要涉及或提供更复杂的降低功耗或总线接口同步功能。8位MCU中的时钟树也更易于理解,即使在时钟树中提供PLL,操作也更加简单。然而,这正是使用8位MCU配套器件的全部意义所在,提供低功耗、低成本、智能但不能流畅支持物联网的器件,以处理所有后台、电源管理和繁琐的任务。

Microchip提供了几个8位MCU器件的示例,包括PIC18-Q41系列和AVR DB系列。这两个系列均提供大量模拟功能,包括片上运算放大器和多电平电压GPIO,减少了对额外的外部模拟元件和电平转换器的需求。

虽然可用的多核32位MCU/RF模块的数量在不断增加,但在物联网环境中,设计稳健的低功耗边缘节点时,添加8位MCU仍然是可行的选择。它们以小型封装形式提供电源和传感器管理,因此仍然在32位物联网领域发挥着重要作用。

围观 31

一、定义

1、上拉就是将不确定的信号通过一个电阻嵌位在高电平!“电阻同时起限流作用”!下拉同理!

2、上拉是对器件注入电流,下拉是输出电流

3、弱强只是上拉电阻的阻值不同,没有什么严格区分

4、对于非集电极(或漏极)开路输出型电路(如普通门电路)提升电流和电压的能力是有限的,上拉电阻的功能主要是为集电极开路输出型电路输出电流通道。

二、上拉电阻作用

1、一般作单键触发使用时,如果IC本身没有内接电阻,为了使单键维持在不被触发的状态或是触发后回到原状态,必须在IC外部另接一电阻。

2、数字电路有三种状态:高电平、低电平、和高阻状态,有些应用场合不希望出现高阻状态,可以通过上拉电阻或下拉电阻的方式使处于稳定状态,具体视设计要求而定!

4、上拉电阻是用来解决总线驱动能力不足时提供电流的。一般说法是拉电流,下拉电阻是用来吸收电流的,也就是我们通常所说的灌电流

5、接电阻就是为了防止输入端悬空

6、减弱外部电流对芯片产生的干扰

7、保护cmos内的保护二极管,一般电流不大于10mA

8、通过上拉或下拉来增加或减小驱动电流

9、改变电平的电位,常用在TTL-CMOS匹配

10、在引脚悬空时有确定的状态

11、增加高电平输出时的驱动能力。

12、为OC门提供电流

三、上拉电阻应用原则

1、当TTL电路驱动COMS电路时,如果TTL电路输出的高电平低于COMS电路的 高电平(一般为3。5V),这时就需要在TTL的输出端接上拉电阻,以提高输出高电平的值。

2、OC门电路“必须加上拉电阻,才能使用”。

3、为加大输出引脚的驱动能力,有的单片机管脚上也常使用上拉电阻。

4、在COMS芯片上,为了防止静电造成损坏,不用的管脚不能悬空,一般接上拉电阻产生降低输入阻抗,提供泄荷通路。

5、芯片的管脚加上拉电阻来提高输出电平,从而提高芯片输入信号的噪声容限增强抗干扰能力。

6、提高总线的抗电磁干扰能力。管脚悬空就比较容易接受外界的电磁干扰。

7、长线传输中电阻不匹配容易引起反射波干扰,加上下拉电阻是电阻匹配,有效的抑制反射波干扰。

8、在数字电路中不用的输入脚都要接固定电平,通过1k电阻接高电平或接地。

四、上拉电阻阻值选择原则

1、从节约功耗及芯片的灌电流能力考虑应当足够大;电阻大,电流小。

2、从确保足够的驱动电流考虑应当足够小;电阻小,电流大。

3、对于高速电路,过大的上拉电阻可能边沿变平缓。综合考虑

以上三点,通常在1k到10k之间选取。对下拉电阻也有类似道理。

对上拉电阻和下拉电阻的选择应“结合开关管特性和下级电路的输入特性进行设定,主要需要考虑以下几个因素”:

1、驱动能力与功耗的平衡。以上拉电阻为例,一般地说,上拉电阻越小,驱动能力越强,但功耗越大,设计是应注意两者之间的均衡。

2、下级电路的驱动需求。同样以上拉电阻为例,当输出高电平时,开关管断开,上拉电阻应适当选择以能够向下级电路提供足够的电流。

3、高低电平的设定。不同电路的高低电平的门槛电平会有不同,电阻应适当设定以确保能输出正确的电平。以上拉电阻为例,当输出低电平时,开关管导通,上拉电阻和开关管导通电阻分压值应确保在零电平门槛之下。

4、频率特性。以上拉电阻为例,上拉电阻和开关管漏源级之间的电容和下级电路之间的输入电容会形成“RC延迟”,电阻越大,延迟越大。上拉电阻的设定应考虑电路在这方面的需求。

下拉电阻的设定的原则和上拉电阻是一样的。

OC门输出高电平时是一个高阻态,其上拉电流要由上拉电阻来提供,设输入端每端口不大于100uA,设输出口驱动电流约500uA,标准工作电压是5V,输入口的高低电平门限为0.8V(低于此值为低电平);2V(高电平门限值)。

选上拉电阻时:500uA x 8.4K= 4.2即选大于8.4K时输出端能下拉至0.8V以下,此为 阻值,再小就拉不下来了。如果输出口驱动电流较大,则阻值可减小,保证下拉时能低于0.8V即可。当输出高电平时,忽略管子的漏电流,两输入口需200uA,200uA x15K=3V即上拉电阻压降为3V,输出口可达到2V,此阻值为 阻值,再大就拉不到2V了。选10K可用。【 压降/ 电流、 压降/ 电流】

COMS门的可参考74HC系列设计时管子的漏电流不可忽略,IO口实际电流在不同电平下也是不同的,上述仅仅是原理,一句话概括为:“输出高电平时要喂饱后面的输入口,输出低电平不要把输出口喂撑了”,否则多余的电流喂给了级联的输入口,高于低电平门限值就不可靠了。

此外,还应注意以下几点:

A、要看输出口驱动的是什么器件,如果该器件需要高电压的话,而输出口的输出电压又不够,就需要加上拉电阻。

B、如果有上拉电阻那它的端口在默认值为高电平,你要控制它必须用低电平才能控制如三态门电路三极管的集电极,或二极管正极去控制把上拉电阻的电流拉下来成为低电平。

C、尤其用在接口电路中,为了得到确定的电平,一般采用这种方法,以保证正确的电路状态,以免发生意外,比如,在电机控制中,逆变桥上下桥臂不能直通,如果它们都用同一个单片机来驱动,必须设置初始状态,防止直通!

驱动尽量用灌电流。

电阻在选用时,选用经过计算后与标准值相近的一个!

P0为什么要上拉电阻原因有:

1、 P0口片内无上拉电阻

2、 P0为I/O口工作状态时,上方FET被关断,从而输出脚浮空,因此P0用于输出线时为开漏输出。

3、 由于片内无上拉电阻,上方FET又被关断,P0输出1时无法拉升端口电平。

P0是双向口,其它P1,P2,P3是准双向口。准双向口是因为在读外部数据时要先“准备”一下,为什么要准备一下呢?

单片机在读准双向口的端口时,先应给端口锁存器赋1,目的是使FET关断,不至于因片内FET导通使端口钳制在低电平。

上下拉一般选10k!

来源:畅学单片机
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 230

对于平常日用的一些产品,产品在进行设计时就会考虑这个问题,客户只是简单的利用插头进行电源的连接,所以一般采用反插错接头,这是种简单,低价而有效的方法。

但是,对于产品处于工厂生产阶段,可能不便采用防差错接头,这可能就会造成由于生产人员的疏忽造成反接,带来损失。

所以给电路增加防接反电路有时还是有必要的,尽管增加了成本。

下面就说说常用的防接反电路:

1、最简单的在电路中串入一只二极管

“实用的单片机接反电路,再也不怕电源接反了"

优点:电路简单,成本较低。适用于小电流,对成本要求比较严的产品。

缺点:由于二极管的PN结在导通时,存在一个压降,一般在0.7V以下。这个压降就导致这种电路不适合应用在电流较大的电路中,如果电路有10A的电流,那么二极管的功耗就是0.7*10=7W,发热量还是很可观的。在结构紧凑空间有限的产品中,对产品的稳定性或人的使用感受上影响还是比较大的。

2、对于上面上面提到的二极管的压降问题,有没有办法克服呢?看下面的电路。

“实用的单片机接反电路,再也不怕电源接反了"

上面的防接反电路采用了一个保险丝和一个反向并联的二极管,电源极性正确,电路正常工作时,由于负载的存在电流较小,二极管处于反向阻断状态,保险丝不会被熔断。

当电源接反时,二极管导通,此时的电流比较大,就会将保险丝熔断,从而切断电源的供给,起到保护负载的作用。

优点:保险丝的压降很小,不存在发热问题。成本不高。

缺点:一旦接反需要更换保险丝,操作比较麻烦。

3、正接反接都可正常工作的电路:

“实用的单片机接反电路,再也不怕电源接反了"

优点:输入端无论怎样接,电路都可以正常工作。
缺点:存在两个二极管的压降。适用于小电流电路。

4、N沟道增强型场效应管防接反电路

由场效应管制作工艺决定了,场效应管的导通电阻比较小。是现在很常用的开关器件,特别是在大功率的场合。以TO-252封装的IRFR1205为例,其主要参数如下:Vdss=55V,Id=44A,Rds=0.027欧姆;可以看到其导通电阻只有27毫欧。

下图就是一个用N沟道场效应管构成的防接反电路

“实用的单片机接反电路,再也不怕电源接反了"

这个电路的最大一个特点就是场效应管的D极和S极的接法。通常我们在使用N沟道的增强型的MOS管时,一般是电流由D极进入而从S极流出。应用在这个电路中时则正好相反。

曾经在一个论坛中看到过这个电路,发布这个电路的楼主被众多网友痛批。说是DS之间存在一个二极管根本没法实现。楼主没有注明场效应管的管脚名称,由于存在一个应用场效应管的惯性思维,导致楼主蒙冤。

其实场效应管只要在G和S极之间建立一个合适的电压就会完全导通。导通之后D和S之间就像是一个开关闭合了,电流是从D到S或S到D都一样的电阻。

在电源极性正确时,电流起始时通过场效应管的稳压管导通,S极电压接近0V。

两个电阻分压后,为G提供电压,使场效应管导通,因为其导通阻值很小,就把场效应管内部的二极管给替代了。

电源反接时,场效应管内的二极管未到击穿电压不导通。分压电阻无电流流过无法提供G极电压,也不导通。从而起到保护作用。

对于电路中并联在分压电阻上的稳压二极管,因为场效应管的输入电阻是很高的,

是一个压控型器件,G极电压要控制在20V内,过高的电压脉冲会导致G极的击穿,这个稳压二极管就是起一个保护场效应管防止击穿的作用。

对于并联在分压电阻上的电容,有一个软启动的作用。在电流开始流过的瞬间,电容充电,G极的电压是逐步建立起来的。

对于并联在场效应管D与S之间的阻容串联电路,我感觉还是值得商榷的。阻容串联电路一般用作脉冲吸收或延时。用在这里要视负载的情况而定,加了或许反而不好。毕竟这会导致在电源在反接的时候会有一个短暂的导通脉冲。

也可以用P沟道的场效应管,只是要将器件串在正极的输入端。这里不再描述。

来源:小麦大叔
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 119

前阵子一朋友使用单片机与某外设进行通信时,外设返回的是一堆格式如下的数据:

AA AA 04 80 02 00 02 7B AA AA 04 80 02 00 08 75 AA AA 04 80 02 00 9B E2 AA AA 04 80 02 00 F6 87 AA AA 04 80 02 00 EC 91

其中 AA AA 04 80 02 是数据校验头,后面三位是有效数据,问我怎么从外设不断返回的数据中取出有效的数据。

对于这种问题最容易想到的就是使用一个标志位用于标志当前正解析到一帧数据的第几位,然后判断当前接收的数据是否与校验数据一致,如果一致则将标志位加一,否则将标志位置0重新判断,使用这种方法解析数据的代码如下:

if(flag == 0)
{
	if(tempData == 0xAA)
		flag++;
	else
		flag = 0;
}
else if(flag == 1)
{
	if(tempData == 0xAA)
		flag++;
	else
		flag = 0;
}
else if(flag == 2)
{
	if(tempData == 0x04)
		flag++;
	else
		flag = 0;
}
else if(flag == 3)
{
	if(tempData == 0x80)
		flag++;
	else
		flag = 0;
}
else if(flag == 4)
{
	if(tempData == 0x02)
		flag++;
	else
		flag = 0;
}
else if(flag == 5 || flag == 6 || flag == 7)
{
	data[flag-5] = tempData;
	flag = (flag == 7) ? 0 : flag+1;
}

使用上述方法是最容易想到的也是最简单的方法了,百度了一下基本上也都是使用类似的方法进行数据解析,但是使用这种方法有如下几个缺点:

1、 大量使用了判断,容易导致出现逻辑混乱

2、 代码重复率高,抽象程度低。从上述代码可以看到一大堆代码仅仅是判断的数据不同,其他代码都完全一致

3、 代码可复用性差。写好的代码无法用在其他类似的外设上,如果有多个外设就需要编写多份类似的代码

4、 可扩展性低。如果外设还有一个数据校验尾需要校验或者数据校验头发生改变,就需要再次写多个判断重新用于校验,无法在原有的代码上进行扩展

5、 容易出现误判

对此,这里提出了一种新的解决方案,可以通用与所有类似的数据解析,原理如下:

使用一个固定容量的队列用来缓存接收到的数据,队列容量等于一帧数据的大小,每来一个数据就将数据往队列里面加,当完整接收到一帧数据时此时队列中的全部数据也就是一帧完整的数据,因此只需要判断队列是否是数据校验头,队列尾是否是数据校验尾就可以得知当前是否已经接收到了一帧完整的数据,然后在将数据从队列中取出即可。原理图如下:

每来一个数据就往队列里面加:

“单片机接收数据帧帧头帧尾校验数据解析"

当接收到一帧完整数据时队列头和数据校验头重合:

“单片机接收数据帧帧头帧尾校验数据解析"

此时只需要从队列中取出有效数据即可。

如果有数据尾校验,仅仅只需要添加一个校验尾即可,如下图所示:

“单片机接收数据帧帧头帧尾校验数据解析"

好,分析结束,开始编码。

首先需要一个队列,为了保证通用性,队列底层使用类似于双向链表的实现(当然也可以使用数组实现),需要封装的结构有队列容量、队列大小、队头节点和队尾节点,需要实现的操作有队列初始化、数据入队、数据出队、清空队列和释放队列,具体代码如下:

/* queue.h */
 
#ifndef _QUEUE_H_
#define _QUEUE_H_
 
#ifndef NULL
#define	NULL	((void *)0)
#endif
 
typedef unsigned char uint8;
 
/* 队列节点 */
typedef struct Node
{
	uint8 data;
	struct Node *pre_node;
	struct Node *next_node;
} Node;
 
/* 队列结构 */
typedef struct Queue
{
	uint8 capacity;     // 队列总容量
	uint8 size;         // 当前队列大小
	Node *front;        // 队列头节点
	Node *back;         // 队列尾节点
} Queue;
 
/* 初始化一个队列 */
Queue *init_queue(uint8 _capacity);
/* 数据入队 */
uint8 en_queue(Queue *_queue, uint8 _data);
/* 数据出队 */
uint8 de_queue(Queue *_queue);
/* 清空队列 */
void clear_queue(Queue *_queue);
/* 释放队列 */
void release_queue(Queue *_queue);
 
#endif
/* queue.c */
 
#include <stdlib.h>
#include "parser.h"
 
/**
 * 初始化一个队列
 *
 * @_capacity: 队列总容量
 */
Queue *init_queue(uint8 _capacity)
{
	Queue *queue = (Queue *)malloc(sizeof(Queue));
	queue->capacity = _capacity;
	queue->size = 0;
	return queue;
}
 
/**
 * 数据入队
 *
 * @_queue: 队列
 * @_data: 数据
 **/
uint8 en_queue(Queue *_queue, uint8 _data)
{
	if(_queue->size < _queue->capacity)
	{
		Node *node = (Node *)malloc(sizeof(Node));
		node->data = _data;
		node->next_node = NULL;
 
        if(_queue->size == 0)
        {
            node->pre_node = NULL;
            _queue->back = node;
            _queue->front = _queue->back;
        }
        else
        {
            node->pre_node = _queue->back;
 
            _queue->back->next_node = node;
            _queue->back = _queue->back->next_node;
        }
		_queue->size++;
	}
	else
	{
		Node *temp_node = _queue->front->next_node;
		_queue->front->pre_node = _queue->back;
		_queue->back->next_node = _queue->front;
		_queue->back = _queue->back->next_node;
		_queue->back->data = _data;
		_queue->back->next_node = NULL;
		_queue->front = temp_node;
	}
	return _queue->size-1;
}
 
/**
 * 数据出队
 *
 * @_queue: 队列
 *
 * @return: 出队的数据
 */
uint8 de_queue(Queue *_queue)
{
    uint8 old_data = 0;
 
    if(_queue->size > 0)
    {
        old_data = _queue->front->data;
        if(_queue->size == 1)
        {
            free(_queue->front);
            _queue->front = NULL;
            _queue->back = NULL;
        }
        else
        {
            _queue->front = _queue->front->next_node;
            free(_queue->front->pre_node);
            _queue->front->pre_node = NULL;
        }
        _queue->size--;
    }
    return old_data;
}
 
/**
 * 清空队列
 *
 * @_queue: 队列
 */
void clear_queue(Queue *_queue)
{
    while(_queue->size > 0)
    {
        de_queue(_queue);
    }
}
 
/**
 * 释放队列
 *
 * @_queue: 队列
 */
void release_queue(Queue *_queue)
{
    clear_queue(_queue);
    free(_queue);
    _queue = NULL;
}

其次是解析器,需要封装的结构有解析数据队列、数据校验头、数据校验尾、解析结果以及指向解析结果的指针,需要实现的操作有解析器初始化、添加数据解析、获取解析结果、重置解析器和释放解析器,具体代码如下:

/* parser.h */
 
#ifndef _PARSER_H_
#define _PARSER_H_
 
#include "queue.h"
 
typedef enum
{
    RESULT_FALSE,
    RESULT_TRUE
} ParserResult;
 
/* 解析器结构 */
typedef struct DataParser
{
    Queue *parser_queue;			// 数据解析队列
    Node *resule_pointer;			// 解析结果数据指针
    uint8 *data_header;				// 数据校验头指针
    uint8 header_size;				// 数据校验头大小
    uint8 *data_footer;				// 数据校验尾指针
    uint8 footer_size;				// 数据校验尾大小
    uint8 result_size;				// 解析数据大小
    ParserResult parserResult;		// 解析结果
} DataParser;
 
/* 初始化一个解析器 */
DataParser *parser_init(uint8 *_data_header, uint8 _header_size, uint8 *_data_footer, uint8 _foot_size, uint8 _data_frame_size);
/* 将数据添加到解析器中进行解析 */
ParserResult parser_put_data(DataParser *_parser, uint8 _data);
/* 解析成功后从解析器中取出解析结果 */
int parser_get_data(DataParser *_parser, uint8 _index);
/* 重置解析器 */
void parser_reset(DataParser *_parser);
/* 释放解析器 */
void parser_release(DataParser *_parser);
 
#endif
/* parser.c */
 
#include <stdlib.h>
#include "parser.h"
 
/**
 * 初始化一个解析器
 *
 * @_data_header: 数据头指针
 * @_header_size: 数据头大小
 * @_data_footer: 数据尾指针
 * @_foot_size: 数据尾大小
 * @_data_frame_size: 一帧完整数据的大小
 *
 * @return: 解析器
 */
DataParser *parser_init(uint8 *_data_header, uint8 _header_size, uint8 *_data_footer, uint8 _foot_size, uint8 _data_frame_size)
{
    if((_header_size+_foot_size) > _data_frame_size || (_header_size+_foot_size) == 0)
        return NULL;
 
    DataParser *parser = (DataParser *)malloc(sizeof(DataParser));
    parser->parser_queue = init_queue(_data_frame_size);
    parser->resule_pointer = NULL;
    parser->data_header = _data_header;
    parser->header_size = _header_size;
	parser->data_footer = _data_footer;
	parser->footer_size = _foot_size;
    parser->result_size = _data_frame_size - parser->header_size - parser->footer_size;
    parser->parserResult = RESULT_FALSE;
 
    while(_data_frame_size-- > 0)
    {
        en_queue(parser->parser_queue, 0);
    }
 
    return parser;
}
 
/**
 * 将数据添加到解析器中进行解析
 *
 * @_parser: 解析器
 * @_data: 要解析的数据
 *
 * @return: 当前解析结果,返回 RESULT_TRUE 代表成功解析出一帧数据
 */
ParserResult parser_put_data(DataParser *_parser, uint8 _data)
{
    uint8 i;
    Node *node;
 
	if(_parser == NULL)
		return RESULT_FALSE;
 
    en_queue(_parser->parser_queue, _data);
 
	/* 校验数据尾 */
	node = _parser->parser_queue->back;
	for(i = _parser->footer_size; i > 0; i--)
	{
		if(node->data != _parser->data_footer[i-1])
            goto DATA_FRAME_FALSE;
        node = node->pre_node;
	}
 
	/* 校验数据头 */
    node = _parser->parser_queue->front;
    for(i = 0; i < _parser->header_size; i++)
    {
        if(node->data != _parser->data_header[i])
            goto DATA_FRAME_FALSE;
        node = node->next_node;
    }
 
    if(_parser->resule_pointer == NULL && _parser->result_size > 0)
        _parser->resule_pointer = node;
    if(_parser->parserResult != RESULT_TRUE)
    	_parser->parserResult = RESULT_TRUE;
    return _parser->parserResult;
 
DATA_FRAME_FALSE:
    if(_parser->resule_pointer != NULL)
        _parser->resule_pointer = NULL;
    if(_parser->parserResult != RESULT_FALSE)
        _parser->parserResult = RESULT_FALSE;
    return _parser->parserResult;
 
}
 
/**
 * 解析成功后从解析器中取出解析结果
 *
 * @_parser: 解析器
 * @_index: 解析结果集合中的第 _index 个数据
 *
 * @return: 获取解析成功的数据,返回 -1 代表数据获取失败
 */
int parser_get_data(DataParser *_parser, uint8 _index)
{
    Node *node;
    if(_parser == NULL
	|| _parser->parserResult != RESULT_TRUE
    || _index >= _parser->result_size
    || _parser->resule_pointer == NULL)
        return -1;
    node = _parser->resule_pointer;
    while(_index > 0)
    {
        node = node->next_node;
        _index--;
    }
    return node->data;
}
 
/**
 * 重置解析器
 *
 * @_parser: 解析器
 */
void parser_reset(DataParser *_parser)
{
	uint8 _data_frame_size;
 
	if(_parser == NULL)
		return;
 
	_data_frame_size = _parser->parser_queue->size;
	while(_data_frame_size-- > 0)
    {
        en_queue(_parser->parser_queue, 0);
    }
    _parser->resule_pointer = NULL;
    _parser->parserResult = RESULT_FALSE;
}
 
/**
 * 释放解析器
 *
 * @_parser: 解析器
 */
void parser_release(DataParser *_parser)
{
	if(_parser == NULL)
		return;
    release_queue(_parser->parser_queue);
    free(_parser);
    _parser = NULL;
}

接下来编写测试代码测试一下:

/* main.c */
 
#include <stdio.h>
#include "parser.h"
 
int main()
{
    uint8 i;
    // 数据头
    uint8 data_header[] = {0xAA, 0xAA, 0x04, 0x80, 0x02};
    // 要解析的数据,测试用
    uint8 data[] = {
        0xAA, 0xAA, 0x04, 0x80, 0x02, 0x00, 0x02, 0x7B, 0xAA, 0xAA, 0x04, 0x80,
        0x02, 0x00, 0x08, 0x75, 0xAA, 0xAA, 0x04, 0x80, 0x02, 0x00, 0x9B, 0xE2,
        0xAA, 0xAA, 0x04, 0x80, 0x02, 0x00, 0xF6, 0x87, 0xAA, 0xAA, 0x04, 0x80,
        0x02, 0x00, 0xEC, 0x91, 0xAA, 0xAA, 0x04, 0x80, 0x02, 0x01, 0x15, 0x67,
        0xAA, 0xAA, 0x04, 0x80, 0x02, 0x01, 0x49, 0x33, 0xAA, 0xAA, 0x04, 0x80,
        0x02, 0x00, 0xE7, 0x96, 0xAA, 0xAA, 0x04, 0x80, 0x02, 0x00, 0x68, 0x15,
        0xAA, 0xAA, 0x04, 0x80, 0x02, 0x00, 0x3C, 0x41, 0xAA, 0xAA, 0x04, 0x80,
        0x02, 0x00, 0x66, 0x17, 0xAA, 0xAA, 0x04, 0x80, 0x02, 0x00, 0xA5, 0xD8,
        0xAA, 0xAA, 0x04, 0x80, 0x02, 0x01, 0x26, 0x56, 0xAA, 0xAA, 0x04, 0x80,
        0x02, 0x01, 0x73, 0x09, 0xAA, 0xAA, 0x04, 0x80, 0x02, 0x01, 0x64, 0x18,
        0xAA, 0xAA, 0x04, 0x80, 0x02, 0x01, 0x8B, 0xF1, 0xAA, 0xAA, 0x04, 0x80,
        0x02, 0x01, 0xC6, 0xB6, 0xAA, 0xAA, 0x04, 0x80, 0x02, 0x01, 0x7B, 0x01,
        0xAA, 0xAA, 0x04, 0x80, 0x02, 0x00, 0xCB, 0xB2, 0xAA, 0xAA, 0x04, 0x80,
        0x02, 0x00, 0x2C, 0x51, 0xAA, 0xAA, 0x04, 0x80, 0x02, 0xFF, 0xE5, 0x99
    };
 
    /**
     * 初始化一个解析器
     * 第一个参数是数据头
     * 第二个参数是数据头长度
     * 第三个参数是数据尾指针
     * 第四个参数是数据尾大小
     * 第五个参数是一整帧数据的大小
     */
    DataParser *data_parser = parser_init(data_header, sizeof(data_header), NULL, 0, 8);
 
    // 将要解析的数据逐个取出,添加到解析器中
    for(i = 0; i < sizeof(data); i++)
    {
        // 解析数据,返回 RESULT_TRUE 代表成功解析出一组数据
        if(parser_put_data(data_parser, data[i]) == RESULT_TRUE)
        {
            printf("成功解析出一帧数据...\n");
 
            /* 一位一位取出解析后的数据 */
            printf("第一个数据是:0x%x\n", parser_get_data(data_parser, 0));
            printf("第二个数据是:0x%x\n", parser_get_data(data_parser, 1));
            printf("第三个数据是:0x%x\n\n\n", parser_get_data(data_parser, 2));
        }
    }
 
    // 当不再需要解析器时,应该把解析器释放掉,回收内存,避免造成内存泄漏
    parser_release(data_parser);
 
    return 0;
}

测试结果如下:

“单片机接收数据帧帧头帧尾校验数据解析"

从上面可以看出,解析的结果与目标一致。

github地址:https://github.com/528787067/DataFrameParser
————————————————
版权声明:本文为CSDN博主「XR528787067」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/XR528787067/article/details/52822377
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 29

对于嵌入式系统,如果没有运行RTOS,那么程序开发中的主函数main()需要通过某种机制使其永远愉快的运行下去,它没有终点。如果想从main函数中退出,具体干什么是由所使用的C语言编译器决定的。

一、问题提出

今天在单片机led模块定义函数中看到一个有趣的问题。提问者在进行基本的C51编程实验,编写了一个简单的C51程序如下:

#include <REGX51.H>

void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}

void main(void) {
    test(1);
}

程序执行完之后,可以看到实验板上的有两个LED被点亮,另外六个居然微微发亮。

“单片机的程序结束后都干嘛去了?"

如果在主程序中,增加一个无限循环:while(1); ,则电路板上的就不再会出现“微微点亮”的现象了。

#include <REGX51.H>

void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}

void main(void) {
    test(1);
    while(1);
}

“单片机的程序结束后都干嘛去了?"

上面两种情况的区别,在于第二个程序中主循环 main()函数始终没有退出,而第一个程序,main()函数退出了。似乎前面LED微微点亮 应该与主函数退出之后,单片机都干了些啥有关系。

那么就剩下一个问题:对于普通的嵌入式系统,C语言编程中main()函数退出之后,程序去哪儿了?

二、程序去哪儿了?

从上面提问者书写的代码来看,应该是一位C51的爱好者,使用的是C51的编译器,在一款C51开发板上愉快的进行实验。他一开始没有安装嵌入式程序开发的惯例 在主程序void main(void)中利用无限循环将程序控制在主程序函数中,就出现了前面实验结果中令人迷惑的情况。

“注:他是一个胆大心细的人,观察还挺仔细的。”

2.1 盘古开天辟地

对于C语言编程来说,所有的用户程序世界是从主程序main()开始的。给用户程序开天辟地的任务是由一小段盘古代码STARTUP.A51。

51单片机程序执行流程(STARTUP.A51管理Main函数的执行)

下面截取了STARTUP.A51 代码的一段,可以看到盘古在单片机RESET之后做了点准备工作(初始化全局变量、堆栈指针)之后,就直接跳转至:?C_START

NAME    ?C_STARTUP

?C_C51STARTUP   SEGMENT   CODE
?STACK          SEGMENT   IDATA

                RSEG    ?STACK
                DS      1

                EXTRN CODE (?C_START)
                PUBLIC  ?C_STARTUP

                CSEG    AT      0
?C_STARTUP:     LJMP    STARTUP1

                RSEG    ?C_C51STARTUP

STARTUP1:

IF IDATALEN <> 0
                MOV     R0,#IDATALEN - 1
                CLR     A
IDATALOOP:      MOV     @R0,A
                DJNZ    R0,IDATALOOP
ENDIF

IF XDATALEN <> 0
                MOV     DPTR,#XDATASTART
                MOV     R7,#LOW (XDATALEN)
  IF (LOW (XDATALEN)) <> 0
                MOV     R6,#(HIGH (XDATALEN)) +1
  ELSE
                MOV     R6,#HIGH (XDATALEN)
  ENDIF
                CLR     A
XDATALOOP:      MOVX    @DPTR,A
                INC     DPTR
                DJNZ    R7,XDATALOOP
                DJNZ    R6,XDATALOOP
ENDIF

IF PPAGEENABLE <> 0
                MOV     PPAGE_SFR,#PPAGE
ENDIF

IF PDATALEN <> 0
                MOV     R0,#LOW (PDATASTART)
                MOV     R7,#LOW (PDATALEN)
                CLR     A
PDATALOOP:      MOVX    @R0,A
                INC     R0
                DJNZ    R7,PDATALOOP
ENDIF

IF IBPSTACK <> 0
EXTRN DATA (?C_IBP)

                MOV     ?C_IBP,#LOW IBPSTACKTOP
ENDIF

IF XBPSTACK <> 0
EXTRN DATA (?C_XBP)

                MOV     ?C_XBP,#HIGH XBPSTACKTOP
                MOV     ?C_XBP+1,#LOW XBPSTACKTOP
ENDIF

IF PBPSTACK <> 0
EXTRN DATA (?C_PBP)
                MOV     ?C_PBP,#LOW PBPSTACKTOP
ENDIF

                MOV     SP,#?STACK-1
                LJMP    ?C_START

                END
上面的代码也被博文51单片机程序执行流程(STARTUP.A51)中进行逐步调试跟踪验证过:

“单片机的程序结束后都干嘛去了?"

2.2 世界尽头

由于进入main()函数是长跳转,所以main函数是不会正常返回到启动程序STARTUP.A51,那么程序去哪了?

在博文单片机C语言while(1)的问题中作者对于KEIL编译器和PIC的MAPLAB编译器对于main函数的最后时光进行了反汇编查看。

Keil编译器

在main函数的最后,程序增加了一下几行代码:

MOV R0, #0x7F
CLR A
MOV @R0, A
DJNZ R0, (3)
MOV SP, #0x0C
LJMP main
这几条语句,前4条,是将我们单片机的内存的前128个地址清零,第5条,是定义堆栈,第6条,是将程序重新跳转到main函数的首行进行执行。

MAPLAB编译器

PIC 单片机语言程序进行跟踪,发现main() 函数最后一条语句为 reset,也就是单片机直接复位,这是 MAPLAB编译器根据 PIC 单片机特点增加的复位语句。

总结

对于嵌入式系统,如果没有运行RTOS,那么程序开发中的主函数(main())需要通过某种机制使其永远愉快的运行下去,它没有终点。如果想从main函数中退出,具体干什么是由所使用的C语言编译器决定的。

来源:TsinghuaJoking公众号
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 49

Cortex-M0处理器允许两种形式的中断请求:电平触发和脉冲输入。

电平触发是外设的中断请求有持续的电平信号,若电平信号在处理器从ISR返回之前没有被取消,则中断返回后将再次触发已经服务过的中断。

脉冲中断是在信号的上升沿同步采样的中断信号,为了确保NVIC检测到中断,外设必须维持中断信号至少一个时钟周期,在此期间,NVIC检测脉冲和锁存中断。后续的脉冲可以将暂挂状态加到活动中断中,使状态为中断活动且挂起。然而,在有限周期内发生的多个脉冲只登记作为中断调度的单个事件。

哪些中断是电平触发的,哪些是脉冲触发的,具体选择哪一种是根据芯片设计来决定,不过大多数的外设使用电平触发中断输出。

1. 电平触发和脉冲输入

Cortex-M0处理器锁存所有中断,外围中断成为等待其中一个原因是:

  • NVIC检测到中断信号被置位并且对应的中断不是active

  • NVIC检测到中断信号的上升沿

  • 软件写入相应的中断集挂起寄存器位

挂起的中断将一直挂起,直到下列情况之一发生:

  • 处理器为中断进入ISR,这将改变中断的状态等待活跃:

对于电平触发型中断,当处理器从ISR返回时,NVIC采样中断信号。如果中断信号仍然有效,表示中断的状态更改为pending,这可能会导致处理器立即重新进入ISR。否则,中断的状态将变为非活动状态。

对于脉冲触发型中断,NVIC继续监视中断信号,如果这样触发时,中断状态变为挂起和活动状态。在这种情况下,当处理器从ISR返回中断状态时,中断状态变为挂起状态可能会导致处理器立即重新进入ISR。如果中断信号不是脉冲而处理器是在ISR,当处理器从ISR返回中断状态变为非活动状态。

  • 软件写入相应的中断清除寄存器位。

对于电平触发型中断,如果中断信号仍然有效,则中断的状态中断不会改变。否则,中断的状态将变为非活动状态。

对于脉冲中断,中断状态变为:

  • inactive,如果状态是pending

  • active,如果状态是活动的和挂起的

2. 中断处理

当中断事件发生时,由于外设连接到了NVIC上,中断信号就会得到确认。在处理器执行中断服务并且没有清除外设的中断的信号以前,该信号会保持高电平。

在NVIC内部,当检测到有中断发生时,该中断的挂起状态会被置位,当处理器接受该中断并且开始执行中断服务后,挂起状态就会被清除。

“单片机中断电平触发和脉冲输入"

针对脉冲输入的中断请求,这种情况下,在中断得到服务之前,挂起状态寄存器将会一直保持该请求。

“单片机中断电平触发和脉冲输入"

如果中断请求没有立即执行,并且在确认之前被软件清除了,处理器将会忽略掉本次请求,并且不会执行中断处理。

如果在软件清除挂起状态时,外设仍然保持着中断请求,挂起状态寄存器还会立即生成。

3. 中断等待

通常情况下,处理器的中断等待时间为16个周期,这个等待时间从中断确认的处理器时钟周期开始,一直到中断处理开始执行结束。

计算中断等待需具备以下前提:

  • 该中断使能并没有PRIMASK或者其他正在执行的异常处理所屏蔽

  • 存储器系统没有任何等待状态,在中断处理、压栈、取向量表或者中断处理开始时取指都会用到总线传输,如果存储器系统需要等待,那么总线传输时产生的等待状态则可能使得中断延迟。

下面几种情况可能会导致不同的中断等待:

  • 中断的咬尾连锁,如果一个中断返回时立即产生另外一个中断请求,处理器就会跳过出栈和压栈时间,减少了中断等待时间。

  • 延迟到达,如果中断发生时,另外一个低优先级中断正在进行压栈处理,由于延迟到达,高优先级的中断就会立即执行,这样会导致高优先级的中断等待时间减少。

4. 异常屏蔽寄存器PRIMASK

有些对时间敏感的应用,需要在短时间内禁止响应所有的中断,对于这种应用,处理器不是直接使用中断使能、禁止控制寄存器来禁止所有中断再恢复,而是一个单独的特殊寄存器 - PRIMASK,通过它可以屏蔽掉除了NMI和HardFault异常的其他的所有的中断和系统异常。

PRIMASK寄存器只有1位有效,并且在复位后默认为0。该寄存器为0时,所有的中断和异常都处于允许状态,设置为1后,只有NMI和HardFault处于使能状态。

MOVS R0, #0x1         ;  //中断#2
MSR PRIMASK , R0       ;  //将R0的值送到PRIMASK

NVIC编程提示软件使用CPSIE i和CPSID i指令来启用和禁用中断。

CPSIE i                 ;  //清除 PRIMASK(使能中断)
CPSID i                 ;  //设置 PRIMASK(不响应中断)

CMSIS设备驱动库提供了C语言的实现函数,用户可以直接使用函数来设置和清除PRIMASK寄存器:

void __disable_irq(void) //不响应中断
void __enable_irq(void)  //启用中断

在对时间敏感的程序完成后,应该清除PRIMASK。要不然即使在中断处理中使用\_\_disable\_irq()函数,处理器将停止接受新的中断请求。主要原因是PRIMASK寄存器和Xpsr是相互独立的,因此异常返回不会影响中断屏蔽状态。

5. NVIC使用提示

确保软件使用正确对齐的寄存器访问,处理器不支持对 NVIC 寄存器的未对齐访问。

即使中断被禁用,它也可以进入挂起状态。

禁用中断只能防止处理器处理中断。

在对中断向量表重定义之前,必须包含所有的异常中断,例如 NMI、HardFault 和外设中断等。

素材来源 | 极术社区
直接转载来源:
strongerHuang
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 38

文章英文版出处:

https://www.eejournal.com/article/what-more-8-bit-microcontrollers/

以下全文转载自EEWeb主编Max Maxfield在EEJournal上发表的文章:

我们周围不断充斥着有关 8 位、16 位、32 位和 64 位处理器的讨论,我不确定如今还有多少年轻工程师知道第一个商用微处理器——英特尔4004。它是一台 4 位机器(部件编号虽然叫4004,却没有一语双关的意思)。

顺便说一句,如果您想了解更多关于 4004 以及当代微处理器的发展史,我衷心推荐我的朋友Steve Leibson在 EEJournal 上撰写的专栏文章:《我们是否真的知道是谁发明了微处理器?》和《哪个才是第一个微处理器?》以及《微处理器50岁生日快乐》的第1部分和第2部分。

微处理器(µP)也称为微处理器单元(MPU)。早期的 MPU 仅包含一个中央处理单元(CPU)。随着时间的推移,添加了其他功能,如高速缓存、浮点运算单元(FPU)以及存储管理单元(MMU)等。关键是:除了任何高速缓存以及 FPU 和 MMU 之类的东西,微处理器不包含任何内部存储器或外设。相比之下,单片机(µC),也称为微控制器单元(MCU),包含闪存等非易失性存储器,SRAM 等易失性存储器,计数器、定时器、模数转换器(ADC)等外设以及UART、I2C 和 SPI 等通信接口。从本质上讲,单片机是一种小型独立计算机,存在于包含自己的程序的单个硅芯片上。它一上电,程序就开始执行。这就解释了为什么单片机出现在嵌入式系统中,以及为什么嵌入式系统随处可见。(您可以通过我的专栏文章《CPU、MPU、MCU和GPU的常见问题解答有哪些?》,了解有关微处理器和单片机之间区别的更多信息)。

单片机的历史与微处理器的历史一样不甚明朗。第一个单片机是哪个?是日本人在1970 年代初为汽车制造的 4 位器件,还是 TI 工程师 Gary Boone 和 Michael Cochran设计并于 1974 年首次亮相的 4 位 TMS 1000?谈到 8 位 MCU,1976 年面市的英特尔 8048(又名 MCS-48)是第一个吗?我不知道。我所知道的是,早期 8 位 MCU 中最著名的可能是1980 年上市的 8051(又名 MCS-51),其指令集架构(ISA)由 John H. Wharton 构想。神奇的是,由8051演变出的新产品至今仍然强劲。

顺便说一句,John曾经告诉我,当他还是一名在英特尔工作的年轻工程师时,他经常和他的主管出去吃午饭。有一天,他们听说要在午餐时间开会讨论一些事情。他们不确定会议的重点是什么,只知道提供免费三明治。这个会议原来是 8051 的启动会。真的是完全从一张白纸开始的。会议结束后,John 饱餐了一顿,回到办公桌前,勾勒出 8051 的架构(功能单元和总线等)和 ISA。

如今,有众多的单片机能够实现我们的各类设想,而PIC®单片机和AVR®单片机是真正有存在感的两个系列。第一个 8 位 PIC MCU由 General Instruments 于 1975 年开发。我不确定它的具体发展史,但 PIC MCU现在是Microchip Technology Inc.的产品。同时,最初的 8 位 AVR 架构是由 Alf-Egil Bogen 和 Vegard Wollan 还是挪威理工学院(NTH)的学生时提出的。该技术随后被 Atmel 收购,该公司于 1996 年发布了 AVR 系列的第一批成员。 Atmel已于 2016 年被 Microchip收购。

我们有什么方法可以量化这种“存在感”?我曾经同Microchip 8位单片机产品部的营销副总裁 Greg Robinson 和资深公共关系经理Brian Thorsen 交流过。如下图所示,在撰写本文时,Microchip 的 8 位 MCU 市场份额为 32%,遥遥领先!如果我在Microchip负责这些产品的话,我肯定要笑逐颜开了。

“Gartner
Gartner 2021 市场份额报告中的全球 8 位单片机市场份额

Greg 告诉我,Microchip将持续创新,其中不乏许多8位新产品。例如,在 2022 年第二季度,Microchip推出了5 个全新系列,约 65 款器件,拥有丰富的片上模拟和其他独立于内核的外设。

除了传统的单芯片系统(即Microchip MCU是板上唯一的处理器)外,8位处理器作为系统管理IC和协处理器的作用也越来越大,尺寸、空间、低功耗和寿命等方方面面的特征都很重要。这在很大程度上是因为我们看到分布式智能在物联网边缘节点、汽车安全、工业控制系统、医疗电子和家用电子等应用领域的急剧增长。即使是最先进的 5G 系统,通常也可以从将某些任务分配给更小的 8 位处理器中受益,从而释放更高级处理器来发挥它们的魔力并做它们最擅长的事情。

Greg说:“尽管听起来很奇怪,很多 8 位的增长是由 32 位的增长推动的,其中 32 位处理器正在将人机界面(HMI)功能和内务管理任务等传递给8 位处理器。此外,8 位器件越来越多地用作协处理器,执行诸如获取传感器读数和预处理此传感器数据等任务,然后将其传递给更高级处理器。”

我们谈到的主题之一是当前的供应链问题。在我们交谈之前,我没有意识到Microchip 出货的 95% 的 8 位产品是内部制造的,而且除了在美国亚利桑那州Tempe、俄勒冈州Gresham和科罗拉多州Colorado Springs设有晶圆厂,他们还拥有自己的封装、制造和测试厂。

尽管如此,但由于贸易战和全球新冠疫情共同引发的大风暴,在过去 18 至 24 个月内产生了大量需求,因此仍然存在供货短缺的情况。Greg 说:“提高产量也不是嘴上一说这么简单。”Microchip 总裁兼首席执行官 Ganesh Moorthy 曾表示,他预计短缺将持续到 2023 年,但 Microchip 已承诺在未来几年会投入 10 亿美元,用于继续推出新产品,同时扩大产能以满足对现有器件的需求。

“2022年第二季度新产品预告"
2022年第二季度新产品预告

ADCC 代表“ADC 计算”, 它是模拟和数字功能的混合体。片上模拟功能(包括 8 位、10 位和 12 位 ADC)可以使用图形工具轻松配置。其他选项包括具有相关可编程增益放大器(PGA)的 ADC,从而无需使用外部 PGA,以及具有上下文/排序功能的 ADC。其他功能包括片上比较器、数模转换器(DAC)、斜坡发生器、温度传感器、电压基准、过零检测和运算放大器(运放)。

考虑下面介绍的运算放大器示例。传统方法是使用外部运算放大器(左)。片上运算放大器(右)的优势包括节省电路板上的空间、减少物料清单(BOM)以及能够在程序控制下动态更改软件中的增益和其他特性(如果您要测量多个信号,每个信号都需要不同的运算放大器参数,这很有用)。

“带有内部运算放大器的
带有内部运算放大器的 PIC®和AVR®单片机

独立于内核的外设(CIP)背后的概念是,外设可以在内核处于“打盹”模式或处理更重要的任务时自行执行任务。例如,CIP 可以在内核进入休眠状态时从传感器获取读数,然后对结果进行累积、求平均值和/或过滤。稍后,当内核唤醒时,外设可以准备好其预处理的数据并等待着。

“使用独立于内核的外设创建自定义外设"
使用独立于内核的外设创建自定义外设

当 CIP 组合在一起创建自定义外设或“超级外设”时,事情开始变得非常有趣。一个很好的例子如下所示。这涉及希望使用串行总线通信协议控制一堆 LED 的应用。

“将
将 CIP 组合在一起以创建“超级外设”或“超级模块”

指定“哪个 LED”和“什么颜色”涉及一个相当复杂的信号,并且可能需要发送大量数据。这通常需要一个高速 32 位 MCU。然而,通过使用少数 CIP 外设——定时器、SPI、PWM 和一些使用 CLC(可配置逻辑单元)实现的逻辑——可以在 8 位 PIC 单片机上实现该算法。(与 PIC MCU的 CLC 不同,可以使用可配置自定义逻辑(CCL)在 AVR 上实现相同的功能。)

结果是允许 8 位 MCU 以比指令速度(即在内核上运行的指令)快得多的逻辑速度驱动一串 LED,同时释放内核以执行其他任务。

一般来说,拥有 CIP,特别是能够将它们组合在一起,打开了通往各种部署场景的大门,允许外设处理各种传感器数据。

“常见的传感器输出类型"
常见的传感器输出类型

考虑下面所示的示例,其中使用 8 位 PIC 或 AVR 单片机来监控温度、湿度和振动传感器的输出。可能来自温度传感器的信号比来自湿度传感器的信号需要更高的增益,这可以通过在程序控制下来回交换片上运算放大器的增益来实现。

“典型的多传感器应用"
典型的多传感器应用

同时,MCU 可能以 5V 运行,而使用 I2C 通信的振动传感器仅需要 1.8V。在这种情况下,解决方案不是采用外部电压电平转换器,而是采用 MCU 的多电压输入/输出(MVIO)功能。

上面的示例显示了 MVIO 和 I2C 的组合,但 MVIO 也可以与通用输入/输出(GPIO)一起使用。事实上,这引出了另一个例子,因为运行在 5V 的 8 位 PIC 或 AVR MCU 可用于从传感器读取值,从而实现比 3.3V MCU 更好的分辨率,而 PIC/ AVR MCU可以使用其 MVIO 功能将此数据传送到 3.3V 32 位 PIC32 SAM MCU。

让 PIC/AVR MCU的新手感到困惑的一件事是可用的不同组件数量之多,每个组件都有不同数量的引脚以及不同的功能和外设组合。有几种方法可以解决这个问题。就我而言,我会直接问我的朋友 Joe Farr。关于Microchip PIC 和 AVR MCU,他就是一本行走着的百科全书。对于那些不认识 Joe 的人,Microchip 网站上有一个产品选型指南,用户说了“我需要这个功能”后,就会被引导到合适的产品。或者,用户可以说“我想实现这个应用”,该工具不仅可以将他们引导到合适的产品,还可以引导到相关的固件和软件以及开发工具。

Greg 在结束谈话前,说了一些很有趣的内容。那就是不光是 8 位 MCU这块饼在增长,各种新的应用也不断涌现,就像有了一个全新的饼。因此,他表示:“Microchip 非常看好 8 位 MCU 市场。”这对我来说是个好消息,因为我喜欢 8 位 MCU。那么你呢?你有什么想法愿意分享吗?

直接转载来源:Microchip微芯
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 116

安全威胁日益复杂,给物联网(IoT)、消费、工业、医疗和其他市场产品开发带来了挑战。这些产品必须具备强大的嵌入式安全性,同时还要求低功耗以延长电池寿命。Microchip Technology Inc.(美国微芯科技公司)近日宣布推出业界首款在单一封装中集成了安全子系统和Arm® TrustZone®技术的PIC32CM LS60单片机(MCU)。新款单片机集成了Microchip的可信平台(Trust Platform)安全子系统,让使用单个单片机而不是两个或多个芯片来开发终端产品变得更加容易。现在,设计人员可使用这款值得信赖的32位单片机,来保护产品和终端用户的智能家居设备、智能手机或平板电脑配件、便携式医疗设备、可穿戴设备、互联电器和工业机器人免受远程或物理攻击。

“Microchip发布业界首款集成强大安全子系统和Arm®

随着物联网行业的持续快速发展,对于边缘设备而言,高标准的安全保护已变得不可或缺。PIC32CM LS60结合了易于使用的Arm TrustZone技术和通用标准联合解释库(JIL)中认定的“高”等级可信平台安全子系统,使开发人员能够实施业界公认的安全实践和对策,以防范各类已知的远程和物理攻击。相关设计得到了MPLAB® 代码配置器(MCC) TrustZone Manager和可信平台设计套件等工具的支持,从而简化了安全子系统的配置。Microchip 可信平台配置服务可用于安全地配置密钥和证书。

Microchip 32位单片机业务部副总裁Rod Drake表示:“PIC32CM LS60在单个封装中集成了Arm TrustZone技术和Microchip的安全子系统,是市场上前所未有的产品。我们相信这款单片机的安全性、易用性和低功耗将有力地推动物联网应用采用先进的安全技术。”

随着防水可穿戴设备和现代电器越来越多地采用触屏功能,嵌入式设计人员面临着开发能在嘈杂和潮湿环境中稳健运行的触屏界面的挑战。PIC32CM LS60集成了增强型外设触屏控制器,具有Driven Shield Plus功能,可防止因潮湿造成的误触屏,并提供高抗噪能力,以实现卓越的触屏界面功能。

此外,该器件具有独立于内核的SleepWalking外设和事件系统。这些外设可使单片机内核长时间处于休眠模式,以减少功耗。它还配有片上模拟,包括运算放大器(o运放)、数模转换器(DAC)和模数转换器(ADC),可在休眠模式下运行并与各种传感器连接。PIC32CM LS60与MPLAB Data Visualizer和Power Debugger工具兼容,可用于实时监测、分析和微调功耗数据。这使客户更容易开发低功耗应用,并更快地向市场推出电池续航时间更长的产品。

开发工具

Microchip提供以下开发工具和服务来支持PIC32CM LS60及其他型号:可信平台配置服务、可信平台设计套件(TPDS)、带有用于Arm TrustZone的MPLAB 代码配置器的MPLAB Harmony v3平台、触摸函数库、触摸配置器、MPLAB Data Visualizer、功率调试器,以及PIC32CM LE00 Curiosity Pro评估工具包、PIC32CM LS00 Curiosity Pro评估工具包、PIC32CM LS60 Curiosity Pro评估工具包、PIC32CM LE00超低功耗防水触屏参考设计、MPLAB X集成开发环境(IDE)及其调试器、编程器和编译器生态系统。

供货

除PIC32CM5164LS60安全性型号外,PIC32CM LE00通用型号和PIC32CM LS00 Arm TrustZone技术型号(无安全子系统)也可提供。PIC32CM5164LS60、PIC32CM5164LS00和 PIC32CM5164LE00现已开始供货,100引脚TQFP封装,10,000件起订,单件售价分别为5.48美元、4.33美元和4.18美元。如需了解更多信息或购买,请联系Microchip销售代表或访问Microchip直销网站:www.microchipDIRECT.com

来源: Microchip微芯
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 26

页面

订阅 RSS - 单片机