ArduPilot开源代码之AP_IOMCU

1. 源由

之前我们讨论了FMU + IMU的硬件架构,该设计主要是由于STM32外设IO、MCU计算资源不够应付外设的情况下,采用的一种折中的方案。

-ArduPilot开源代码之FMU+IOMCU设计
-Ardupilot开源飞控之IOMCU设计细节

随着技术的日益发展,更高效,更多的IO资源的MCU芯片已经具备。当然在特殊应用领域或者方案,仍然可以考虑采用这一方案。

为此,我们研究下AP_IOMCU应用类:FMU与IOMCU通信实现。

注:参考Pixhawk4硬件飞控板。

2. 框架设计

hwdef.h头文件定义FMU端通信端口UART8.

#define HAL_UART_IOMCU_IDX 8
#define HAL_UART_IO_DRIVER ChibiOS::UARTDriver uart_io(HAL_UART_IOMCU_IDX)

libraries/AP_HAL_ChibiOS/HAL_ChibiOS_Class.cpp初始化FMU串口实例.

#if HAL_WITH_IO_MCU
HAL_UART_IO_DRIVER;
#include <AP_IOMCU/AP_IOMCU.h>
AP_IOMCU iomcu(uart_io);
#endif

2.1 启动代码

AP_Vehicle::setup
 └──> AP_BoardConfig::init
     └──> AP_BoardConfig::board_setup
         └──> hal.rcout->init();
             └──> AP_IOMCU::init
                 └──> !hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_IOMCU::thread_main, void)

2.2 初始化

采用1.5Mbps,默认(测试)确保,当前连接在无CTS/RTS的方式下进行通信无异常。

void AP_IOMCU::init(void)
{
    // uart runs at 1.5MBit, 不知道为何,因为后面`thread_main`里面仍然要初始化一遍。难道是为了`check_crc`
    uart.begin(1500*1000, 128, 128);
    uart.set_unbuffered_writes(true);

#if IOMCU_DEBUG_ENABLE
    crc_is_ok = true;
#else
    AP_BoardConfig *boardconfig = AP_BoardConfig::get_singleton();
    if ((!boardconfig || boardconfig->io_enabled() == 1) && !hal.util->was_watchdog_reset()) {
        check_crc();
    } else {
        crc_is_ok = true;
    }
#endif

    if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_IOMCU::thread_main, void), "IOMCU",
                                      1024, AP_HAL::Scheduler::PRIORITY_BOOST, 1)) {
        AP_HAL::panic("Unable to allocate IOMCU thread");
    }
    initialised = true;
}

2.3 动态过程

