ArduPilot第4章rangefinder


参考文献
https://blog.csdn.net/weixin_43321489/article/details/133312281
https://blog.csdn.net/qq_20016593/article/details/132715941

The Cube Orange 标准套装
https://muginuav.en.made-in-china.com/product/cwmQPxIEIrWb/China-Pixhawk-Autopilot-The-Cube-Orange-Standard-Set.html

Ardupilot设备驱动 IIC、SPI、USART
https://www.cnblogs.com/BlogsOfLei/p/7745636.html

APM代码阅读(一):串口驱动
https://blog.51cto.com/u_15352549/6507034

ArduPilot之开源代码Sensor Drivers设计
https://blog.csdn.net/lida2003/article/details/130438349

前言

以测距传感器为例,介绍ArduPilot传感器开发流程


一、传感器代码结构

ardupilot中传感器驱动的重要结构是前台和后台分离。
第1步 Library库调用前端程序
第2步 前端程序调用后端程序
第3步 后端程序一般是个for循环

二、传感器初始化

以测距传感器为例,讲解ardupilot是如何初始化传感器驱动

1.void AP_Vehicle::setup()*

此函数仅在启动时调用一次。用于初始化一些必要的任务。此函数由 HAL 中的 main() 函数调用。
\libraries\AP_Vehicle\AP_Vehicle.cpp

void AP_Vehicle::setup()
{
	init_ardupilot();
}

2.AP_Vehicle& vehicle = copter

\ArduCopter\ArduCopter.cpp

Copter copter;
AP_Vehicle& vehicle = copter;

3.void Copter::init_ardupilot()

init_ardupilot() 函数将处理空中重启所需的一切。稍后将确定飞行器是否真的在地面上,并在这种情况下处理地面启动。
\ArduCopter\system.cpp

void Copter::init_ardupilot()
{
	init_rangefinder();
}

4.void Copter::init_rangefinder(void)

测距仪初始化。此函数会初始化朝下的测距仪
\ArduCopter\sensors.cpp

void Copter::init_rangefinder(void)
{
#if RANGEFINDER_ENABLED == ENABLED
   rangefinder.set_log_rfnd_bit(MASK_LOG_CTUN);
   rangefinder.init(ROTATION_PITCH_270);
   rangefinder_state.alt_cm_filt.set_cutoff_frequency(g2.rangefinder_filt);
   rangefinder_state.enabled = rangefinder.has_orientation(ROTATION_PITCH_270);

   // upward facing range finder
   rangefinder_up_state.alt_cm_filt.set_cutoff_frequency(g2.rangefinder_filt);
   rangefinder_up_state.enabled = rangefinder.has_orientation(ROTATION_PITCH_90);
#endif
}

5.RangeFinder rangefinder

在 AP_Vehicle.h 文件中,我们用 RangeFinder 类定义了 rangefinder 对象。
\libraries\AP_Vehicle\AP_Vehicle.h

