**
ARM公司是全球领先的半导体知识产权(IP)提供商。全世界超过95%的智能手机和平板电脑都采用ARM架构。ARM设计了大量高性价比、耗能低的RISC处理器、相关技术及软件。2014年基于ARM技术的全年全球出货量是120亿颗,从诞生到现在为止基于ARM技术的芯片有600亿颗。技术具有性能高、成本低和能耗省的特点。在智能机、平板电脑、嵌入控制、多媒体数字等处理器领域拥有主导地位。
CISV以Intel和AMD的x86架构为代表,RISV以ARM和MIPS为代表,目前正在崛起的RISC-V也是RISV架构,开源采用宽松的BSD协议,企业完全自由免费使用,同时也容许企业添加自有指令集拓展而不必开放共享以实现差异化发展。
CISV和RISV区别
插曲:ARM与RISC-V的竞争
ARM上线了质疑risc-v的网站riscv-basics.com,24小时后又下线了,该网站列举了对RISC-V的几大质疑,包括:
-成本:虽然RISC-V是开源ISA,但是ISA仅仅是处理器芯片的一小部分,因此RISC-V的免费ISA对于整体处理器的成本影响并不大。
-生态系统:RISC-V生态系统不够完整,无法提供完整的(包括硬件到软件)设计支持。
-碎片化风险:RISC-V生态中每个项目都可能会做自己的私有指令集扩展,导致软件兼容性不好,即碎片化问题。
-安全性问题:之前由大厂出品的x86和ARM许多处理器都遇到了安全性问题(Spectre漏洞),而由一个尚处于起步阶段组织或初创公司发布的RISC-V处理器遇到安全性问题的可能性更大。
-设计验证:RISC-V虽然是开源指令集可以自行改动指令集,但是改动指令集会需要大量设计验证,所以如果设计验证完整度不够的情况下自行修改指令集反而容易出错。
ARMCortex™-M4处理器是由ARM专门开发的嵌入式处理器,在M3的基础上强化了运算能力,新加了浮点、DSP、并行计算等,用以满足需要有效且易于使用的控制和信号处理功能混合的数字信号控制市场。其高效的信号处理功能与Cortex-M处理器系列的低功耗、低成本和易于使用的优点的组合,旨在满足专门面向电动机控制、汽车、电源管理、嵌入式音频和工业自动化市场的新兴类别的灵活解决方案。
以STM32L4R9为例,MCU中几个比较典型的外设:
仅仅从单处理器运行多线程这一点来说,实时嵌入式系统中的多任务与桌面电脑的多任务从概念上来讲是相似的。但实时嵌入式系统的侧重点却不同于桌面电脑——特别是当嵌入式系统期望提供”硬实时”行为的时候。
桌面电脑的输入处理可以归类为”软实时”。为了保证用户的最佳体验,计算机对每个输入的响应应当限定在一个恰当的时间范围——但是如果响应时间超出了限定范围,并不会让人觉得这台电脑无法使用。比如说,键盘操作必须在键按下后的某个时间内作出明显的提示。但如果按键提示超出了这个时间,会使得这个系统看起来响应太慢,而不致于说这台电脑不能使用。
硬实时功能必须在给定的时间限制之内完成——如果无法做到即意味着整个系统的绝对失败。汽车的安全气囊触发机制就是一个硬实时功能的例子。安全气囊在撞击发生后给定时间限制内必须弹出。如果响应时间超出了这个时间限制,会使得驾驶员受到伤害,而这原本是可以避免的。
大多数嵌入式系统不仅能满足硬实时要求,也能满足软实时要求。
(1)常见的嵌入式软件结构
可以分为轮询系统、前后台系统和多任务系统。这些方案是根据具体的需求提出的,各有各的特点和使用的领域。
(2)RTOS多任务机制概述
uCOS-III中每个任务能保持的时间片可以单独设置,需要在任务初始化时作为形参传入。这样做的坏处是对用户不太友好(API的形参如果太多,应用开发人员接受起来有些麻烦);这样做的好处是不会在每个时间片都发生任务的切换(任务切换是需要开销的),提高了总的CPU利用率。
另外,uC/OS-III中,由于可以对每个任务的时间片分别进行设置和修改,所以可以很方便地调节同优先级下每个任务的CPU占用率,尽管两个任务的优先级是一样的,但是有个任务比较重要,我们希望让它的CPU占用率高一点,这时只要把它的时间片设置得大一点即可。而在FreeRTOS中同优先级下的每个任务对CPU的占用率都只能是一样。
void xPortSysTickHandler( void )
{
/* The SysTick runs at the lowest interrupt priority, so when this interrupt
executes all interrupts must be unmasked. There is therefore no need to
save and then restore the interrupt mask value as its value is already
known. */
( void ) portSET_INTERRUPT_MASK_FROM_ISR();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* A context switch is required. Context switching is performed in
the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( 0 );
}
中断状态寄存器bit28位被写入1,将PendSV中断设置为挂起状态,等到优先级高于PendSV的中断执行完成后,PendSV中断服务程序将被执行,进行任务切换工作。
堆 Heap,用于动态内存分配,一般是通过定义一个数组实现固定内存分配,malloc从数组中分配内存:
/* Allocate the memory for the heap. */
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
/* The application writer has already defined the array used for the RTOS
heap - probably so it can be placed in a special segment or address. */
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
栈空间大小 = usStackDepth * sizeof( StackType_t ) bytes,在CM4上,sizeof( StackType_t ) = 4。
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
任务栈空间使用量估算方法:
(1)函数的嵌套调用,针对每一级函数用到栈空间的有如下四项:
函数局部变量;
函数形参,一般情况下函数的形参是直接使用的 CPU 寄存器,不需要使用栈空间,但是这个函数中如果还嵌套了一个函数的话,这个存储了函数形参的 CPU 寄存器内容是要入栈的。 所以建议大家也把这部分算在栈大小中;
函数返回地址,针对 M3 和 M4 内核的 MCU,一般函数的返回地址是专门保存到 LR(LinkRegister)寄存器里面的,如果这个函数里面还调用了一个函数的话,这个存储了函数返回地址的 LR 寄存器内容是要入栈的。 所以建议大家也把这部分算在栈大小中;
函数内部的状态保存操作也需要额外的栈空间;
(2)任务切换,任务切换时所有的寄存器都需要入栈,对于带 FPU 浮点处理单元的 M4 内核 MCU 来说,FPU 寄存器也是需要入栈的。
FreeRTOS 提供了两种栈溢出检测机制,这两种检测都是在任务切换时才会进行:
方法一
在任务切换时检测任务栈指针是否过界了,如果过界了,在任务切换的时候会触发栈溢出钩子函
数。
void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName );
用户可以在钩子函数里面做一些处理。这种方法不能保证所有的栈溢出都能检测到。比如任务在执行的过程中出现过栈溢出。任务切换前栈指针又恢复到了正常水平,这种情况在任务切换的时候是检测不到的。又比如任务栈溢出后,把这部分栈区的数据修改了,这部分栈区的数据不重要或者暂时没有用到还好,但如果是重要数据被修改将直接导致系统进入硬件异常,这种情况下,栈溢出检测功能也是检测不到的。
使用方法一需要用户在 FreeRTOSConfig.h 文件中配置如下宏定义:
#define configCHECK_FOR_STACK_OVERFLOW 1
方法二
任务创建的时候将任务栈所有数据初始化为 0xa5,任务切换时进行任务栈检测的时候会检测末尾的 16 个字节是否都是 0xa5,通过这种方式来检测任务栈是否溢出了。相比方法一,这种方法的速度稍慢些,但是这样就有效地避免了方法一里面的部分情况。 不过依然不能保证所有的栈溢出都能检测到,比如任务栈末尾的 16 个字节没有用到,即没有被修改,但是任务栈已经溢出了,这种情况是检测不到的。 另外任务栈溢出后,任务栈末尾的 16 个字节没有修改,但是溢出部分的栈区数据被修
改了,这部分栈区的数据不重要或者暂时没有用到还好,但如果是重要数据被修改将直接导致系统进入硬件异常,这种情况下,栈溢出检测功能也是检测不到的。
使用方法二需要用户在 FreeRTOSConfig.h 文件中配置如下宏定义:
#define configCHECK_FOR_STACK_OVERFLOW 2
钩子函数
钩子函数的主要作用就是对原有函数的功能进行扩展,用户可以根据自己的需要往里面添加相关的测
试代码, 大家可以在 FreeRTOS 工程中检索这个钩子函数 vApplicationStackOverflowHook 所在的位
置。
追踪Hardfault的原因:
R14 是连接寄存器( LR),在一个汇编程序中,你可以把它写作 LR 和 R14。 LR 用于在调用子程序时存储返回地址。例如,当你在使用 BL(分支并连接, Branch and Link)指令时,就自动填充 LR 的值。
R15 是程序计数器,在汇编代码中你也可以使用名字“PC”来访问它。
当MCU出现死机问题时,可以通过查看R14和R15的内容来复现死机现场:
在发生hardfault时,把R14和R15的值保存下来,通过查看map文件中的函数地址信息,可以得知发生死机时的主程序和子程序信息。
4、FreeRTOS的介绍
(1)特点
在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对于C/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行。
FreeRTOS是由Richard Barry在2003年由设计的,由于其设计的小巧简单,整个核心代码只有3到4个C文件。在设计之初就异军突起,累计开发者数百万,是目前市场占有率最高的RTOS,现在FreeRTOS已经支持三十多种芯片,基本包含市场上所有的微控制器。FreeRTOS在2018年被亚马逊收购,继续遵循GPLV2许可协议完全免费。 Richard Barry为了让代码容易阅读、移植和维护,大部分的代码都是以C语言编写,只有一些内核调度函数采用汇编编写。