AP_IOMCU::thread_main(void)
|
|-- 初始化部分
|   |-- thread_ctx = chThdGetSelfX()
|   |-- chEvtSignal(thread_ctx, initial_event_mask)
|   |-- uart.begin(1500*1000, 128, 128)
|   |-- uart.set_unbuffered_writes(true)
|
|-- 条件编译(DSHOT 相关配置)
|   |-- HAL_WITH_IO_MCU_BIDIR_DSHOT
|       |-- erpm_period_ms = 10
|       |-- HAVE_AP_BLHELI_SUPPORT
|           |-- blh = AP_BLHeli::get_singleton()
|           |-- erpm_period_ms = constrain_int16(1000 / blh->get_telemetry_rate(), 1, 1000)
|
|-- 触发初始化事件
|   |-- trigger_event(IOEVENT_INIT)
|
|-- 主循环
    |
    |-- 检查与 IOMCU 的通信
    |   |-- now_ms = AP_HAL::millis()
    |   |-- last_reg_access_ms 检查
    |       |-- INTERNAL_ERROR(AP_InternalError::error_t::iomcu_reset)
    |
    |-- 等待事件
    |   |-- mask = chEvtWaitAnyTimeout(~0, chTimeMS2I(10))
    |
    |-- 处理各类 IO 事件
        |
        |-- IOEVENT_SEND_PWM_OUT
        |   |-- send_servo_out()
        |
        |-- IOEVENT_INIT
        |   |-- 读取协议版本
        |       |-- read_registers()
        |       |-- is_chibios_backend 赋值
        |       |-- DEV_PRINTF
        |       |-- modify_register()
        |       |-- AP_IOMCU_FORCE_ENABLE_HEATER
        |           |-- modify_register()
        |
        |-- IOEVENT_MIXING
        |   |-- write_registers(PAGE_MIXING, ...)
        |
        |-- IOEVENT_FORCE_SAFETY_OFF
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_FORCE_SAFETY_OFF, FORCE_SAFETY_MAGIC)
        |
        |-- IOEVENT_FORCE_SAFETY_ON
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_FORCE_SAFETY_ON, FORCE_SAFETY_MAGIC)
        |
        |-- IOEVENT_SET_RATES
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_ALTRATE, rate.freq)
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_PWM_RATE_MASK, rate.chmask)
        |
        |-- IOEVENT_ENABLE_SBUS
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_SBUS_RATE, rate.sbus_rate_hz)
        |   |-- modify_register(PAGE_SETUP, PAGE_REG_SETUP_FEATURES, 0, P_SETUP_FEATURES_SBUS1_OUT)
        |
        |-- IOEVENT_SET_HEATER_TARGET
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_HEATER_DUTY_CYCLE, heater_duty_cycle)
        |
        |-- IOEVENT_SET_DEFAULT_RATE
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_DEFAULTRATE, rate.default_freq)
        |
        |-- IOEVENT_SET_DSHOT_PERIOD
        |   |-- write_registers(PAGE_SETUP, PAGE_REG_SETUP_DSHOT_PERIOD, ...)
        |
        |-- IOEVENT_SET_ONESHOT_ON
        |   |-- modify_register(PAGE_SETUP, PAGE_REG_SETUP_FEATURES, 0, P_SETUP_FEATURES_ONESHOT)
        |
        |-- IOEVENT_SET_BRUSHED_ON
        |   |-- modify_register(PAGE_SETUP, PAGE_REG_SETUP_FEATURES, 0, P_SETUP_FEATURES_BRUSHED)
        |
        |-- IOEVENT_SET_OUTPUT_MODE
        |   |-- write_registers(PAGE_SETUP, PAGE_REG_SETUP_OUTPUT_MODE, ...)
        |
        |-- IOEVENT_SET_CHANNEL_MASK
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_CHANNEL_MASK, pwm_out.channel_mask)
        |
        |-- IOEVENT_SET_SAFETY_MASK
        |   |-- write_register(PAGE_SETUP, PAGE_REG_SETUP_IGNORE_SAFETY, pwm_out.safety_mask)
        |
        |-- is_chibios_backend
        |   |-- IOEVENT_GPIO
        |       |-- write_registers(PAGE_GPIO, ...)
        |
        |-- IOEVENT_DSHOT
            |-- dshot_command_queue.pop(dshot)
            |-- write_registers(PAGE_DSHOT, ...)
    |
    |-- 定时事件检查
        |
        |-- 读取 RC 输入
        |   |-- now - last_rc_read_ms > 20
        |       |-- read_rc_input()
        |       |-- last_rc_read_ms = AP_HAL::millis()
        |
        |-- 读取状态
        |   |-- now - last_status_read_ms > 50
        |       |-- read_status()
        |       |-- last_status_read_ms = AP_HAL::millis()
        |       |-- write_log()
        |
        |-- 读取伺服输出
        |   |-- now - last_servo_read_ms > 50
        |       |-- read_servo()
        |       |-- last_servo_read_ms = AP_HAL::millis()
        |
        |-- 读取 ERPM 和遥测
        |   |-- HAL_WITH_IO_MCU_BIDIR_DSHOT
        |       |-- now - last_erpm_read_ms > erpm_period_ms
        |           |-- read_erpm()
        |           |-- last_erpm_read_ms = AP_HAL::millis()
        |       |-- now - last_telem_read_ms > 100
        |           |-- read_telem()
        |           |-- last_telem_read_ms = AP_HAL::millis()
        |
        |-- 更新安全选项
        |   |-- now - last_safety_option_check_ms > 1000
        |       |-- update_safety_options()
        |       |-- last_safety_option_check_ms = now
        |
        |-- 更新故障安全 PWM
            |-- pwm_out.failsafe_pwm_set != pwm_out.failsafe_pwm_sent
                |-- write_registers(PAGE_FAILSAFE_PWM, ...)
                |-- pwm_out.failsafe_pwm_sent = set
    |
    |-- 发送 RC 协议
        |-- send_rc_protocols()
