关于RTOS的全部真相。 第三十三条 使用Nucleus SE实时操作系统

到目前为止,在本系列文章中,我们一直在研究Nucleus SE提供的功能。 现在是时候看看如何在实际的固件应用程序中使用它了。




什么是Nucleus SE?


我们知道Nucleus SE是实时操作系统的核心,但是您需要了解它如何适合应用程序的其余部分。 它恰好适合,因为与台式机操作系统(例如Windows)不同,该应用程序无法在Nucleus SE上启动; 内核只是嵌入式设备上运行的程序的一部分。 这是RTOS的最常见用例。

从高级的角度来看,嵌入式应用程序是某种在CPU启动时启动的代码。 在这种情况下,将初始化硬件和软件环境,然后调用main()函数,从而启动主应用程序代码。

当使用Nucleus SE(和许多其他类似的内核)时,区别在于main()函数是内核代码的一部分。 该函数只是简单地初始化内核的数据结构,然后调用调度程序,从而导致应用程序代码(任务)的启动。 用户可以将任何本机初始化代码添加到main()函数。

Nucleus SE还包括一组功能-应用程序编程接口(API),它提供了一组功能,例如通信和任务同步,使用计时器,内存分配等。 本系列文章的前面已经介绍了所有API函数。

所有Nucleus SE软件均作为源代码提供(主要在C中)。 为了根据特定应用程序的要求配置代码,使用条件编译。 本文在“配置”部分中对此进行了详细描述。

编译代码后,所得的Nucleus SE对象模块与应用程序代码模块相关联,从而生成单个二进制映像,该映像通常放置在嵌入式设备的闪存中。 静态绑定的结果是,所有符号信息仍然可以从应用程序代码和内核代码中获得。 这对于调试很有用;但是,请注意避免滥用Nucleus SE数据。

CPU和工具支持


由于Nucleus SE是作为源代码提供的,因此它必须是可移植的。 但是,以如此低的级别运行的代码(当使用需要上下文切换的调度程序时,即“运行至完成”以外的任何其他代码)不能完全独立于汇编语言。 我最小化了这种依赖性,并且几乎不需要移植到新的CPU底层编程。 使用一套新的开发工具(编译器,汇编器,链接器等)也可能导致移植问题。

设置Nucleus SE应用


有效使用Nucleus SE的关键是正确设置。 它可能看起来很复杂,但是实际上,所有事情都是很合理的,只需要一种系统的方法。 几乎所有配置都是通过编辑两个文件完成的: nuse_config.hnuse_config.c

Nuse_config.h设置


该文件只是#define指令的字符集,为它们分配了适当的值以获得必要的内核配置。 默认情况下,在nuse_config.h文件中所有字符都存在,但是为它们分配了最小设置。

对象计数器
每种类型的内核对象的数量由NUSE_SEMAPHORE_NUMBER形式的符号值设置。 对于大多数对象,此值可以在0到15之间变化。任务是一个例外,必须至少有一个。 实际上,信号不是独立的对象,因为它们与任务相关联,并且通过将NUSE_SIGNAL_SUPPOR T 设置TRUE打开

API函数激活器
通过将名称与功能名称相匹配的符号(例如NUSE_PIPE_JAM )分配给TRUE ,可以分别激活每个Nucleus SE API函数。 这导致在应用程序中包含功能代码。

计划程序的选择和设置
如上一篇文章所述,Nucleus SE支持四种类型的调度程序。 通过将NUSE_SCHEDULER_TYPE分配给以下值之一来设置使用的调度程序: NUSE_RUN_TO_COMPLETION_SCHEDULERNUSE_TIME_SLICE_SCHEDULERNUSE_ROUND_ROBIN_SCHEDULERNUSE_PRIORITY_SCHEDULER

您可以配置其他调度程序参数:
NUSE_TIME_SLICE_TICKS指示时间片调度程序每个插槽的滴答数。 如果使用另一个调度程序,则此参数应设置为0。
NUSE_SCHEDULE_COUNT_SUPPORT可以设置为TRUEFALSE来启用/禁用调度程序计数器机制。
NUSE_SUSPEND_ENABLE启用许多API函数的任务锁定(挂起)。 这意味着对此类函数的调用可能导致调用任务挂起,直到释放资源为止。 要选择此选项, NUSE_SUSPEND_ENABLE也必须设置为TRUE

其他选择
还可以为其他几个参数分配TRUEFALSE值,以激活/停用其他内核功能:
NUSE_API_PARAMETER_CHECKING添加API函数调用参数验证代码。 常用于调试。
NUSE_INITIAL_TASK_STATE_SUPPORT将所有任务的初始状态设置为NUSE_READYNUSE_PURE_SUSPEND 。 如果禁用此参数,则所有任务将具有初始状态NUSE_READY
NUSE_SYSTEM_TIME_SUPPORT-支持系统时间。
NUSE_INCLUDE_EVERYTHING-一个参数,用于将最大功能数添加到Nucleus SE的配置中。 它导致所有已配置对象的可选功能和每个API功能的激活。 用于快速创建Nucleus SE配置以验证内核代码的新移植。

