Ardupilot设备驱动 IIC、SPI、USART
设备代码层次结构
Ardupilot设备驱动代码的层次结构采用 前端实现 和 后端实现 分割,前端库主要供机器代码层调用,后端库主要供前端调用。这里前端可以理解为应用层,后端理解为驱动层,前端调用后端代码,实际是驱动层提供接口供应用层使用。


前端调用后端代码之前,系统会通过自动检测设备或者通过用户配置的参数创建并且启动一个或者多个后端对象。用户自定义参数(_TYPE),例如RNGFND_TYPE。每个后端对象都会保存在前端创建的指针数组中( _drivers[])。
设备驱动代码被调用方式
图中左边的后端设备驱动代码运行于后台线程中,主要实现从外部设备读取原始数据,转化为标准单位,并且将处理后的数据存储在缓冲区中。具体的飞行控制器代码通过调用前端代码获取最新的设备数据,并在主线程中周期处理运行(400HZ for copter)。例如从传感器的前端代码中读取加速度计、陀螺仪数据等。

其中,为了不阻碍主线程的运行,IIC和SPI通信在后台线程中运行。但主线程中可以调用USART接口函数,因为为底层的串行驱动程序本身在后台收集数据保存在一个缓冲区中。
飞行控制上层代码调用设备前段代码示例
sensors.cpp文件中包含有调用设备驱动前端代码,例如飞控控制以20HZ的频率调用read_rangefinder()函数而读取高度数据,而该函数内部则调用了rangefinder.update() 和rangefinder.set_estimated_terrain_height()前端代码来获取数据。摘取代码如下:
/*
read the rangefinder and update height estimate
*/
void Plane::read_rangefinder(void)
{
/* notify the rangefinder of our approximate altitude above ground to allow it to power on*/
/* during low-altitude flight when configured to power down during higher-altitude flight*/
float height;
#if AP_TERRAIN_AVAILABLE
if (terrain.status() == AP_Terrain::TerrainStatusOK && terrain.height_above_terrain(height, true)) {
rangefinder.set_estimated_terrain_height(height);
} else
#endif
{
/* use the best available alt estimate via baro above home*/
if (flight_stage == AP_Vehicle::FixedWing::FLIGHT_LAND) {
/* ensure the rangefinder is powered-on when land alt is higher than home altitude.
This is done using the target alt which we know is below us and we are sinking to it*/
height = height_above_target();
} else {
/* otherwise just use the best available baro estimate above home.*/
height = relative_altitude;
}
rangefinder.set_estimated_terrain_height(height); //设置地形估计高度
}
rangefinder.update(); //通过传感器更新高度数据
if ((rangefinder.num_sensors() > 0) && should_log(MASK_LOG_SONAR)) {
Log_Write_Sonar();
}
rangefinder_height_update();
}
以下代码为rangefinder.update()函数内部实现
/*
update the state of the sensor by usart
*/
void AP_RangeFinder_LightWareSerial::update(void)
{
//获取缓冲区中获取的原始数据,并且将处理后的数据保存至distance_cm中,数据为true,否则为false
if (get_reading(state.distance_cm)) {
// update range_valid state based on distance measured
last_reading_ms = AP_HAL::millis(); //获取当前系统运行时间
update_status(); /*判断distance_cm数据情况,高于最大测量范围或者小于最小测量范围或者数据正常*/
} else if (AP_HAL::millis() - last_reading_ms > 200) { /* 超过200ms缓冲没有数据 */
set_status(RangeFinder::RangeFinder_NoData);
}
}
串口设备后端实现示例
此处以获取LightWare数据为例,首先需通过serial_manager类和用户设置的参数获取串口设备对象实例。代码如下:
/*
The constructor also initialises the rangefinder. Note that this
constructor is not called until detect() returns true, so we
already know that we should setup the rangefinder
*/
AP_RangeFinder_LightWareSerial::AP_RangeFinder_LightWareSerial(RangeFinder::RangeFinder_State &_state, AP_SerialManager &serial_manager) : AP_RangeFinder_Backend(_state)
{
uart = serial_manager.find_serial(AP_SerialManager::SerialProtocol_Lidar, 0);
if (uart != nullptr) {
uart->begin(serial_manager.find_baudrate(AP_SerialManager::SerialProtocol_Lidar, 0));
}
}
前端代码在读取串口数据之前,需每次调用update()方法获取串口接受缓冲区中的数据,update方法中则调用的get_reading()方法将数据读取的内存中进行数据处理。其中关于update的代码可见之前的rangefinder.update()代码的实现,另外get_reading()代码如下:
// read - return last value measured by sensor
bool AP_RangeFinder_LightWareSerial::get_reading(uint16_t &reading_cm)
{
if (uart == nullptr) {
return false;
}
// read any available lines from the lidar
float sum = 0;
uint16_t count = 0;
int16_t nbytes = uart->available(); //检测串口接收缓冲区中的数据个数
while (nbytes-- > 0) { //将缓冲区的数据读出,可能会读到多组数据
char c = uart->read(); //获取一个字符
if (c == '\r') { //一组数据以'\r'为结尾
linebuf[linebuf_len] = 0;
sum += (float)atof(linebuf); //将浮点字符串转换成字符串
count++;
linebuf_len = 0;
} else if (isdigit(c) || c == '.') { //判断数据是否有效
linebuf[linebuf_len++] = c;
if (linebuf_len == sizeof(linebuf)) {
// too long, discard the line
linebuf_len = 0;
}
}
}
// we need to write a byte to prompt another reading
uart->write('d');
if (count == 0) {
return false; //无数据返回false
}
reading_cm = 100 * sum / count; //单位换算成cm,并且求多组数据的平均值
return true; //有数据返回true
}
因系统中每个串口都有自己接收缓冲区,所以主线程中可以直接调用get_reading()方法,而不影响其性能。而IIC和SPI通信则需要通过另外的机制来获取数据。
IIC后端代码实例
前端代码通过指定IIC设备的地址而对IIC实例对象进行初始化,初始化代码位于RangeFinder.cpp文件中的RangeFinder::detect_instance(uint8_t instance)函数中:
case RangeFinder_TYPE_LWI2C:
if (state[instance].address) {
#ifdef HAL_RANGEFINDER_LIGHTWARE_I2C_BUS
_add_backend(AP_RangeFinder_LightWareI2C::detect(state[instance],
hal.i2c_mgr->get_device(HAL_RANGEFINDER_LIGHTWARE_I2C_BUS, state[instance].address)));
#else
if (!_add_backend(AP_RangeFinder_LightWareI2C::detect(state[instance],
hal.i2c_mgr->get_device(1, state[instance].address)))) {
_add_backend(AP_RangeFinder_LightWareI2C::detect(state[instance],
hal.i2c_mgr->get_device(0, state[instance].address)));
}
#endif
}
break;
其中代码
hal.i2c_mgr->get_device(HAL_RANGEFINDER_LIGHTWARE_I2C_BUS, state[instance].address)
通过指定IIC地址在总线上得到对应设备。指定设备之后,则可以通过调用相应的后端代码来初始化该设备与读取数据。代码如下:
void AP_RangeFinder_LightWareI2C::init()
{
// call timer() at 20Hz 以20HZ的频率执行定时器回调函数
_dev->register_periodic_callback(50000,
FUNCTOR_BIND_MEMBER(&AP_RangeFinder_LightWareI2C::timer, void));
}
// read - return last value measured by sensor
bool AP_RangeFinder_LightWareI2C::get_reading(uint16_t &reading_cm)
{
be16_t val;
if (state.address == 0) {
return false;
}
// read the high and low byte distance registers
bool ret = _dev->read((uint8_t *) &val, sizeof(val));
if (ret) {
// combine results into distance
reading_cm = be16toh(val);
}
return ret;
}
而定时回调函数中则调用了get_reading()方法获取IIC设备的数据。
SPI后端代码实例
以MPU9250 IMU后端代码介绍SPI总线后端代码的编写。获取SPI设备对象的初始化代码类似于IIC,代码位于AP_InertialSensor.cpp文件AP_InertialSensor::detect_backends(void)函数中。
_add_backend(AP_InertialSensor_Invensense::probe(*this, hal.spi->get_device(HAL_INS_MPU9250_NAME))); //获取SPI设备
此外,在后台线程中start()方法会自动调用对SPI总线上对应设备(此处为MPU9250)进行初始化和配置。程序中使用信号量区别SPI总线上的不同设备。


