前言
ArduPilot 支持来自许多不同制造商的各种传感器。这方面的一个明显的例子可以在测距仪( list of range finders)(又称声纳、激光雷达)的列表中看到。本页试图解释传感器驱动程序是如何编写和集成到飞行器代码中的。
1 支持的协议
支持 I2C,SPI,UART(aka Serial)和 CANBUS(特别是 UAVCAN/DRoneCAN)协议。如果你打算编写一个新的驱动程序,你可能需要参考传感器的数据手册,以确定它使用的协议。
1.1 I2C
- 一个主机,可能有许多从设备;
- 一种相对简单的协议,适用于短距离通信(即小于1米);
- 总线以 100kHz 或 400kHz 的速度运行,但与其他协议相比,数据速率相对较低;
- 只需要4个引脚(VCC,GND,SDA,SCL)。
1.2 SPI
- 一个主机,一个从机;
- 20Mhz+ 的速度意味着它非常快,特别是与 I2C 相比;
- 只适用于短距离内工作(10厘米);
- 需要至少5个引脚(VCC、GND、SCLK、主-出-从-入、主-入-从-出)+ 每个从机的一个从机选择引脚。
1.3 Serial / UART
- 一个主机,一个从机;
- 基于字符的协议,与 I2C 和 SPI 相比,适合长距离通信(如1米);
- 速度相对较快,57Kbps ~ 1.5Mbps;(Bps (Bytes per second),即字节每秒,因为一字节对应八比特,所以1Bps = 8bps)
- 至少需要4个引脚(VCC,GND,TX,RX),加上2个可选的引脚(清除发送,清除接收)。
1.4 带有UAVCAN的CAN总线
- 多主机总线,任何节点都可以在需要时启动数据传输;
- 基于数据包的协议,适用于非常长的距离;
- 高速,通常为1Mb(然而,只有50%的总线比特率可以在没有重大冲突的情况下真正使用);
- 至少需要3个引脚(GND,CAN HI,CAN LO)。可选择使用 VCC 为节点供电;
- 点对点拓扑结构。不建议使用星形或树枝形拓扑结构;
- 总线的每一端都需要终端。
2 前端/后端分离
传感器驱动程序架构中的一个重要概念是前端/后端分离。
飞行器代码只会调用库(又称传感器驱动程序)的前端。
在启动时,前端根据传感器的自动检测(即探测已知 I2C 地址上的响应)或使用用户定义的 _TYPE 参数(即 RNGFND_TYPE,RNGFND_TYPE2)创建一个或多个后端。前端维护指向每个后端的指针,这些指针通常保存在一个名为 _drivers[] 的数组中。
用户可设置的参数总是被保存在前端。
3 如何以及何时运行驱动程序代码
上图显示了 ardupilot 架构的放大视图。左上角的蓝框说明了传感器驱动程序的后端是如何在后台线程中运行的。收集来自传感器的原始数据,转换为标准单位,然后保存在驱动程序的缓冲区内。
无人机代码的主线程定期运行(如 400hz 的旋翼),并通过驱动程序前端的方法访问可用的最新数据。例如,为了计算最新的姿态估计,AHRS/EKF 将从传感器驱动程序的前端获取最新的加速度计、陀螺仪和罗盘信息。
图片是一个小小的概括,对于使用 I2C 或 SPI 的驱动程序,它们必须在后台线程中运行,这样与传感器的高速通信就不会影响主循环的性能,但对于使用串行(又称 UART)接口的驱动程序,在主线程中运行是安全的,因为底层串行驱动程序本身在后台收集数据并包括一个缓冲区。
4 无人机代码和前端示例
下面的例子显示了 Copter 无人机代码如何从测距仪(又称声纳、激光雷达)驱动程序中获取数据。旋翼代码的调度程序(scheduler)以 20Hz 的频率调用无人机的 read_rangefinder() 方法。下面是这个方法的图片,可以在 sensors.cpp 文件中看到最新版本。rangefinder.update() 方法是对驱动程序前端的一个调用。
下面是测距仪驱动程序的前端更新方法( update method)。这给了驱动程序一个机会在主线程中做任何它可能想要的一般处理。每个后端的更新方法被依次调用。
5 UART / Serial后端示例
接下来是 LightWare 后端(LightWare back-end)使用串行协议的更新方法。如用户维基上(user wiki)所述,串行测距仪可以连接到飞行控制器的任何一个串行端口,但用户必须通过设置SERIALX_BAUD 和 SERIALX_PROTOCOL 参数来指定哪个串行端口,以及使用什么波特率。
在串行驱动程序的后端代码(backend code)中,它首先通过 serial_manager 类找到用户要使用的 UART,该类将查找上述的参数设置。
每次调用驱动程序的后端 update() 方法时,它都会调用 get_reading 方法,检查是否有新的字符从传感器到达,然后对其进行解码。
如上所述,由于串行协议实现了自己的缓冲,来自传感器的任何数据(见 get_reading 方法)的处理都在主线程中运行。也就是说,没有像你在 I2C 和 SPI 驱动中看到的 "register_periodic_callback"。
6 I2C后端示例
这个例子显示了 Lightware I2C 驱动程序的后端。在这种情况下,前端获得 I2C 总线,并在初始化时将其传递给后端。
然后后端的 init 方法注册其“timer”方法,以 20hz 的频率调用。在 timer 方法中(未显示),get_reading() 方法被调用,它从传感器中读取字节并将距离转换为厘米。
7 SPI后端示例
这个例子显示了 MPU9250 IMU 的后端,它包括一个陀螺仪、加速度计和罗盘。前端获得 SPI 总线,并在初始化时将其传递给后端。
在初始化期间将调用 start() 方法并配置传感器。它使用信号量来确保不干扰同一总线上的其他 SPI 设备。
注册 _read_sample 方法,以便以 1000hz 调用它。请注意,在 _read_sample 方法中不需要获取/释放信号量,因为这是作为定期回调代码的一部分完成的。
_block_read 方法显示如何从传感器的寄存器中读取数据。
8 附加建议
在编写传感器驱动程序时,千万不要包含任何等待或睡眠代码,因为这将延迟主线程或正在使用的与总线相关的后台线程。
如果编写了一个新的库,它必须被添加到飞行器目录下的 wscript 文件中(即/ardupilot/ArduCopter/wscript),以便将其链接到最终的二进制文件中。