PX4模块设计之三十一:ManualControl模块

本文深入解析了PX4中的ManualControl模块,该模块主要处理手动控制输入并发布手动控制设定点消息。模块采用ModuleBase和WorkQueue设计,通过task_spawn初始化,并在Run函数中处理RC遥控器输入,发布不同指令。 ManualControl模块还涉及到了开关状态的更新和处理,以及对飞行器模式的切换请求。

1. ManualControl模块简介

从帮助的角度,该模块主要处理manual_control_inputs消息,发布one manual_control_setpoint消息。

### Description
Module consuming manual_control_inputs publishing one manual_control_setpoint.

manual_control <command> [arguments...]
 Commands:
   start

   stop

   status        print status info

注:print_usage函数是具体对应实现。

class ManualControl : public ModuleBase<ManualControl>, public ModuleParams, public px4::ScheduledWorkItem

注:ManualControl模块采用了ModuleBaseWorkQueue设计。

2. 模块入口函数

2.1 主入口manual_control_main

同样继承了ModuleBase,由ModuleBase的main来完成模块入口。

manual_control_main
 └──> return ManualControl::main(argc, argv)

2.2 自定义子命令custom_command

模块仅支持start/stop/status命令,不支持其他自定义命令。

ManualControl::custom_command
 └──> return print_usage("unknown command");

3. ManualControl模块重要函数

3.1 task_spawn

这里主要初始化了ManualControl对象,后续通过WorkQueue来完成进行轮询。

ManualControl::task_spawn
 ├──> ManualControl *instance = new ManualControl()
 ├──> <instance>
 │   ├──> _object.store(instance);
 │   ├──> _task_id = task_id_is_work_queue;
 │   ├──> <instance->init()>
 │   │   └──> return PX4_OK
 │   └──> <else>
 │       └──> PX4_ERR("alloc failed")
 ├──> delete instance
 ├──> _object.store(nullptr)
 ├──> _task_id = -1
 └──> return PX4_ERROR

3.2 instantiate

注:鉴于该模块不采用任务(线程),所以ModuleBase::run_trampoline无需执行,所以可以不实现。

3.3 init

在task_spawn中使用,直接调用SubscriptionCallbackWorkItem::ScheduleNow,ManualControl::Run的初次调度任务。

ManualControl::init
 ├──> ScheduleNow()
 └──> return true

3.4 Run

根据RC遥控器控制输入,发布不同switch和setpoint消息。