class AP_Vehicle : public AP_HAL::HAL::Callbacks {
{
	protected:
	RangeFinder rangefinder;	
}

6.RangeFinder::init

所以,在跳转 init() 这个成员函数的时候,跳转到 RangeFinder 类的 init() 函数。

初始化 RangeFinder 类。将在这里检测已连接的测距仪。目前还不允许热插拔测距仪。
\libraries\AP_RangeFinder\AP_RangeFinder.cpp

void RangeFinder::init(enum Rotation orientation_default)
{
    if (init_done) {
        // init called a 2nd time?
        return;
    }
    init_done = true;

    convert_params();

    // set orientation defaults
    for (uint8_t i=0; i<RANGEFINDER_MAX_INSTANCES; i++) {
        params[i].orientation.set_default(orientation_default);
    }

    for (uint8_t i=0, serial_instance = 0; i<RANGEFINDER_MAX_INSTANCES; i++) {
        // serial_instance will be increased inside detect_instance
        // if a serial driver is loaded for this instance
        WITH_SEMAPHORE(detect_sem);
        detect_instance(i, serial_instance);
        if (drivers[i] != nullptr) {
            // we loaded a driver for this instance, so it must be
            // present (although it may not be healthy). We use MAX()
            // here as a UAVCAN rangefinder may already have been
            // found
            num_instances = MAX(num_instances, i+1);
        }

        // initi

7.RangeFinder::detect_instance

检测是否连接了测距仪实例。

根据在文章开始介绍的 RNGFND1_TYPE 参数,选择不同传感器进行检测。

有三种通信后端可以选择
串口后端
IIC后端
SPI后端

此处,以 RNGFND1_TYPE(16) 中 AP_RangeFinder_VL53L0X 为例介绍I2C后端。
前端代码通过指定IIC设备的地址而对IIC实例对象进行初始化,初始化代码位于RangeFinder.cpp文件中的RangeFinder::detect_instance函数中:

其中,hal.i2c_mgr->get_device(…)
通过指定IIC地址在总线上得到对应设备

\libraries\AP_RangeFinder\AP_RangeFinder.cpp

void RangeFinder::detect_instance(uint8_t instance, uint8_t& serial_instance)
{
    const Type _type = (Type)params[instance].type.get();
    switch (_type) {
    case Type::PLI2C:
    case Type::PLI2CV3:
    case Type::PLI2CV3HP:
        FOREACH_I2C(i) {
            if (_add_backend(AP_RangeFinder_PulsedLightLRF::detect(i, state[instance], params[instance], _type),
                             instance)) {
                break;
            }
        }
        break;
    ...
    //I2C后端
    case Type::VL53L0X:
    case Type::VL53L1X_Short:
            FOREACH_I2C(i) {
                if (_add_backend(AP_RangeFinder_VL53L0X::detect(state[instance], params[instance],
                                                                hal.i2c_mgr->get_device(i, params[instance].address)),
                        instance)) {
                    break;
                }
                if (_add_backend(AP_RangeFinder_VL53L1X::detect(state[instance], params[instance],
                                                                hal.i2c_mgr->get_device(i, params[instance].address),
                                                                _type == Type::VL53L1X_Short ?  AP_RangeFinder_VL53L1X::DistanceMode::Short :
                                                                AP_RangeFinder_VL53L1X::DistanceMode::Long),
                                 instance)) {
                    break;
                }
            }
        break;
   ...
  
    
    //串口后端
   case Type::LWSER:
        if (AP_RangeFinder_LightWareSerial::detect(serial_instance)) {
            _add_backend(new AP_RangeFinder_LightWareSerial(state[instance], params[instance]), instance, serial_instance++);
        }
     

三、后端调用

1.I2C后端

此处,以 RNGFND1_TYPE(16) 中 AP_RangeFinder_VL53L0X 为例介绍I2C后端。

1.1 hal.i2c_mgr->get_device

(1)
\libraries\AP_HAL\I2CDevice.h

class I2CDeviceManager {
public:
    /* Get a device handle */
    virtual OwnPtr<AP_HAL::I2CDevice> get_device(uint8_t bus, uint8_t address,
                                                 uint32_t bus_clock=400000,
                                                 bool use_smbus = false,
                                                 uint32_t timeout_ms=4) = 0;
    /*
     * Get device by looking up the I2C bus on the buses from @devpaths.
     *
     * Each string in @devpaths are possible locations for the bus. How the
     * strings are implemented are HAL-specific. On Linux this is the info
     * returned by 'udevadm info -q path /dev/i2c-X'. The first I2C bus
     * matching a prefix in @devpaths is used to create a I2CDevice object.
     */
    virtual OwnPtr<I2CDevice> get_device(std::vector<const char *> devpaths,
                                         uint8_t address) {
        // Not implemented
        return nullptr;
    }

(2)

\libraries\AP_HAL_Linux\I2CDevice.cpp
如果当前的 bus 没有超过 i2c_bus_desc,会 new 一个新的 I2CDevice 类对象返回。

AP_HAL::OwnPtr<AP_HAL::I2CDevice>
I2CDeviceManager::get_device(std::vector<const char *> devpaths, uint8_t address)
{
    const char *dirname = "/sys/class/i2c-dev/";
    struct dirent *de = nullptr;
    DIR *d;

    d = opendir(dirname);
    if (!d) {
        AP_HAL::panic("Could not get list of I2C buses");
    }

    for (de = readdir(d); de; de = readdir(d)) {
        char *str_device, *abs_str_device;
        const char *p;

        if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
            continue;
        }

        if (asprintf(&str_device, "%s/%s", dirname, de->d_name) < 0) {
            continue;
        }

        abs_str_device = realpath(str_device, nullptr);
        if (!abs_str_device || !(p = startswith(abs_str_device, "/sys/devices/"))) {
            free(abs_str_device);
            free(str_device);
            continue;
        }

        auto t = std::find_if(std::begin(devpaths), std::end(devpaths),
                              [p](const char *prefix) {
                                  return startswith(p, prefix) != nullptr;
                              });

        free(abs_str_device);
        free(str_device);

        if (t != std::end(devpaths)) {
            unsigned int n;

            /* Found the bus, try to create the device now */
            if (sscanf(de->d_name, "i2c-%u", &n) != 1) {
                AP_HAL::panic("I2CDevice: can't parse %s", de->d_name);
            }
            if (n > UINT8_MAX) {
                AP_HAL::panic("I2CDevice: bus with number n=%u higher than %u",
                              n, UINT8_MAX);
            }

            closedir(d);
            return get_device(n, address);
        }
    }

1.2.AP_RangeFinder_VL53L0X::detect(…)

检测是否连接了 VL53L0X 测距仪。我们将通过 I2C 读取数据来进行检测。如果得到结果,则表示传感器已连接。

首先会根据传入的参数,new 一个 AP_RangeFinder_VL53L0X 类对象。

然后读取测距仪固有的产品 ID 是否正确(sensor->check_id()),再对测距仪进行必要的初始化(sensor->init())。

\libraries\AP_RangeFinder\AP_RangeFinder_VL53L0X.cpp

AP_RangeFinder_Backend *AP_RangeFinder_VL53L0X::detect(RangeFinder::RangeFinder_State &_state, AP_RangeFinder_Params &_params, AP_HAL::OwnPtr<AP_HAL::I2CDevice> dev)
{
	if (!dev) {
		return nullptr;
	}
    AP_RangeFinder_VL53L0X *sensor
        = new AP_RangeFinder_VL53L0X(_state, _params, std::move(dev));

    if (!sensor) {
        delete sensor;
        return nullptr;
    }

    sensor->dev->get_semaphore()->take_blocking();
    
    if (!sensor->check_id() || !sensor->init()) {
        sensor->dev->get_semaphore()->give();
        delete sensor;
        return nullptr;
    }

    sensor->dev->get_semaphore()->give();
    
    return sensor;
}

在7中的void RangeFinder::detect_instance函数中调用AP_RangeFinder_VL53L0X::detect函数

1.3 bool AP_RangeFinder_VL53L0X::check_id(void)

检查测距仪 ID 寄存器。每种类型测距仪的 ID 寄存器都有唯一值。

bool AP_RangeFinder_VL53L0X::check_id(void)
{
    uint8_t v1, v2;
    if (!dev->read_registers(0xC0, &v1, 1) ||
        !dev->read_registers(0xC1, &v2, 1) ||
        v1 != 0xEE ||
        v2 != 0xAA) {
        return false;
    }
    printf("Detected VL53L0X on bus 0x%x\n", dev->get_bus_id());
    return true;
}

1.4 bool AP_RangeFinder_VL53L0X::init()

初始化传感器,并注册测距仪的周期运行函数 timer()。

bool AP_RangeFinder_VL53L0X::init() {
{
	...
    通过I2C向传感器寄存器写入初始值。
    ...
 
    // call timer() every 50ms. We expect new data to be available every 50ms
    dev->register_periodic_callback(50000, FUNCTOR_BIND_MEMBER(&AP_RangeFinder_VL53L3CX::timer, void));
 
    return true;

}

1.5 void AP_RangeFinder_VL53L0X::timer(void)

20Hz 调用一次该函数,读取测距仪的测量值。

void AP_RangeFinder_VL53L0X::timer(void)
{
    uint16_t range_mm;
    if (get_reading(range_mm) && range_mm < 8000) {
        sum_mm += range_mm;
        counter++;
    }
}

1.6 AP_RangeFinder_VL53L0X::get_reading函数

// read - return last value measured by sensor
bool AP_RangeFinder_VL53L0X::get_reading(uint16_t &reading_mm)
{
    if ((read_register(RESULT_INTERRUPT_STATUS) & 0x07) == 0) {
        if (AP_HAL::millis() - start_ms > 200) {
            start_continuous();
        }
        return false;
    }

    // assumptions: Linearity Corrective Gain is 1000 (default);
    // fractional ranging is not enabled
    reading_mm = read_register16(RESULT_RANGE_STATUS + 10);
    write_register(SYSTEM_INTERRUPT_CLEAR, 0x01);

    return true;
}

2.串口后端

此处以获取LightWare数据,为例介绍串口后端。

2.1 RangeFinder::_add_backend函数

\libraries\AP_RangeFinder\AP_RangeFinder.cpp

bool RangeFinder::_add_backend(AP_RangeFinder_Backend *backend, uint8_t instance, uint8_t serial_instance)
{
    if (!backend) {
        return false;
    }
    if (instance >= RANGEFINDER_MAX_INSTANCES) {
        AP_HAL::panic("Too many RANGERS backends");
    }
    if (drivers[instance] != nullptr) {
        // we've allocated the same instance twice
        INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
    }
    backend->init_serial(serial_instance);
    drivers[instance] = backend;
    num_instances = MAX(num_instances, instance+1);

    return true;
}

2.2 AP_RangeFinder_Backend_Serial::init_serial函数

此处以获取LightWare数据为例,首先需通过serial_manager类和用户设置的参数获取串口设备对象实例

\libraries\AP_RangeFinder\AP_RangeFinder_Backend_Serial.cpp

void AP_RangeFinder_Backend_Serial::init_serial(uint8_t serial_instance)
{
    uart = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_Rangefinder, serial_instance);
    if (uart != nullptr) {
        uart->begin(initial_baudrate(serial_instance), rx_bufsize(), tx_bufsize());
    }
}

2.3 AP_RangeFinder_Backend_Serial::detect函数

\libraries\AP_RangeFinder\AP_RangeFinder_Backend_Serial.cpp

bool AP_RangeFinder_Backend_Serial::detect(uint8_t serial_instance)
{
    return AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_Rangefinder, serial_instance);
}

2.4 AP_SerialManager::have_serial函数

\libraries\AP_SerialManager\AP_SerialManager.cpp

// have_serial - return true if we have the given serial protocol configured
bool AP_SerialManager::have_serial(enum SerialProtocol protocol, uint8_t instance) const
{
    return find_protocol_instance(protocol, instance) != nullptr;
}

2.5 AP_SerialManager::UARTState *AP_SerialManager::find_protocol_instance函数

\libraries\AP_SerialManager\AP_SerialManager.cpp

const AP_SerialManager::UARTState *AP_SerialManager::find_protocol_instance(enum SerialProtocol protocol, uint8_t instance) const
{
    uint8_t found_instance = 0;

    // search for matching protocol
    for(uint8_t i=0; i<SERIALMANAGER_NUM_PORTS; i++) {
        if (protocol_match(protocol, (enum SerialProtocol)state[i].protocol.get())) {
            if (found_instance == instance) {
                return &state[i];
            }
            found_instance++;
        }
    }

    // if we got this far we did not find the uart
    return nullptr;
}

3.SPI后端

    ....

四、前端调用

1.前端

1.1 SCHED_TASK(read_rangefinder, 20, 100, 33)函数
Copter.cpp 文件的周期任务列表中注册了一个线程:SCHED_TASK(read_rangefinder, 20, 100, 33)
注册了调用频率为 20Hz 的 read_rangefinder() 函数。
在\ArduCopter\Copter.cpp中

const AP_Scheduler::Task Copter::scheduler_tasks[] = {
	...
	SCHED_TASK(read_rangefinder,      20,    100,  33),
	...
}

1.2 read_rangefinder函数
在 sensors.cpp 文件中,read_rangefinder()调用rangefinder.update()函数
以厘米为单位返回测距仪高度
sensors.cpp文件中包含有调用设备驱动前端代码,例如飞控控制以20HZ的频率调用read_rangefinder()函数而读取高度数据,而该函数内部则调用了rangefinder.update()前端代码来获取数据

在\ArduCopter\sensors.cpp中

void Copter::read_rangefinder(void)
{
#if RANGEFINDER_ENABLED == ENABLED
    //通过传感器更新高度数据
    rangefinder.update();
    ...
}

1.3 RangeFinder::update函数

update实现代码在:
\libraries\AP_RangeFinder\AP_RangeFinder.cpp中

请添加图片描述

1.4 调用传感器类中的update函数

(1)
AP_RangeFinder_VL53L0X 对象
根据 返回的 AP_RangeFinder_VL53L0X 对象,调用 AP_RangeFinder_VL53L0CX 类中的 update() 函数。

\libraries\AP_RangeFinder\AP_RangeFinder_VL53L0X.cpp中

void AP_RangeFinder_VL53L0X::update(void)
{
    if (counter > 0) {
        state.distance_m = (sum_mm * 0.001f) / counter;
        state.last_reading_ms = AP_HAL::millis();
        sum_mm = 0;
        counter = 0;
        update_status();
    } else {
        set_status(RangeFinder::Status::NoData);
    }
}

(2)
如果返回的为AP_RangeFinder_LightWareSerial,调用 AP_RangeFinder_Backend_Serial中的 update() 函数。
libraries\AP_RangeFinder\AP_RangeFinder_Backend_Serial.cpp中

void AP_RangeFinder_Backend_Serial::update(void)
{
	//获取缓冲区中获取的原始数据,并且将处理后的数据保存至distance_cm中,数据为true,否则为false
    if (get_reading(state.distance_m)) {
        // update range_valid state based on distance measured
        state.last_reading_ms = AP_HAL::millis();//获取当前系统运行时间
        //判断distance_cm数据情况,高于最大测量范围或者小于最小测量范围或者数据正常
        update_status();
    	 // 超过200ms缓冲没有数据 
    } else if (AP_HAL::millis() - state.last_reading_ms > read_timeout_ms()) {
        set_status(RangeFinder::Status::NoData);
    }
}

前端代码在读取串口数据之前,需每次调用update()方法获取串口接受缓冲区中的数据,update方法中则调用的get_reading()方法将数据读取的内存中进行数据处理。其中关于update的代码可见之前的rangefinder.update()代码的实现,另外get_reading()代码如下:
进入AP_RangeFinder_LightWareSerial::get_reading函数
\libraries\AP_RangeFinder\AP_RangeFinder_LightWareSerial.cpp

// read - return last value measured by sensor
bool AP_RangeFinder_LightWareSerial::get_reading(float &reading_m)
{
    if (uart == nullptr) {
        return false;
    }

    float sum = 0;              // sum of all readings taken
    uint16_t valid_count = 0;   // number of valid readings
    uint16_t invalid_count = 0; // number of invalid readings

    // max distance the sensor can reliably measure - read from parameters
    const int16_t distance_cm_max = max_distance_cm();

    // read any available lines from the lidar
    //检测串口接收缓冲区中的数据个数
    int16_t nbytes = uart->available();
    //将缓冲区的数据读出,可能会读到多组数据
    while (nbytes-- > 0) {
        //获取一个字符
        char c = uart->read();
        // use legacy protocol
        if (protocol_state == ProtocolState::UNKNOWN || protocol_state == ProtocolState::LEGACY) {
            //一组数据以'\r'为结尾
            if (c == '\r') {
                linebuf[linebuf_len] = 0;
                const float dist = strtof(linebuf, nullptr);
                if (!is_negative(dist) && !is_lost_signal_distance(dist * 100, distance_cm_max)) {
                    sum += dist;
                    valid_count++;
                    // if still determining protocol update legacy valid count
                    if (protocol_state == ProtocolState::UNKNOWN) {
                        legacy_valid_count++;
                    }
                } else {
                    invalid_count++;
                }
                linebuf_len = 0;
            //判断数据是否有效
            } else if (isdigit(c) || c == '.' || c == '-') {
                linebuf[linebuf_len++] = c;
                if (linebuf_len == sizeof(linebuf)) {
                    // too long, discard the line
                    linebuf_len = 0;
                }
            }
        }
        // use binary protocol
        if (protocol_state == ProtocolState::UNKNOWN || protocol_state == ProtocolState::BINARY) {
            bool msb_set = BIT_IS_SET(c, 7);
            if (msb_set) {
                // received the high byte
                high_byte = c;
                high_byte_received = true;
            } else {
                // received the low byte which should be second
                if (high_byte_received) {
                    const int16_t dist = (high_byte & 0x7f) << 7 | (c & 0x7f);
                    if (dist >= 0 && !is_lost_signal_distance(dist, distance_cm_max)) {
                        sum += dist * 0.01f;
                        valid_count++;
                        // if still determining protocol update binary valid count
                        if (protocol_state == ProtocolState::UNKNOWN) {
                            binary_valid_count++;
                        }
                    } else {
                        invalid_count++;
                    }
                }
                high_byte_received = false;
            }
        }
    }
    // protocol set after 10 successful reads
    if (protocol_state == ProtocolState::UNKNOWN) {
        if (binary_valid_count > 10) {
            protocol_state = ProtocolState::BINARY;
        } else if (legacy_valid_count > 10) {
            protocol_state = ProtocolState::LEGACY;
        }
    }

    uint32_t now = AP_HAL::millis();
    if (last_init_ms == 0 ||
        (now - last_init_ms > 1000 &&
         now - state.last_reading_ms > 1000)) {
        // send enough serial transitions to trigger LW20 into serial
        // mode. It starts in dual I2C/serial mode, and wants to see
        // enough transitions to switch into serial mode.
        uart->write("www\r\n");
        last_init_ms = now;
    } else {
        uart->write('d');
    }

    // return average of all valid readings
    if (valid_count > 0) {
        reading_m = sum / valid_count;
        no_signal = false;
        return true;
    }

    // all readings were invalid so return out-of-range-high value
    if (invalid_count > 0) {
        reading_m = MIN(MAX(LIGHTWARE_DIST_MAX_CM, distance_cm_max + LIGHTWARE_OUT_OF_RANGE_ADD_CM), UINT16_MAX) * 0.01f;
        no_signal = true;
        return true;
    }

    // no readings so return false
    return false;
}

总结

以上就是今天要讲的内容,本文介绍ArduPilot的·rangefinder。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值