FKUAV 发表于 2022-12-15 13:37:41

理解ArduPilot的线程(下)

点关注啦!

上篇文章讲到ArduPilot中有许多与线程相关的关键概念:


定时器回调

HAL特定的线程

驱动程序特定线程

ardupilot驱动与平台驱动

平台特定的线程和任务

AP_Scheduler系统

信号量

无锁数据结构


这篇继续讲解余下的概念:

AP_Scheduler系统

The AP_Scheduler system

ArduPilot线程和要理解的任务的下一个方面是AP_Scheduler系统。AP_Scheduler库用于在主控制线程内划分时间,同时提供一些简单的机制来控制每个操作使用多少时间(在AP_Scheduler中称为“任务”)。

它的工作方式是每个航模实现的loop()函数都包含一些代码,可以实现这一点:


等待新的IMU样本到达

在每个IMU样本之间调用一组任务

它是一个列表驱动调度器,每种航模都有一个AP_Scheduler ::Task表。要了解它的工作原理,请查看 AP_Scheduler / examples / Scheduler_test.cpp (https://github.com/ArduPilot/ardupilot/blob/master/libraries/AP_Scheduler/examples/Scheduler_test/Scheduler_test.cpp)草图。

如果你查看该文件,你将看到一个包含3个调度任务集的小表。与每个任务相关的是两个数字。该表看起来像这样:

static const AP_Scheduler::Task scheduler_tasks[] PROGMEM = {

{ ins_update, 1, 1000 },

{ one_hz_print, 50, 1000 },

{ five_second_call, 250, 1800 },

};

每个函数名称后面的第一个数字是调用频率,以ins.init()调用控制的单位。对于这个示例,ins.init()使用RATE_50HZ,因此每个调度步骤都是20ms。这意味着每20ms调用一次ins_update()调用,每50次调用one_hz_print()函数(即每秒一次),并且每250次调用一次five_second_call()(即每5秒一次)。

第三个数字是该功能预计需要的最长时间。这用于避免运行调用,除非在此计划运行中有足够的时间来运行该功能。调用scheduler.run()时,会传递可用于运行任务的时间量(以微秒为单位),如果此任务的最坏情况时间意味着它在该时间耗尽之前就不适合了,则它不会调用。

另一点需要注意的是ins.wait_for_sample()调用。这就是驱动ArduPilot调度的“节拍器”。它阻止主飞行器线程的执行,直到有新的IMU样本可用。IMU采样之间的时间由ins.init()调用的参数控制。

注意,AP_Scheduler表中的任务必须具有以下属性:


他们不应该阻塞(ins.update()调用除外)

他们不应该在飞行时调用睡眠功能(自动驾驶仪,就像真正的飞行员一样,在飞行时不应该休眠)

他们应该有可预见的最坏情况时机

你现在可以去修改Scheduler_test示例并添加你自己的任务来运行。尝试添加执行以下操作的任务:


阅读气压计

阅读指南针

阅读GPS

更新AHRS并输出音调

查看本教程前面所使用的每个库的示例程序,以了解如何使用每个传感器库。

信号量

Semaphores

当你有多个线程(或定时器回调)时,你需要确保两个逻辑执行线程共享的数据结构以防止损坏的方式进行更新。在ArduPilot中有三种主要的方法 - 信号量,无锁数据结构、PX4 ORB。

AP_HAL信号量只是在特定平台上提供任何信号量系统的包装,并提供了一种用于互斥的简单机制。例如,I2C驱动程序可以要求I2C总线信号量以确保一次只能使用一个I2C设备。

去看看库/ AP_Compass /AP_Compass_HMC5843.cpp中的hmc5843驱动程序,然后查找get_semaphore()调用。看看它使用的所有地方,看看你是否可以弄清楚为什么需要它。

无锁数据结构

Lockless Data Structures

ArduPilot代码还包含使用无锁数据结构以避免需要信号量的示例。这可能比信号量更有效。

ArduPilot中无锁数据结构的两个例子是:


库/ AP_InertialSensor / AP_InertialSensor_MPU9250.cpp中的_shared_data结构

在许多地方使用环形缓冲区。一个很好的例子是库/ DataFlash / DataFlash_File.cpp

去看看这两个例子,并向自己证明它们对于并发访问是安全的。对于DataFlash_File,请查看_writebuf_head和_writebuf_tail变量的用法。

在ArduPilot的几个地方创建一个通用的环形缓冲区类可以用来代替单独的环缓冲区实现,这将是很好的。

PX4 ORB

这种机制的另一个例子是PX4 ORB。ORB(对象请求代理)是一种使用在多线程环境中,安全的发布/订阅模型将数据从系统的一部分提供给另一部分(例如设备驱动程序 - >车辆代码)的方式。


ORB提供了一种很好的机制来声明将以这种方式共享的结构(全部在 PX4Firmware / src / modules / uORB /中定义)。然后,代码可以将数据“发布”到其中一个主题,这些主题由其他代码片段提供。

另外两种与PX4驱动程序通信的通用机制是:


ioctl调用(请参阅AP_HAL_PX4 / RCOutput.cpp中的示例)

/ dev / xxx读/写调用(请参阅AP_HAL_PX4 / RCOutput.cpp中的_timer_tick)



页: [1]
查看完整版本: 理解ArduPilot的线程(下)