ManualControl::Run
 ├──> [优雅退出处理]
 ├──> <_vehicle_status_sub.updated()><_vehicle_status_sub.copy(&vehicle_status)>  //飞行器状态:system_id/rotary_wing/vtol
 │   ├──> _system_id = vehicle_status.system_id;
 │   ├──> _rotary_wing = (vehicle_status.vehicle_type == vehicle_status_s::VEHICLE_TYPE_ROTARY_WING);
 │   └──> _vtol = vehicle_status.is_vtol;
 ├──> <_parameter_update_sub.updated()>
 │   ├──> _parameter_update_sub.copy(&param_update)
 │   ├──> updateParams()
 │   ├──> _stick_arm_hysteresis.set_hysteresis_time_from(false, _param_com_rc_arm_hyst.get() * 1_ms)
 │   ├──> _stick_disarm_hysteresis.set_hysteresis_time_from(false, _param_com_rc_arm_hyst.get() * 1_ms)
 │   ├──> _button_hysteresis.set_hysteresis_time_from(false, _param_com_rc_arm_hyst.get() * 1_ms)
 │   ├──> _selector.setRcInMode(_param_com_rc_in_mode.get());
 │   ├──> _selector.setTimeout(_param_com_rc_loss_t.get() * 1_s);
 │   └──> [MAN_ARM_GESTURE处理场景:1)RC_MAP_ARM_SW & MAN_ARM_GESTURE: disable arm gesture if an arm switch is configured;2)MC_AIRMODE & MAN_ARM_GESTURE: check for unsafe Airmode settings: yaw airmode requires disabling the stick arm gesture]
 ├──> _selector.updateValidityOfChosenInput(now) //检查RC输入有效性
 ├──> <for (int i = 0; i < MAX_MANUAL_INPUT_COUNT; i++)><_manual_control_setpoint_subs[i].update(&manual_control_input)>
 │   └──>_selector.updateWithNewInputSample(now, manual_control_input, i);
 ├──> switches_updated = _manual_control_switches_sub.update(&switches);
 ├──> <_selector.setpoint().valid> //setpoint有效
 │   ├──> _published_invalid_once = false
 │   ├──> processStickArming(_selector.setpoint())
 │   ├──> [ 更新_selector.setpoint().sticks_moving ]
 │   ├──> <switches_updated><_previous_switches_initialized>
 │   │   ├──> <switches.mode_slot != _previous_switches.mode_slot>
 │   │   │   └──> evaluateModeSlot(switches.mode_slot)
 │   │   ├──> [message: Arming button/switch]  // _action_request_pub发布
 │   │   ├──> [message:Return switch] // _action_request_pub发布
 │   │   ├──> [message:Loiter switch] // _action_request_pub发布
 │   │   ├──> [message:Offboard switch] // _action_request_pub发布
 │   │   ├──> [message:Kill switch] // _action_request_pub发布
 │   │   ├──> [message:Gear switch]        // _landing_gear_pub发布
 │   │   ├──> [message:Transition switch] // _action_request_pub发布
 │   │   ├──> [message:Photo switch] // _action_request_pub发布
 │   │   ├──> [message:Video switch] // _action_request_pub发布
 │   │   ├──> _previous_switches_initialized = true;
 │   │   └──>  _previous_switches = switches;
 │   ├──> <else>
 │   │   └──> _previous_switches_initialized = false
 │   ├──> _selector.setpoint().timestamp = now;
 │   ├──> _manual_control_setpoint_pub.publish(_selector.setpoint());  //有效setpoint发布
 ├──> <!_selector.setpoint().valid> //setpoint无效
 │   ├──> <!_published_invalid_once>
 │   │   ├──> _published_invalid_once = true
 │   │   └──> _manual_control_setpoint_pub.publish(_selector.setpoint())  //无效setpoint发布
 │   ├──> _x_diff.reset();
 │   ├──> _y_diff.reset();
 │   ├──> _z_diff.reset();
 │   ├──> _r_diff.reset();
 │   ├──> _stick_arm_hysteresis.set_state_and_update(false, now);
 │   ├──> _stick_disarm_hysteresis.set_state_and_update(false, now);
 │   └──> _button_hysteresis.set_state_and_update(false, now);
 ├──> _last_time = now
 └──> ScheduleDelayed(200_ms)

注:ManualControl::Run的主要途径是Init/Run和下面两个WorkItem内部的ScheduleNow触发的。

uORB::SubscriptionCallbackWorkItem _manual_control_setpoint_subs[MAX_MANUAL_INPUT_COUNT] {
	{this, ORB_ID(manual_control_input), 0},
	{this, ORB_ID(manual_control_input), 1},
	{this, ORB_ID(manual_control_input), 2},
};
uORB::SubscriptionCallbackWorkItem _manual_control_switches_sub{this, ORB_ID(manual_control_switches)};

4. 总结

从代码的角度

  • 输入
  uORB::SubscriptionInterval _parameter_update_sub{ORB_ID(parameter_update), 1_s};
  uORB::Subscription _vehicle_status_sub{ORB_ID(vehicle_status)};
  uORB::SubscriptionCallbackWorkItem _manual_control_setpoint_subs[MAX_MANUAL_INPUT_COUNT] {{this, ORB_ID(manual_control_input), 0},{this, ORB_ID(manual_control_input), 1},{this, ORB_ID(manual_control_input), 2},};
  uORB::SubscriptionCallbackWorkItem _manual_control_switches_sub{this, ORB_ID(manual_control_switches)};
  • 输出
  uORB::Publication<action_request_s> _action_request_pub{ORB_ID(action_request)};
  uORB::Publication<landing_gear_s> _landing_gear_pub{ORB_ID(landing_gear)};
  uORB::Publication<manual_control_setpoint_s> _manual_control_setpoint_pub{ORB_ID(manual_control_setpoint)};