设置nuse_config.c


nuse_config.h中指定内核配置后有必要初始化ROM中存储的各种数据结构。 这是在nuse_config.c文件中完成的。 数据结构的定义由条件编译控制,因此所有结构都包含在默认nuse_config.c文件的副本中。

任务数据
NUSE_Task_Start_Address []数组必须使用每个任务的起始地址的值进行初始化。 这通常只是函数名称的列表,不带括号。 任务输入功能的原型也应该是可见的。 在默认文件中,任务配置为名称NUSE_Idle_Task() ,可以将其更改为应用程序任务。

如果使用“运行至完成”以外的任何调度程序,则每个任务都需要自己的堆栈。 对于每个任务堆栈,必须在RAM中创建一个阵列。 这些数组必须为ADDR类型,并且每个数组的地址必须存储在NUSE_Task_Stack_Base []中 。 很难预测数组的大小,因此最好使用度量值(请参阅本文后面的“调试”部分)。 每个数组的大小(即堆栈上的单词数)应存储在NUSE_Task_Stack_Size []中

如果已启用一个指示任务初始状态的功能(使用NUSE_INITIAL_TASK_STATE_SUPPORT参数),则必须使用NUSE_READY或NUSE_PURE_SUSPEND状态初始化NUSE_Task_Initial_State []数组。

分区池数据
如果配置了至少一个分区池,则必须在ROM中为每个分区创建一个数组(类型为U8 )。 这些数组的大小计算如下:(分区数*(分区大小+ 1))。 这些部分的地址(即它们的名称)必须分配给相应的NUSE_Partition_Pool_Data_Address []元素。 对于每个池,分区数及其大小应分别放在NUSE_Partition_Pool_Partition_Number []NUSE_Partition_Message_Size []中

队列数据
如果配置了至少一个队列,则必须在RAM中为每个队列创建一个( ADDR类型的)阵列。 这些数组的大小是每个队列中元素的数量。 这些数组的地址(即它们的名称)必须分配给相应的NUSE_Queue_Data []元素。 必须将每个队列的大小分配给相应的NUSE_Queue_Size []元素。

数据链接数据
如果配置了至少一个数据通道,则必须在RAM中为其(或每个)创建一个数组(类型为U8 )。 这些数组的大小计算如下:(通道大小*通道中的消息大小)。 这些数组的地址(即它们的名称)必须分配给相应的NUSE_Pipe_Data []元素。 对于每个通道,必须分别将其消息大小和大小分配给相应的NUSE_Pipe_Size []NUSE_Pipe_Message_Size []元素。

信号量数据
如果配置了至少一个信号量,则必须使用倒计时的初始值初始化NUSE_Semaphore_Initial_Value []数组。

应用计时器数据
如果配置了至少一个计时器,则必须使用计数器的初始值初始化NUSE_Timer_Initial_Time []数组。 此外,必须为NUSE_Timer_Reschedule_Time []分配重新启动值。 这些计时器值将在第一个计时器周期结束后使用。 如果重启值设置为0,则计数器将在一个周期后停止。

如果配置了对帐户完成机制的支持(通过将NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT参数设置为TRUE ),则必须再创建两个数组。 完成机制的地址(仅是函数名列表,不带括号)必须放在NUSE_Timer_Expiration_Routine_Address []中 。 数组NUSE_Timer_Expiration_Routine_Parameter []必须使用完成参数的值进行初始化。

哪个API?


一种或另一种形式的所有操作系统都具有API(应用程序编程接口)。 Nucleus SE也不例外,并且在本系列文章中已详细描述了构成API的功能。

看起来很明显,当使用Nucleus SE编写应用程序时,您需要使用如先前文章中所述的API。 但是,并非总是如此。

对于大多数用户而言,Nucleus SE API将会是全新的东西,甚至可能是他们首次使用操作系统API的初体验。 由于它非常简单,因此可以很好地介绍该主题。 在这种情况下,过程很清楚。

对于某些用户,替代API可能是更有吸引力的选择。 在三种明显的情况下,这是可能的。
  1. Nucleus SE只是将其他操作系统用于其他组件的系统的一部分。 因此,代码的可移植性,更重要的是,使用各种操作系统的经验看起来很诱人。
  2. 用户具有使用其他操作系统的API的丰富经验。 使用这种经验也是非常可取的。
  3. 用户想重用为另一个操作系统的API编写的代码。 可以更改API调用,但很费时间。