其中,_read_sample()方法被注册以1000HZ的频率被调用。__block_read()方法则主要从传感器寄存器中获取数据供上层代码处理。
注意
如果添加新的设备驱动程序代码,则在代码中绝对不能有任何的等待或者线程休眠代码,因为这样会影响其他线程所使用的总线。
如果想将新的驱动代码加入的工程中,则必须在make.inc和wscript文件中编写相应的工程代码,这两个文件位于对应的飞行器代码目录下(ArduPlane、ArduCopter...)。这样新编写的驱动才会参与工程代码的编译,最后一同生成可执行的二进制文件。后续可将该文件烧写至飞控处理器中运行。
Ardupilot设备驱动 IIC、SPI、USART的更多相关文章
- RT thread 设备驱动组件之USART设备
本文以stm32f4xx平台介绍串口驱动,主要目的是:1.RTT中如何编写中断处理程序:2.如何编写RTT设备驱动接口代码:3.了解串行设备的常见处理机制.所涉及的主要源码文件有:驱动框架文件(usa ...
- Linux设备驱动剖析之SPI(一)
写在前面 初次接触SPI是因为几年前玩单片机的时候,由于普通的51单片机没有SPI控制器,所以只好用IO口去模拟.最近一次接触SPI是大三时参加的校内选拔赛,当时需要用2440去控制nrf24L01, ...
- RT-thread 设备驱动组件之IIC总线设备
本文主要介绍RT-thread中IIC总线设备驱动,涉及到的主要文件有:驱动框架文件(i2c_core.c,i2c_dev.c,i2c-bit-ops.c,i2c_dev.h,i2c.h):底层硬件驱 ...
- 通信方案软件设计(环形动态申请内存,支持USART+IIC+SPI+CAN协议
1 <STM32进阶之串口环形缓冲区实现>中讲得比较清楚(链接) 2 amobbs中讲的方法有点复杂,以下是链接和参考源码: 通信方案软件设计(环形动态申请内存,支持USART+IIC+S ...
- Linux设备驱动剖析之IIC(二)
953行,适配器的编号大于MAX_ID_MASK是不行的,MAX_ID_MASK是一个宏,展开后的值为61. 957至968行,关于管理小整形ID数的,没怎么了解,略过. 974行,调用i2c_reg ...
- Linux设备驱动剖析之IIC(一)
写在前面 由于IIC总线只需要两根线就可以完成读写操作,而且通信协议简单,一条总线上可以挂载多个设备,因此被广泛使用.但是IIC总线有一个缺点,就是传输速率比较低.本文基于Linux-2.6.36版本 ...
- Linux设备驱动剖析之SPI(三)
572至574行,分配内存,注意对象的类型是struct spidev_data,看下它在drivers/spi/spidev.c中的定义: struct spidev_data { dev_t de ...
- Linux设备驱动剖析之SPI(二)
957至962行,一个SPI控制器用一个master来描述.这里使用SPI核心的spi_alloc_master函数请求分配master.它在drivers/spi/spi.c文件中定义: struc ...
- spi驱动框架全面分析,从master驱动到设备驱动
内核版本:linux2.6.32.2 硬件资源:s3c2440 参考: 韦东山SPI视频教程 内容概括: 1.I2C 驱动框架回顾 2.SPI 框架简单介绍 3.maste ...
随机推荐
- sql+PHP基础+面向对象基础简单总结
一.MYSQL 1.配置MySql 第一步安装服务器(apache). 第二部安装MySql界面程序 2 ...
- Akka(26): Stream:异常处理-Exception handling
akka-stream是基于Actor模式的,所以也继承了Actor模式的“坚韧性(resilient)”特点,在任何异常情况下都有某种整体统一的异常处理策略和具体实施方式.在akka-stream的 ...
- BackTrack 5无线网卡混杂模式设置
用ifconfig查看网络设备 主机无线网卡名称一般为wlan0,USB网卡一般为wlan1 虚拟机中USB网卡一般无法自动识别,可以用ifconfig wlan1 up启用 用ifconfig wl ...
- Maven 整合strut与Hibernate,获取不到Session
struts使用的是2.3.24 Hibernate使用的5.0.7 注意hebernate一定要在struts之前申明,不然容易出现500错误, <project xmlns="ht ...
- css之outline实现圆角效果
对,你没有看错,outline也可以实现圆角,不过请先读完自然会知道结果: ============================================================== ...
- 一个完整的Node.js RESTful API
前言 这篇文章算是对Building APIs with Node.js这本书的一个总结.用Node.js写接口对我来说是很有用的,比如在项目初始阶段,可以快速的模拟网络请求.正因为它用js写的,跟i ...
- 《深入浅出设计模式》读书笔记 C#版(第一章)
原始需求和设计 事情是这样开始的,公司需要做一套程序,鸭子,设计如下: 一个鸭子父类,多个派生类,三个可override的方法. 第一次需求变更 我们要会飞的鸭子!!!!! 所以我们做了如下的更改: ...
- Centos7搭建swarm集群
1. 准备 两台虚拟机,IP分别为: 192.168.1.104 192.168.1.105 保证能互相 ping 通 2. 修改虚拟机的 host,分别任 c1.c2 在 192.168.1.105 ...
- 五年.net程序员转型Java之路
大学毕业后笔者进入一家外企,做企业CRM系统开发,那时候开发效率最高的高级程序语言,毫无疑问是C#.恰逢公司也在扩张,招聘了不少.net程序员,笔者作为应届生,也乐呵呵的加入到.net程序员行列中. ...
- php中常用的字符串大小写转换函数实例解释
PHP字符串处理函数中,最为简单的几个函数,相关解释就不上了,直接看例子. PHP字符串处理函数中,最为简单的几个函数,相关解释就不上了,直接看例子. strtolower函数.strtoupper函 ...