5. 参考资料

【1】PX4开源软件框架简明简介
【2】PX4模块设计之十一:Built-In框架
【3】PX4模块设计之十二:High Resolution Timer设计
【4】PX4模块设计之十三:WorkQueue设计
【5】PX4模块设计之十七:ModuleBase模块
【6】PX4模块设计之三十:Hysteresis类
【7】PX4 modules_main

<think>我们注意到用户遇到了编译错误,提示三个函数未声明:'autoControl'、'manualControl' 和 'processSerialCommand'。这是因为在Arduino代码中,函数在使用前必须被声明或定义。在当前的代码结构中,这些函数的定义出现在它们被调用(在loop函数中)之后,因此编译器在遇到调用时还不知道这些函数的存在。 解决方案: 1. 可以在文件顶部(例如在setup函数之前)添加这些函数的前向声明(forward declarations)。 2. 或者将这三个函数的定义移到loop函数之前。 由于代码已经比较长,且为了保持结构清晰,我们选择添加前向声明。 修改步骤: 在全局变量声明之后,loop函数之前,添加以下三行: ```cpp void autoControl(); void manualControl(); void processSerialCommand(); ``` 这样,编译器在编译loop函数时就知道这些函数的存在,而它们的实际定义可以在后面。 修改后的代码片段如下(仅显示修改部分): 在全局变量声明区域之后: ```cpp ... // 全局变量声明 // 函数前向声明 void autoControl(); void manualControl(); void processSerialCommand(); void setup() { ... } ``` 这样修改后,代码应该能够通过编译。 另外,我们注意到用户之前还关注字体显示问题,但当前问题与编译错误相关,因此先解决编译问题。 完整修改后的代码(仅包含修改部分,其他部分保持不变): ```cpp #include <U8g2lib.h> // ... 其他代码(U8g2对象定义、引脚定义、全局变量等) // 函数前向声明 void autoControl(); void manualControl(); void processSerialCommand(); void setup() { // ... setup函数内容 } void loop() { // ... loop函数内容 } // 现在可以在后面定义这些函数了 void autoControl() { // 自动控制逻辑 } void manualControl() { // 手动控制逻辑 } void processSerialCommand() { // 处理串口命令 } // ... 其他函数(如displayData等) ``` 如果用户还没有实现这三个函数的具体内容,那么需要补充实现。根据之前代码中的调用,这三个函数应该包含相应的控制逻辑和串口处理。 注意:由于用户提供的原始代码片段中并没有给出这三个函数的实现,所以我们在上面只是给出了空函数。用户需要根据实际需求填充内容。 如果用户暂时不需要这些函数,也可以先给出一个空实现来避免编译错误。 例如: ```cpp void autoControl() { // TODO: 添加自动控制代码 } void manualControl() { // TODO: 添加手动控制代码 } void processSerialCommand() { // 简单示例:检查串口是否有数据 if (Serial.available() > 0) { char command = Serial.read(); // 处理命令 } } ``` 综上所述,通过添加前向声明并确保函数被定义,即可解决编译错误。 如果用户需要进一步帮助实现这些函数,请提供更多关于自动控制、手动控制和串口命令协议的信息。 §§相关问题§§ 1. 如何正确使用前向声明解决Arduino编译错误? 2. Arduino函数定义顺序对编译的影响? 3. 如何实现Arduino的自动光照和温度控制逻辑? 4. 如何通过串口与Arduino通信并发送控制命令? </think> # 解决Arduino编译错误:'autoControl'、'manualControl'、'processSerialCommand'未声明 在Arduino开发中,"未声明"错误通常是由于函数在使用前未正确定义或声明造成的。让我为您提供完整的解决方案。 ## 问题分析 编译错误`'autoControl' was not declared in this scope`等提示表明: 1. 函数在使用前**未被正确定义** 2. 函数**未进行前向声明** 3. 函数实现可能**位于错误位置** ## 完整解决方案 ```cpp // 添加前向声明解决未声明错误 void autoControl(); void manualControl(); void processSerialCommand(); // ... [其他代码保持不变] ... // 在loop()函数之后添加缺失的函数实现 void autoControl() { // 自动控制逻辑实现 if (lightIntensity < 200) { // 夜间模式 digitalWrite(LED4_PIN, HIGH); digitalWrite(LED5_PIN, LOW); } else { // 白天模式 digitalWrite(LED4_PIN, LOW); digitalWrite(LED5_PIN, HIGH); } } void manualControl() { // 手动控制逻辑实现 switch (manualState) { case 0: // 关闭所有LED digitalWrite(LED4_PIN, LOW); digitalWrite(LED5_PIN, LOW); break; case 1: // 开启LED4 digitalWrite(LED4_PIN, HIGH); digitalWrite(LED5_PIN, LOW); break; case 2: // 开启LED5 digitalWrite(LED4_PIN, LOW); digitalWrite(LED5_PIN, HIGH); break; } } void processSerialCommand() { // 串口命令处理实现 if (Serial.available() > 0) { char command = Serial.read(); switch (command) { case 'A': // 切换到自动模式 autoMode = true; Serial.println("Mode: Auto"); break; case 'M': // 切换到手动模式 autoMode = false; Serial.println("Mode: Manual"); break; case 'N': // 手动模式切换状态 if (!autoMode) { manualState = (manualState + 1) % 3; Serial.print("Manual state: "); Serial.println(manualState); } break; } } } ``` ## 关键修正说明 ### 1. 添加前向声明 ```cpp void autoControl(); void manualControl(); void processSerialCommand(); ``` **作用**:在函数被调用前告知编译器这些函数的存在,解决"未声明"错误[^1] ### 2. 完整函数实现 ```cpp void autoControl() { // 基于光照强度自动控制LED if (lightIntensity < 200) { digitalWrite(LED4_PIN, HIGH); // 夜间开灯 digitalWrite(LED5_PIN, LOW); } else { digitalWrite(LED4_PIN, LOW); digitalWrite(LED5_PIN, HIGH); // 白天开灯 } } ``` **逻辑**:当光照强度低于200lx时开启LED4(模拟夜间照明),否则开启LED5(白天照明) ### 3. 函数位置调整 将函数实现移至`loop()`函数之后,确保它们在使用前已被声明: ``` setup() | loop() | 其他功能函数 ``` ### 4. 串口命令处理 ```cpp void processSerialCommand() { if (Serial.available() > 0) { char command = Serial.read(); // 处理不同命令 } } ``` **支持命令**: - 'A':切换到自动模式 - 'M':切换到手动模式 - 'N':循环手动状态 ## 常见错误预防 1. **函数顺序错误**:始终在使用前声明函数 ```cpp // 错误示例 void loop() { autoControl(); // 此时autoControl未声明 } void autoControl() { ... } // 定义在调用之后 ``` 2. **拼写错误**:检查函数名一致性 ```cpp // 错误示例 void autoControl() { ... } loop() { autocontrol(); // 大小写错误 } ``` 3. **作用域问题**:确保函数在全局作用域声明 ```cpp // 错误示例 void setup() { void autoControl(); // 局部声明,loop()不可见 } ``` ## 优化建议 1. **使用函数原型文件**: 创建`prototypes.h`文件存放所有函数声明: ```cpp // prototypes.h #ifndef PROTOTYPES_H #define PROTOTYPES_H void autoControl(); void manualControl(); void processSerialCommand(); #endif ``` 在主文件中包含: ```cpp #include "prototypes.h" ``` 2. **添加调试输出**: ```cpp void autoControl() { Serial.print("AutoControl: "); if (lightIntensity < 200) { Serial.println("Night mode"); } else { Serial.println("Day mode"); } // ...控制逻辑... } ``` 3. **参数化阈值**: ```cpp const int NIGHT_THRESHOLD = 200; void autoControl() { if (lightIntensity < NIGHT_THRESHOLD) { // 夜间逻辑 } } ``` 这些修改将解决编译错误,同时提供更健壮的控制逻辑和调试能力[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值