由于Nucleus SE的完整源代码可供所有人使用,因此没有什么可以阻止您编辑每个API函数,以使其看起来与其他操作系统等效。 但是,这将花费大量时间,并且将非常无用。 一种更正确的方法是编写“包装纸”。 有几种方法可以执行此操作,但是最简单的方法是创建一个头文件( #include ),其中包含一组#define宏,这些宏会将第三方API的功能映射到Nucleus SE API的功能。

随Nucleus SE一起分发了将Nucleus RTOS API的功能(部分)转移到Nucleus SE的包装器。 对于具有使用Nucleus RTOS经验的开发人员,或者将来有可能切换到此RTOS的开发人员,这可能会很有用。 当开发类似的东西时,这个包装器也可以作为一个例子。

调试Nucleus SE应用程序


使用多任务内核编写嵌入式应用程序是一项复杂的任务。 确保代码正常工作并检测错误可能是一项艰巨的任务。 尽管事实上这只是在处理器上运行的代码,但同时执行多个任务使得很难专注于特定的执行线程。 当多个任务共享一个公共代码时,这将变得更加复杂。 最糟糕的是,当两个任务具有完全相同的代码(但使用不同的数据)时。 同样复杂的是,用于实现内核对象以查看有意义的信息的数据结构的拆散。

要调试使用Nucleus SE构建的应用程序,不需要其他库或其他服务。 调试器可以读取所有内核代码。 因此,所有符号信息均可供研究。 在使用Nucleus SE应用程序时,可以使用任何现代调试工具。

使用调试器


在存在的30年中,专门为嵌入式系统设计的调试工具变得非常强大。 与桌面程序相比,嵌入式应用程序的主要特征是所有嵌入式系统都是不同的(并且所有个人计算机彼此之间都非常相似)。 一个好的嵌入式调试器应具有灵活性,并具有足够的设置以匹配各种嵌入式系统和用户要求。 调试器的可定制性以多种形式表示,但是通常可以创建脚本。 正是这一功能使调试器可以与内核级应用程序很好地协同工作。 下面,我将讨论使用调试器的一些情况。

值得注意的是,通常调试器是一系列工具,而不仅仅是一个程序。 调试器可以具有多种操作模式,在虚拟系统或真实硬件上开发代码时,调试器可通过该模式提供帮助。

任务敏感的断点


如果程序具有多个任务共有的代码,则在调试过程中使用常规断点会很复杂。 最有可能的是,您仅需要在当前正在调试的特定任务的上下文中达到断点时才停止代码。 为此,您需要一个将任务考虑在内的断点。

幸运的是,在现代调试器上创建脚本的能力以及Nucleus SE字符数据的可用性使实现特定于任务的断点变得非常简单。 所需要做的只是编写一个简单的脚本,该脚本将与您要教给他们的区分任务的断点相关联。 该脚本将采用参数:您感兴趣的任务的索引(ID)。 该脚本将简单地将此值与当前任务的索引( NUSE_Task_Active )进行比较。 如果值匹配,程序将暂停。 如果它们不同,则继续执行。 值得注意的是,该脚本的执行将实时影响应用程序的执行( 译者注:这意味着程序的执行相对于其正常操作而言会变慢 )。 但是,如果脚本不在经常执行的循环中,则这种影响将很小。

内核对象信息


调试Nucleus SE应用程序的明显需求是能够获得有关内核对象的信息的能力:它们的特征是什么以及它们的当前状态是什么。 这使您可以获得以下问题的答案:“此队列有多大,现在有多少条消息?”

可以通过在应用程序中添加其他调试代码来使用此代码,这些代码将使用“信息性” API调用(例如NUSE_Queue_Information )。 当然,这将意味着您的应用程序现在包含其他代码,在实现该应用程序之后将不需要这些代码。 使用#define使用条件编译打开和关闭此代码将是一个合理的决定。

一些调试器可以进行目标函数调用,即直接调用API函数以检索信息。 , API , .

, «» . , . , NUSE_Queue_Size[] , NUSE_Queue_Data[] . , ( NUSE_Queue_Data[] ).

API


API , , . , NUSE_SUCCESS ( ). , . (, NUSE_API_Call_Status ) ( #define). , API, NUSE_API_Call_Status = , . , , , :

NUSE_Mailbox_Send(mbox, msg, NUSE_SUSPSEND);

:

NUSE_API_Call_Status = NUSE_Mailbox_Send(mbox, msg, NUSE_SUSPEND);

, API , . , API, API .


(#31). .

: - , . , , . , , , , . .

#31 , , , « », . , .

- Nucleus SE


Nucleus SE , . , , Nucleus SE. , , - , , Nucleus SE.
  1. Nucleus SE. , Nucleus SE , Nucleus SE .
  2. CPU/. .
  3. . , , .
  4. . . . , . 16 .
  5. . - main() ?
  6. . 4 , .
  7. , .
  8. .
  9. . , .
  10. . , .
  11. . , . . — 16 .
  12. . , .
  13. . , (, ).
  14. API. API, .


( ) Nucleus SE, , Nucleus SE .

: , . — Mentor Embedded ( Mentor Graphics). , . . , e-mail: colin_walls@mentor.com.

Source: https://habr.com/ru/post/zh-CN467763/


All Articles