前文链接:
【轻量级开源ROS 的机器人设备(4)】--(3)通信实现
【轻量级开源ROS 的机器人设备(4)】--(2)通信实现
【轻量级开源ROS 的机器人设备(4)】--(1)通信模块
根据第 4 章中解释的研究目标,我们设计了并实现了一个通信框架,µROSnode(uros inside the代码,图 5.1 中的标识),它与 ROS 具有原生兼容性,并且是针对嵌入式系统软件。其软件架构和实施细节突出了其模块化方法,以及设计针对简单性和便携性进行了优化。此外,我们开发了一些工具帮助用户将应用软件与框架集成,通过生成(取消)编组过程,并估计堆栈使用情况适用于内存受限的目标平台。一个开发过程终于来了提议,展示了使用我们的框架开发应用程序的理想方式从头开始,或通过整合。
下面说明了主要特性和实现选择,以便更好地了解在嵌入式系统领域所做的工作。针对资源受限的设备进行开发,我们必须找到合适的语言可移植性和特性之间以及占用空间之间的折衷大小和代码优雅。
该框架是用几乎纯 ANSI C89 [1] 编写的,也称为 ISOC90 [26],在 GCC [10] 支持的更正版本中。这确保了跨多个平台的最大可移植性,如广泛说明3.2节的研究策略。框架广泛使用C99标准
整数类型(例如 uint32_t),大部分可以在 C89 中手动定义;
实际上,任何最近的编译器都支持它们,包括 stdint.h。遵守如果使用 gcc -ansi -pedantic 编译,则符合 C89 标准。
选择这种语言,尽管它具有可移植性,但使我们接受了一些其他类似语言中没有的强烈限制,特别是它的广泛使用进化 C++。缺乏通用编程特性、命名空间和类,它可以使代码严格类型化并更具可读性,迫使我们做出一些共同的妥协。
例如,列表中的对象由指向 void 的指针弱寻址,其中C++ 模板会为它们分配正确的类型;键入宏的使用在这种情况下会降低可读性。命名空间缺失问题解决通过在函数名称前加上 uros 前缀,通常后跟模块名称(_lld_+低级驱动模块);主要类型名称以 Uros 为前缀,uros_未成年人。我们试图编写一个干净的代码库,具有用户友好的编码风格,以应对 ANSI C 的局限性。
如第 3 章所述,并非所有 C++ 特性都适用于所有嵌入式系统,或者是矫枉过正。例如,仅启用 RTTI编译器生成一个更大的目标代码,无法放入微控制器的小代码存储器。此外,某些人根本不支持有用的异常处理编译器、目标架构或操作系统。此外,C++语言对普通的旧 ANSI C 没有同样成熟的支持。
在可能的情况下,编写代码以尽量减少内存使用。这可能会使代码有点混乱,但我们尝试尽量减少别名结构的使用(通过联合),只有在堆栈分析工具显示有很大机会减少堆栈使用的情况下。无论如何,此类优化通常供内部使用,而用户 API 是干净的。
得益于堆栈分析工具,我们减少了函数调用的次数,并最小化函数参数或局部变量。虽然声明更多具有有意义的名称和单一赋值的变量使代码更具可读性,编译器并不总是能够优化堆栈用法。所以,一些函数重用局部变量或参数来减少堆栈使用,没有积极的重用,不会混淆代码。
具有可预测寿命的对象通常被放置在堆栈上,即使相当大的。这样,堆栈分析工具可以跟踪此类 对象,并且程序员知道后果。此外,堆栈分配速度更快并且自动管理。
相比之下,堆分配速度较慢(甚至不是恒定时间)并且容易出错;此外,疏忽地分配到堆中可能会使它饱和(因此失败)出乎意料。这就是为什么我们鼓励整个过程中的堆栈分配代码库。
为了减少堆栈使用,我们避免在任何地方进行激进的函数嵌套可能,同时保持合理程度的因式分解。
在我们看来,目标代码大小是一个次要问题,因为最新的微控制器有足够的空间,如第 3.2 节所示。尽管如此,正如常识所暗示的那样,特征分解以及一组忽略未使用代码的预处理器开关可以减少目标代码的大小。
例如,TCPROS 连接头发送和接收功能,相当冗长,在客户端和服务器之间共享;同样地,XML-RPC 客户端调用依赖于通用语法。做了一些优化编译器也可以帮助减少目标代码占用空间,即使是那些哪个目标速度。
该框架由反映其领域的软件模块组成专业知识。大多数模块都是平台无关的,而有些模块有
依赖于平台的对应物,以绑定的 lld(低级驱动程序)为前缀到为特定平台提供的低级库。还有
一些应用相关的模块,应该由用户提供,定义回调函数。可选地,一些用户模块包含序列化
ROS 主题和服务的函数和处理程序。主要优化强调了资源受限的平台(嵌入式系统)。
图 5.2 显示了框架软件模块的逻辑视图。
urosBase 模块提供了整个过程中使用的软件功能框架,例如基本类型和内存管理功能。低级对应物 uros_lld_base 为内存管理提供绑定功能。
1)基本类型
框架提供的基本类型是那些由C99标准,加上一些常用的类型。此外,一些有用的定义函数类型(签名),如谓词、比较和线程入口点签名。
2)字符串Strings
字符串类型,UrosString,用于字符串管理,取而代之C 语言使用的以 null 结尾的字符串。它们由 size_t 组成包含字符串长度的字段,后跟指向字符串内容的指针。这种表示优化了字符串管理,例如通过解析和序列化功能,得益于嵌入式长度计数器;这允许在不同的字符串描述符之间共享相同的文本块,这可能只使用它的一部分。
该模块提供了从 C 字符串(分配的或克隆的)进行转换的函数、检查函数和字符串比较函数。
3)列表
列表类型 UrosList 是一个单向串联的元素列表 任何类型(用户知道)。列表对象本身包含的计数 条目,以及指向第一个条目的指针。列表节点 UrosListNode 由一个 指向节点数据的指针,以及指向下一个节点的指针。 该库提供了常见列表操作(添加、删除、 节点的存在),以及一些谓词函数类型,用于查找 条目。
消息类型 消息类型记录 UrosMsgType 包含名称、可选的文本描述,以及主题/服务消息类型的 MD5 和 以及 topic/service message 类的总和,通常在应用程序领域内众所周知。因此,一个全局列表,建立在启动时,保留应用程序中使用的所有消息类型;一套函数在此列表上运行。服务消息类型的处理方式相同,但在单独的全局列表中。
4)主题/服务描述符
主题和服务共享相同的描述符输入 UrosTopic。它包含主题/服务名称,指向其消息类型的指针,指向处理程序函数的指针、一组标志和一个引用计数器。这消息类型通常包含在全局静态寄存器中(见上文)。
标志指定一些连接设置,以及主题和主题之间的切换服务。使用引用计数器,以便可以共享描述符引用它的多个处理程序实例(共享指针机制)。
5)堆分配器框
架管理的大多数操作都需要动态分配;考虑解析传入的 XML-RPC 请求持有 URI 列表。框架使用堆分配器技术。
一组函数允许动态分配和删除内存任何可能大小的块。该库支持多个堆,对于那些具有脱节内存空间的平台,常见于嵌入式系统架构。
6)内存池
管理内存块的常用方法同样大小的,比如线程池栈,就是内存池。块作为一个单向列表,其中每个节点都是一个内存块,另外还有一个最开始的保留指针,用于寻址链表中的下一个节点列表。
内存池由 UrosMemPool 类型定义。它保存块大小(保留指针的综合),指向第一个块的指针 列表,以及剩余的空闲块数,分配器函数和锁 word(用于多线程访问)。
如果提供了分配器功能,当池已满并且一个新块被提供时 请求,然后分配器函数将创建一个新块。否则, 内存池大小固定,申请线程挂起。
该库提供了用于创建内存池的函数,既可以通过分配器函数动态创建,也可以从固定大小的内存块(数组)创建,并且 请求或释放块的功能。 上面说明的内存池机制是独立于平台的,但是 如果可用,可以为本机内存池覆盖。
urosConn 模块提供了 TCP 和 UDP 连接特性框架。低级对应物 uros_lld_conn 连接到用户选择的网络堆栈。
1)连接地址Addresses
该库提供用于保存 IP 号 (UrosIp) 的数据类型,和 TCP 或 UDP 地址(UrosAddr)。一些辅助函数可以解决地址的主机名或 ROS URI。
2)连接记录
连接记录,UrosConn,保存着本地和远程地址,协议标识符,以及接收和传输的字节。某些字段可能由所选的低级驱动程序添加。
3)与 BSD 套接字的比较
连接生命周期和使用等同于广泛使用的POSIX套接字 [11],只有少数有意义的例外,所以读者知道查阅 POSIX 套接字手册。下面解释的差异是仅在发送和接收调用行为中。他们被介绍为了在不同网络堆栈之间实现更高程度的可移植性;例如,没有其他聪明的方法来为 LWIP 堆栈提供绑定,而保持 POSIX 套接字的合理实现。连线还可以支持超时,如果在目标体系结构上可用。
4)传输
对 urosConnSend()(或相关)的调用发送指定的数据块数据的整体,而 POSIX 套接字可以部分发送(即使不是在全部)。
5)接待调用
urosConnRecv() (或相关)填充由提供的缓冲区低级驱动程序,而不是像 POSIX 那样由用户提供的缓冲区插座。
1)urosRpc 模块
urosRpc 模块提供了所有需要的 XML-RPC 特性。符合 ROS 的节点,如解析和流功能,以及客户端调用和 Slave API 服务器。
2)参数
XML-RPC 参数由 UrosRpcParam 描述。它指定值类(参数类型开关)和多态值本身。有关 XML-RPC 参数类型的更多信息,请参阅第 4.3 节。
3)参数列表
一个参数列表,UrosRpcParamList,是一个双端的,UrosRpcParamNode 条目的单向列表。列表描述符包含条目数和指向头节点和尾节点的指针。一个节点嵌入一个UrosRpcParam 值,以及指向下一个节点的指针。
这样的结构使得在解析或处理 XML-RPC 流时参数管理更容易。实际上,节点附加在末尾(尾部),并且列表仍然可以从最旧的节点(头)遍历到最新的节点(尾巴);相比之下,UrosList 可以在恒定时间内进行管理和遍历仅以相反的顺序。将值嵌入节点内避免了UrosListNode 不需要的间接寻址和堆碎片。
4)解析器
作为一种基于 XML 的语言,提供的众多 XML 解析器之一外部库可以用于 XML-RPC 解析。无论如何,大多数(如果不是全部)不是专门为资源受限的平台设计的,因为嵌入式系统是。因此,这个模块提供了一个优化的解析器,它可以通过 HTTP 对传入的 XML-RPC 请求执行一次解析,代码和堆栈占用空间小。
UrosRpcParser 记录保存了最后的错误代码(便于避免堆栈上的局部变量)、指向连接的指针和用于解析的字段;其中,指向接收到的 HTTP 缓冲区的指针及其长度,以及当前游标状态。此外,缓冲区保存可能的传入字符串实体;实际上,LL(k) 假设 [24] 唯一不成立的情况是当值是没有字符串标记的字符串类型时,因此解决方法是预取可能的最大长度的字符串。
5)Streamer
该库还能够流式传输 XML-RPC 消息通过 HTTP。 streamer对象类型为UrosRpcStreamer,结构化类似于解析器;这使得在任何需要的地方都可以使用别名,通过重用解析器和流媒体的内存块(互斥),从而优化内存使用。
6)Calls
一组所有可能的 ROS 客户端对 Master、Slave 和参数服务器 API 可用。它们在代码和内存中的足迹通过利用通用语法和 XML-RPC,使用量保持在最低限度参数。
7)Slave
框架用于创建一个Slave ROS Node,所以暴露了它的 Slave API 服务器,由 Slave 服务器线程执行。它听任何传入的 XML-RPC 请求消息,并将它们分派到适当的从 API 方法处理程序,处理它们并生成预期的响应消息。
urosTcpRos 模块提供了通过TCPROS 协议。
1)连接状态
TCPROS 连接状态由描述符 UrosTpcRosStatus 保存,它指定调用者姓名、引用的主题或
服务描述符和标志、退出请求标志和其他选项。
2)函数
由于 TCPROS 协议非常接近内存中的二进制表示,其函数只是原始数据传输的简写和接待处。相反,(取消)编组例程应该由特定于应用程序的代码,在 urosMsgTypes 模块内(参见第 5.2.8 节)。
一对函数可以发送和接收 TCPROS 连接头,对于两者发布者和订阅者。
3)宏
此外,为了简化主题/服务处理函数的主体,提供了一组有用的宏。它们被扩展到(未)编组
语句,以及有意义的错误检查、(未)初始化和(取消)分配声明。这些宏被那些处理程序广泛使用由代码生成器创建(参见第 5.3.1 节)。
4)数组
为了管理 TCPROS 可变长度数组,记录类型UrosTcpRosArray 可以被(un)编组过程使用。数组类型保存条目的数量,以及指向条目内存块的指针(任何类型,用户知道类型检查)。
urosNode 模块管理配置、状态和生命周期节点单例。
1)配置
配置记录,UrosNodeConfig,持有节点配置,在启动时加载。它由节点名称、URI以及远程 Master XML-RPC 服务器的 TCP/IP 地址,本地XML-RPC 从属服务器和本地 TCPROS 服务器。
2)Status
状态记录,UrosNodeStatus,保存了节点的运行时状态节点。它包含发布者和订阅者列表,活动的 TCPROS连接列表,用于 XML-RPC 和 TCPROS 的线程(和内存)池处理程序,以及用于对所有这些进行多线程访问的函数。
3)注册函数
一组例程用于管理主题发布/订阅、服务发布/调用和参数订阅。这些例程还对节点状态列表执行检查和操作作为要建立或关闭的 TCPROS 通道的自动管理。
4)节点线程
节点线程负责节点的生命周期。它填充线程池,将主题/服务/参数注册到主节点,保持检查后者是否始终可达,并在相反的顺序。
urosThreading 模块根据需要提供多线程环境由框架,如线程、同步原语和线程池。低级驱动程序 uros_lld_threading 绑定到多线程 API目标操作系统。
1)原语Primitives
框架提供的同步原语是规范的,例如信号量、互斥量和条件变量。它们的用法应该很简单。
2)线程
至于同步原语,线程遵循规范的方法。可以使用来自堆或来自内存池;线程最终可以加入。
3)线程池
线程库包括线程池机制,其中线程池具有 UrosThreadPool 类型,并且它们使用内存池 (UrosMemPool) 作为他们的堆栈供应商。线程池可以启动(线程创建)和停止(线程加入)优雅。
如果没有某种管理,框架的某些功能将无法工作 由用户应用程序。 urosUser 模块的回调函数提供 框架内部和用户应用程序之间的链接。
1)节点配置持久性
如果决定不在启动时加载默认的、硬编码的节点配置,一对加载/保存函数访问一个非易失性存储设备。
2)注册和注销
有一组功能可以管理消息类型、主题和服务(取消)注册回调,由节点线程。
3)关机回调
必须定义回调函数才能正常关闭通信渠道,并在 shutdown() 远程时释放资源向节点发出调用。
4)参数更新回调
当主节点向持有全局 ROS 的节点发出 paramUpdate() 时,将触发进一步的回调函数参数名称及其值。
可选模块 urosMsgTypes 应提供主题和服务消息类型的(取消)编组功能,以及它们的注册。这个强烈建议使用这种方法,因为它可以使代码库保持清洁并且易于维护。该模块依赖于应用程序,并且可以自动由代码生成器工具生成。有关详细说明,请参阅第 4.1 节。
另一个可选模块 urosHandlers 应该提供主题和服务处理函数。与 urosMsgTypes 一样,强烈建议使用它,并且由代码生成器工具自动生成(至少是它的骨架)第 4.1 节。