/* * This file is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Author: Eugene Shamaev, Siddharth Bharat Purohit */ #include #include #if HAL_WITH_UAVCAN #include "AP_UAVCAN.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LED_DELAY_US 50000 extern const AP_HAL::HAL& hal; #define debug_uavcan(level_debug, fmt, args...) do { if ((level_debug) <= AP::can().get_debug_level_driver(_driver_index)) { printf(fmt, ##args); }} while (0) // Translation of all messages from UAVCAN structures into AP structures is done // in AP_UAVCAN and not in corresponding drivers. // The overhead of including definitions of DSDL is very high and it is best to // concentrate in one place. // table of user settable CAN bus parameters const AP_Param::GroupInfo AP_UAVCAN::var_info[] = { // @Param: NODE // @DisplayName: UAVCAN node that is used for this network // @Description: UAVCAN node should be set implicitly // @Range: 1 250 // @User: Advanced AP_GROUPINFO("NODE", 1, AP_UAVCAN, _uavcan_node, 10), // @Param: SRV_BM // @DisplayName: RC Out channels to be transmitted as servo over UAVCAN // @Description: Bitmask with one set for channel to be transmitted as a servo command over UAVCAN // @Bitmask: 0: Servo 1, 1: Servo 2, 2: Servo 3, 3: Servo 4, 4: Servo 5, 5: Servo 6, 6: Servo 7, 7: Servo 8, 8: Servo 9, 9: Servo 10, 10: Servo 11, 11: Servo 12, 12: Servo 13, 13: Servo 14, 14: Servo 15 // @User: Advanced AP_GROUPINFO("SRV_BM", 2, AP_UAVCAN, _servo_bm, 0), // @Param: ESC_BM // @DisplayName: RC Out channels to be transmitted as ESC over UAVCAN // @Description: Bitmask with one set for channel to be transmitted as a ESC command over UAVCAN // @Bitmask: 0: ESC 1, 1: ESC 2, 2: ESC 3, 3: ESC 4, 4: ESC 5, 5: ESC 6, 6: ESC 7, 7: ESC 8, 8: ESC 9, 9: ESC 10, 10: ESC 11, 11: ESC 12, 12: ESC 13, 13: ESC 14, 14: ESC 15, 15: ESC 16 // @User: Advanced AP_GROUPINFO("ESC_BM", 3, AP_UAVCAN, _esc_bm, 0), // @Param: SRV_RT // @DisplayName: Servo output rate // @Description: Maximum transmit rate for servo outputs // @Range: 1 200 // @Units: Hz // @User: Advanced AP_GROUPINFO("SRV_RT", 4, AP_UAVCAN, _servo_rate_hz, 50), AP_GROUPEND }; // this is the timeout in milliseconds for periodic message types. We // set this to 1 to minimise resend of stale msgs #define CAN_PERIODIC_TX_TIMEOUT_MS 2 // publisher interfaces static uavcan::Publisher* act_out_array[MAX_NUMBER_OF_CAN_DRIVERS]; static uavcan::Publisher* esc_raw[MAX_NUMBER_OF_CAN_DRIVERS]; static uavcan::Publisher* rgb_led[MAX_NUMBER_OF_CAN_DRIVERS]; AP_UAVCAN::AP_UAVCAN() : _node_allocator( UAVCAN_NODE_POOL_SIZE, UAVCAN_NODE_POOL_SIZE) { AP_Param::setup_object_defaults(this, var_info); for (uint8_t i = 0; i < UAVCAN_SRV_NUMBER; i++) { _SRV_conf[i].esc_pending = false; _SRV_conf[i].servo_pending = false; } debug_uavcan(2, "AP_UAVCAN constructed\n\r"); } AP_UAVCAN::~AP_UAVCAN() { } AP_UAVCAN *AP_UAVCAN::get_uavcan(uint8_t driver_index) { if (driver_index >= AP::can().get_num_drivers() || AP::can().get_protocol_type(driver_index) != AP_BoardConfig_CAN::Protocol_Type_UAVCAN) { return nullptr; } return static_cast(AP::can().get_driver(driver_index)); } void AP_UAVCAN::init(uint8_t driver_index) { if (_initialized) { debug_uavcan(2, "UAVCAN: init called more than once\n\r"); return; } _driver_index = driver_index; AP_HAL::CANManager* can_mgr = hal.can_mgr[driver_index]; if (can_mgr == nullptr) { debug_uavcan(2, "UAVCAN: init called for inexisting CAN driver\n\r"); return; } if (!can_mgr->is_initialized()) { debug_uavcan(1, "UAVCAN: CAN driver not initialized\n\r"); return; } uavcan::ICanDriver* driver = can_mgr->get_driver(); if (driver == nullptr) { debug_uavcan(2, "UAVCAN: can't get UAVCAN interface driver\n\r"); return; } _node = new uavcan::Node<0>(*driver, SystemClock::instance(), _node_allocator); if (_node == nullptr) { debug_uavcan(1, "UAVCAN: couldn't allocate node\n\r"); return; } if (_node->isStarted()) { debug_uavcan(2, "UAVCAN: node was already started?\n\r"); return; } uavcan::NodeID self_node_id(_uavcan_node); _node->setNodeID(self_node_id); char ndname[20]; snprintf(ndname, sizeof(ndname), "org.ardupilot:%u", driver_index); uavcan::NodeStatusProvider::NodeName name(ndname); _node->setName(name); uavcan::protocol::SoftwareVersion sw_version; // Standard type uavcan.protocol.SoftwareVersion sw_version.major = AP_UAVCAN_SW_VERS_MAJOR; sw_version.minor = AP_UAVCAN_SW_VERS_MINOR; _node->setSoftwareVersion(sw_version); uavcan::protocol::HardwareVersion hw_version; // Standard type uavcan.protocol.HardwareVersion hw_version.major = AP_UAVCAN_HW_VERS_MAJOR; hw_version.minor = AP_UAVCAN_HW_VERS_MINOR; _node->setHardwareVersion(hw_version); int start_res = _node->start(); if (start_res < 0) { debug_uavcan(1, "UAVCAN: node start problem, error %d\n\r", start_res); return; } // Roundup all subscribers from supported drivers AP_GPS_UAVCAN::subscribe_msgs(this); AP_Compass_UAVCAN::subscribe_msgs(this); AP_Baro_UAVCAN::subscribe_msgs(this); AP_BattMonitor_UAVCAN::subscribe_msgs(this); AP_Airspeed_UAVCAN::subscribe_msgs(this); act_out_array[driver_index] = new uavcan::Publisher(*_node); act_out_array[driver_index]->setTxTimeout(uavcan::MonotonicDuration::fromMSec(2)); act_out_array[driver_index]->setPriority(uavcan::TransferPriority::OneLowerThanHighest); esc_raw[driver_index] = new uavcan::Publisher(*_node); esc_raw[driver_index]->setTxTimeout(uavcan::MonotonicDuration::fromMSec(2)); esc_raw[driver_index]->setPriority(uavcan::TransferPriority::OneLowerThanHighest); rgb_led[driver_index] = new uavcan::Publisher(*_node); rgb_led[driver_index]->setTxTimeout(uavcan::MonotonicDuration::fromMSec(20)); rgb_led[driver_index]->setPriority(uavcan::TransferPriority::OneHigherThanLowest); _led_conf.devices_count = 0; configureCanAcceptanceFilters(*_node); /* * Informing other nodes that we're ready to work. * Default mode is INITIALIZING. */ _node->setModeOperational(); // Spin node for device discovery _node->spin(uavcan::MonotonicDuration::fromMSec(5000)); snprintf(_thread_name, sizeof(_thread_name), "uavcan_%u", driver_index); if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_UAVCAN::loop, void), _thread_name, 4096, AP_HAL::Scheduler::PRIORITY_CAN, 0)) { _node->setModeOfflineAndPublish(); debug_uavcan(1, "UAVCAN: couldn't create thread\n\r"); return; } _initialized = true; debug_uavcan(2, "UAVCAN: init done\n\r"); } void AP_UAVCAN::loop(void) { while (true) { if (!_initialized) { hal.scheduler->delay_microseconds(1000); continue; } const int error = _node->spin(uavcan::MonotonicDuration::fromMSec(1)); if (error < 0) { hal.scheduler->delay_microseconds(100); continue; } if (_SRV_armed) { bool sent_servos = false; if (_servo_bm > 0) { // if we have any Servos in bitmask uint32_t now = AP_HAL::micros(); const uint32_t servo_period_us = 1000000UL / unsigned(_servo_rate_hz.get()); if (now - _SRV_last_send_us >= servo_period_us) { _SRV_last_send_us = now; SRV_send_actuator(); sent_servos = true; for (uint8_t i = 0; i < UAVCAN_SRV_NUMBER; i++) { _SRV_conf[i].servo_pending = false; } } } // if we have any ESC's in bitmask if (_esc_bm > 0 && !sent_servos) { SRV_send_esc(); } for (uint8_t i = 0; i < UAVCAN_SRV_NUMBER; i++) { _SRV_conf[i].esc_pending = false; } } led_out_send(); } } ///// SRV output ///// void AP_UAVCAN::SRV_send_actuator(void) { uint8_t starting_servo = 0; bool repeat_send; WITH_SEMAPHORE(SRV_sem); do { repeat_send = false; uavcan::equipment::actuator::ArrayCommand msg; uint8_t i; // UAVCAN can hold maximum of 15 commands in one frame for (i = 0; starting_servo < UAVCAN_SRV_NUMBER && i < 15; starting_servo++) { uavcan::equipment::actuator::Command cmd; /* * Servo output uses a range of 1000-2000 PWM for scaling. * This converts output PWM from [1000:2000] range to [-1:1] range that * is passed to servo as unitless type via UAVCAN. * This approach allows for MIN/TRIM/MAX values to be used fully on * autopilot side and for servo it should have the setup to provide maximum * physically possible throws at [-1:1] limits. */ if (_SRV_conf[starting_servo].servo_pending && ((((uint32_t) 1) << starting_servo) & _servo_bm)) { cmd.actuator_id = starting_servo + 1; // TODO: other types cmd.command_type = uavcan::equipment::actuator::Command::COMMAND_TYPE_UNITLESS; // TODO: failsafe, safety cmd.command_value = constrain_float(((float) _SRV_conf[starting_servo].pulse - 1000.0) / 500.0 - 1.0, -1.0, 1.0); msg.commands.push_back(cmd); i++; } } if (i > 0) { act_out_array[_driver_index]->broadcast(msg); if (i == 15) { repeat_send = true; } } } while (repeat_send); } void AP_UAVCAN::SRV_send_esc(void) { static const int cmd_max = uavcan::equipment::esc::RawCommand::FieldTypes::cmd::RawValueType::max(); uavcan::equipment::esc::RawCommand esc_msg; uint8_t active_esc_num = 0, max_esc_num = 0; uint8_t k = 0; WITH_SEMAPHORE(SRV_sem); // find out how many esc we have enabled and if they are active at all for (uint8_t i = 0; i < UAVCAN_SRV_NUMBER; i++) { if ((((uint32_t) 1) << i) & _esc_bm) { max_esc_num = i + 1; if (_SRV_conf[i].esc_pending) { active_esc_num++; } } } // if at least one is active (update) we need to send to all if (active_esc_num > 0) { k = 0; for (uint8_t i = 0; i < max_esc_num && k < 20; i++) { if ((((uint32_t) 1) << i) & _esc_bm) { // TODO: ESC negative scaling for reverse thrust and reverse rotation float scaled = cmd_max * (hal.rcout->scale_esc_to_unity(_SRV_conf[i].pulse) + 1.0) / 2.0; scaled = constrain_float(scaled, 0, cmd_max); esc_msg.cmd.push_back(static_cast(scaled)); } else { esc_msg.cmd.push_back(static_cast(0)); } k++; } esc_raw[_driver_index]->broadcast(esc_msg); } } void AP_UAVCAN::SRV_push_servos() { WITH_SEMAPHORE(SRV_sem); for (uint8_t i = 0; i < NUM_SERVO_CHANNELS; i++) { // Check if this channels has any function assigned if (SRV_Channels::channel_function(i)) { _SRV_conf[i].pulse = SRV_Channels::srv_channel(i)->get_output_pwm(); _SRV_conf[i].esc_pending = true; _SRV_conf[i].servo_pending = true; } } _SRV_armed = hal.util->safety_switch_state() != AP_HAL::Util::SAFETY_DISARMED; } ///// LED ///// void AP_UAVCAN::led_out_send() { uint64_t now = AP_HAL::micros64(); if ((now - _led_conf.last_update) < LED_DELAY_US) { return; } uavcan::equipment::indication::LightsCommand msg; { WITH_SEMAPHORE(_led_out_sem); if (_led_conf.devices_count == 0) { return; } uavcan::equipment::indication::SingleLightCommand cmd; for (uint8_t i = 0; i < _led_conf.devices_count; i++) { cmd.light_id =_led_conf.devices[i].led_index; cmd.color.red = _led_conf.devices[i].red >> 3; cmd.color.green = _led_conf.devices[i].green >> 2; cmd.color.blue = _led_conf.devices[i].blue >> 3; msg.commands.push_back(cmd); } } rgb_led[_driver_index]->broadcast(msg); _led_conf.last_update = now; } bool AP_UAVCAN::led_write(uint8_t led_index, uint8_t red, uint8_t green, uint8_t blue) { if (_led_conf.devices_count >= AP_UAVCAN_MAX_LED_DEVICES) { return false; } WITH_SEMAPHORE(_led_out_sem); // check if a device instance exists. if so, break so the instance index is remembered uint8_t instance = 0; for (; instance < _led_conf.devices_count; instance++) { if (_led_conf.devices[instance].led_index == led_index) { break; } } // load into the correct instance. // if an existing instance was found in above for loop search, // then instance value is < _led_conf.devices_count. // otherwise a new one was just found so we increment the count. // Either way, the correct instance is the current value of instance _led_conf.devices[instance].led_index = led_index; _led_conf.devices[instance].red = red; _led_conf.devices[instance].green = green; _led_conf.devices[instance].blue = blue; if (instance == _led_conf.devices_count) { _led_conf.devices_count++; } return true; } #endif // HAL_WITH_UAVCAN