|
|-- 关闭线程标记
    |-- done_shutdown = true

3. 底层协议

+--------------------+
|    IOPacket        |
+--------------------+
| uint8_t count:6    | <-- 6 bits (范围:0-63)
+--------------------+
| uint8_t code:2     | <-- 2 bits (范围:0-3)
+--------------------+
| uint8_t crc        | <-- 8 bits (校验码)
+--------------------+
| uint8_t page       | <-- 8 bits (页码)
+--------------------+
| uint8_t offset     | <-- 8 bits (偏移量)
+--------------------+
| uint16_t regs[]    | <-- 16位寄存器数组,长度为 `PKT_MAX_REGS`
| (0 到 count-1)     |
+--------------------+

3.1 AP_IOMCU::read_registers

AP_IOMCU::read_registers(uint8_t page, uint8_t offset, uint8_t count, uint16_t *regs)
|
|-- While 循环: 如果要读取的寄存器数量超过每包最大寄存器数
|   |-- 递归调用自身处理每包最大寄存器数
|   |-- 更新 offset, count 和 regs 指针
|
|-- 定义 IOPacket pkt 变量
|
|-- 丢弃 UART 输入缓冲区
|   |-- discard_input()
|
|-- 初始化 pkt 结构体
|   |-- memset(pkt.regs, 0, count*2)
|   |-- pkt.code = CODE_READ
|   |-- pkt.count = count
|   |-- pkt.page = page
|   |-- pkt.offset = offset
|   |-- pkt.crc = 0
|
|-- 计算 pkt_size
|   |-- if (is_chibios_backend)
|       |-- pkt_size = 4
|   |-- else
|       |-- pkt_size = pkt.get_size()
|
|-- 计算 pkt 的 CRC
|   |-- pkt.crc = crc_crc8((const uint8_t *)&pkt, pkt_size)
|
|-- 通过 UART 写入 pkt 数据
|   |-- ret = write_wait((uint8_t *)&pkt, pkt_size)
|   |-- if (ret != pkt_size)
|       |-- debug("write failed1 %u %u %u\n", pkt_size, page, offset)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 等待 UART 超时或收到期望的字节数
|   |-- if (!uart.wait_timeout(count*2+4, 10))
|       |-- debug("t=%lu timeout read page=%u offset=%u count=%u avail=%u\n",
|                  AP_HAL::millis(), page, offset, count, uart.available())
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 检查 UART 可用数据长度
|   |-- if (n < offsetof(struct IOPacket, regs))
|       |-- debug("t=%lu small pkt %u\n", AP_HAL::millis(), n)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 检查 pkt 大小
|   |-- if (pkt.get_size() != n)
|       |-- debug("t=%lu bad len %u %u\n", AP_HAL::millis(), n, pkt.get_size())
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 读取 UART 数据到 pkt
|   |-- uart.read(b, MIN(n, sizeof(pkt)))
|
|-- 验证 CRC
|   |-- got_crc = pkt.crc
|   |-- pkt.crc = 0
|   |-- expected_crc = crc_crc8((const uint8_t *)&pkt, pkt.get_size())
|   |-- if (got_crc != expected_crc)
|       |-- debug("t=%lu bad crc %02x should be %02x n=%u %u/%u/%u\n",
|                  AP_HAL::millis(), got_crc, expected_crc, n, page, offset, count)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 检查 pkt.code
|   |-- if (pkt.code != CODE_SUCCESS)
|       |-- debug("bad code %02x read %u/%u/%u\n", pkt.code, page, offset, count)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 检查 pkt.count
|   |-- if (pkt.count < count)
|       |-- debug("bad count %u read %u/%u/%u n=%u\n", pkt.count, page, offset, count, n)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 将 pkt 中的寄存器值复制到 regs
|   |-- memcpy(regs, pkt.regs, count*2)
|
|-- 检查失败次数是否超过最大值
|   |-- if (protocol_fail_count > IOMCU_MAX_REPEATED_FAILURES)
|       |-- handle_repeated_failures()
|
|-- 更新错误计数器和访问计数器
|   |-- total_errors += protocol_fail_count
|   |-- protocol_fail_count = 0
|   |-- protocol_count++
|   |-- last_reg_access_ms = AP_HAL::millis()
|
|-- 返回 true 表示读取成功

