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 ...
随机推荐
- 万物皆对象的JavaScript
写在前面: 经过前段时间对于H5语言的学习,开始慢慢适应现在的生活节奏和代码语言.从一开始的毫无头绪到现在可以大概地写出部分的网站静态页面,用了两个周的时间.作为一个之前从未接触过计算机语言的初学者来 ...
- 多线程:head first Thread.join()
不使用Thread.join() 测试线程 先上代码: /** * Created by Zero on 2017/8/23. */ public class TestJoin implements ...
- Linux下的定时任务 - Cron服务
最近搞咕自己的笔记系统,虽然现在是个人的使用,对于数据库的数据还是比较少,但是安全还是一个我必须注意的东西. (特别是前段时间中了比特币的病毒之后,更是让我关注了我的主机的安全的问题.) 今天的随记是 ...
- 小甲鱼:Python学习笔记001_变量_分支_数据类型_运算符等基础
1.下载并安装Python 3.5.0 Python是一个跨平台语言,Python从3.0的版本的语法很多不兼容2版本,官网找到最新的版本并下载:http://www.python.org 2.IDL ...
- Apache Spark 2.2.0 中文文档 - 快速入门 | ApacheCN
快速入门 使用 Spark Shell 进行交互式分析 基础 Dataset 上的更多操作 缓存 独立的应用 快速跳转 本教程提供了如何使用 Spark 的快速入门介绍.首先通过运行 Spark 交互 ...
- 【Spring源码深度解析系列 】Spring整体架构
一.Spring的整体架构和模块 二.模块分类: 1.Core Container Core Container包含有Core .Beans.Context.和Expression Language ...
- global,local,static的区别
1.在函数内部使用global关键字定义的变量可以成为全局变量,如果该变量已经被定义了,那么他的值就是原来的值,否则就是一个新的全局变量(一句话:已存在就不再创建): <?php $a=1; f ...
- Linux脚本入门(1)
1. Linux 脚本编写基础1.1 语法基本介绍1.1.1 开头程序必须以下面的行开始(必须方在文件的第一行): #!/bin/sh 符号#!用来告诉系统它后面的参数是用来执行该文件的程序.在这个例 ...
- 如何修改select标签的默认下拉箭头样式?
对于一般的项目而言,select标签在浏览器中表现出来的默认样式也不算丑,但是一次项目中,项目经理却要求对select标签本身进行样式修改,美化其下拉小箭头的样式.我思考和尝试了许多方法,最终得到一种 ...
- java中属性,set get 以及如何学习类的一些用法
1,先来看一个例子 package com.tdq.java; public class Run { public static void main(String[]args){ Student st ...