3.2 AP_IOMCU::write_registers

AP_IOMCU::write_registers(uint8_t page, uint8_t offset, uint8_t count, const uint16_t *regs)
|
|-- While 循环: 处理寄存器过多的情况(除 PAGE_SETUP 页面外)
|   |-- 递归调用自身处理最大可写寄存器数
|   |-- 更新 offset, count 和 regs 指针
|
|-- 定义 IOPacket pkt 变量
|
|-- 丢弃 UART 输入缓冲区
|   |-- discard_input()
|
|-- 初始化 pkt 结构体
|   |-- memset(pkt.regs, 0, count*2)
|   |-- pkt.code = CODE_WRITE
|   |-- pkt.count = count
|   |-- pkt.page = page
|   |-- pkt.offset = offset
|   |-- pkt.crc = 0
|   |-- memcpy(pkt.regs, regs, 2*count)
|   |-- pkt.crc = crc_crc8((const uint8_t *)&pkt, pkt.get_size())
|
|-- 写入 pkt 到 UART
|   |-- pkt_size = pkt.get_size()
|   |-- ret = write_wait((uint8_t *)&pkt, pkt_size)
|   |-- if (ret != pkt_size)
|       |-- debug("write failed2 %u %u %u %u\n", pkt_size, page, offset, ret)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 等待 UART 应答
|   |-- if (!uart.wait_timeout(4, 10))
|       |-- debug("no reply for %u/%u/%u\n", page, offset, count)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 读取 UART 数据到 pkt
|   |-- uint8_t *b = (uint8_t *)&pkt
|   |-- uint8_t n = uart.available()
|   |-- for (uint8_t i=0; i<n; i++)
|       |-- if (i < sizeof(pkt))
|           |-- b[i] = uart.read()
|
|-- 验证 pkt.code
|   |-- if (pkt.code != CODE_SUCCESS)
|       |-- debug("bad code %02x write %u/%u/%u %02x/%02x n=%u\n", pkt.code, page, offset, count, pkt.page, pkt.offset, n)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 验证 CRC
|   |-- got_crc = pkt.crc
|   |-- pkt.crc = 0
|   |-- expected_crc = crc_crc8((const uint8_t *)&pkt, pkt.get_size())
|   |-- if (got_crc != expected_crc)
|       |-- debug("bad crc %02x should be %02x\n", got_crc, expected_crc)
|       |-- protocol_fail_count++
|       |-- return false
|
|-- 检查失败次数是否超过最大值
|   |-- if (protocol_fail_count > IOMCU_MAX_REPEATED_FAILURES)
|       |-- handle_repeated_failures()
|
|-- 更新错误计数器和访问计数器
|   |-- total_errors += protocol_fail_count
|   |-- protocol_fail_count = 0
|   |-- protocol_count++
|
|-- 更新最后一次访问寄存器的时间
|   |-- last_reg_access_ms = AP_HAL::millis()
|
|-- 返回 true 表示写入成功

4. API函数

  • read/write one channel

void write_channel(uint8_t chan, uint16_t pwm);
uint16_t read_channel(uint8_t chan);

  • cork/push output

void cork(void);
void push(void);

  • set/get output frequency

void set_freq(uint16_t chmask, uint16_t freq);
uint16_t get_freq(uint16_t chan);

  • get state of safety switch

AP_HAL::Util::safety_state get_safety_switch_state(void) const;

  • force safety on/off

bool force_safety_on(void);
void force_safety_off(void);

  • set mask of channels that ignore safety state

void set_safety_mask(uint16_t chmask);

  • set PWM of channels when in FMU failsafe

void set_failsafe_pwm(uint16_t chmask, uint16_t period_us);

  • enable sbus output

bool enable_sbus_out(uint16_t rate_hz);

  • check for new RC input

bool check_rcinput(uint32_t &last_frame_us, uint8_t &num_channels, uint16_t *channels, uint8_t max_channels);

  • Do DSM receiver binding

void bind_dsm(uint8_t mode);

  • get the name of the RC protocol

const char *get_rc_protocol(void);

  • get receiver RSSI

int16_t get_RSSI(void)

  • get servo rail voltage / rssi voltage adc counts

uint16_t get_vservo_adc_count(void)
uint16_t get_vrssi_adc_count(void)

  • set target for IMU heater

void set_heater_duty_cycle(uint8_t duty_cycle);

  • set default output rate

void set_default_rate(uint16_t rate_hz);

  • set to oneshot/brushed mode

void set_oneshot_mode(void);
void set_brushed_mode(void);

  • set/get output mode

void set_output_mode(uint16_t mask, uint16_t mode);
AP_HAL::RCOutput::output_mode get_output_mode(uint8_t& mask) const;

  • set bi-directional mask

void set_bidir_dshot_mask(uint16_t mask);

  • get MCUID/CPUID

uint32_t get_mcu_id()
uint32_t get_cpu_id()

  • set dshot output period

void set_dshot_period(uint16_t period_us, uint8_t drate);

  • set telem request mask

void set_telem_request_mask(uint32_t mask);

  • set the dshot esc_type

void set_dshot_esc_type(AP_HAL::RCOutput::DshotEscType dshot_esc_type);

  • send a dshot command

void send_dshot_command(uint8_t command, uint8_t chan, uint32_t command_timeout_ms, uint16_t repeat_count, bool priority);

  • enable/disable channels

void enable_ch(uint8_t ch);
void disable_ch(uint8_t ch);

  • check if IO is healthy

bool healthy(void);

  • shutdown IO protocol (for reboot)

void shutdown();
void soft_reboot();

  • setup for FMU failsafe mixing

bool setup_mixing(RCMapper *rcmap, int8_t override_chan, float mixing_gain, uint16_t manual_rc_mask);

  • GPIO operation

bool valid_GPIO_pin(uint8_t pin) const; // Check if pin number is valid and configured for GPIO
bool convert_pin_number(uint8_t& pin) const; // convert external pin numbers 101 to 108 to internal 0 to 7
void set_GPIO_mask(uint8_t mask); // set GPIO mask of channels setup for output
void write_GPIO(uint8_t pin, bool value); // write to a output pin
void toggle_GPIO(uint8_t pin); // toggle a output pin

5. 参考资料

【1】ArduPilot开源飞控系统之简单介绍
【2】ArduPilot之开源代码Task介绍
【3】ArduPilot飞控启动&运行过程简介
【4】ArduPilot之开源代码Library&Sketches设计
【5】ArduPilot之开源代码Sensor Drivers设计
【6】ArduPilot开源代码之EKF系列研读
【7】ArduPilot开源代码之AP_DAL研读系列

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值