/* This program 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 program 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 . */ /* * AP_KDECAN.cpp * * Author: Francisco Ferreira and Tom Pittenger */ #include "AP_KDECAN.h" #if AP_KDECAN_ENABLED #include #include #include #include #include #include // for MIN,MAX extern const AP_HAL::HAL& hal; #define AP_KDECAN_DEBUG 0 // table of user settable CAN bus parameters const AP_Param::GroupInfo AP_KDECAN::var_info[] = { // @Param: NPOLE // @DisplayName: Number of motor poles // @Description: Sets the number of motor poles to calculate the correct RPM value AP_GROUPINFO("NPOLE", 1, AP_KDECAN, _num_poles, DEFAULT_NUM_POLES), AP_GROUPEND }; AP_KDECAN::AP_KDECAN() { AP_Param::setup_object_defaults(this, var_info); #if CONFIG_HAL_BOARD == HAL_BOARD_SITL if (_singleton != nullptr) { AP_HAL::panic("AP_KDECAN must be singleton"); } #endif _singleton = this; } void AP_KDECAN::init() { if (_driver != nullptr) { // only allow one instance return; } for (uint8_t i = 0; i < HAL_NUM_CAN_IFACES; i++) { if (CANSensor::get_driver_type(i) == AP_CAN::Protocol::KDECAN) { _driver = NEW_NOTHROW AP_KDECAN_Driver(); return; } } } void AP_KDECAN::update() { if (_driver == nullptr) { return; } _driver->update((uint8_t)_num_poles.get()); } AP_KDECAN_Driver::AP_KDECAN_Driver() : CANSensor("KDECAN") { register_driver(AP_CAN::Protocol::KDECAN); // start thread for receiving and sending CAN frames. Tests show we use about 640 bytes of stack hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_KDECAN_Driver::loop, void), "kdecan", 2048, AP_HAL::Scheduler::PRIORITY_CAN, 0); } // parse inbound frames void AP_KDECAN_Driver::handle_frame(AP_HAL::CANFrame &frame) { if (!frame.isExtended()) { return; } const frame_id_t id { .value = frame.id & AP_HAL::CANFrame::MaskExtID }; #if AP_KDECAN_DEBUG if (id.object_address != TELEMETRY_OBJ_ADDR) { GCS_SEND_TEXT(MAV_SEVERITY_DEBUG,"KDECAN: rx id:%d, src:%d, dest:%d, len:%d", (int)id.object_address, (int)id.source_id, (int)id.destination_id, (int)frame.dlc); } #endif if (id.destination_id != AUTOPILOT_NODE_ID || id.source_id < ESC_NODE_ID_FIRST) { // not for us or invalid id (0 and 1 are invalid) return; } // check if frame is valid: directed at autopilot, doesn't come from broadcast and ESC was detected before switch (id.object_address) { case ESC_INFO_OBJ_ADDR: if (frame.dlc == 5 && (id.source_id < (ARRAY_SIZE(_output.pwm) + ESC_NODE_ID_FIRST))) { if (__builtin_popcount(_init.detected_bitmask) >= KDECAN_MAX_NUM_ESCS) { // we already have the maximum number of ESCs return; } const uint16_t bitmask = (1UL << (id.source_id - ESC_NODE_ID_FIRST)); if ((bitmask & _init.detected_bitmask) != bitmask) { _init.detected_bitmask |= bitmask; GCS_SEND_TEXT(MAV_SEVERITY_INFO,"KDECAN: Found ESC id %u mapped to SERVO%u", id.source_id, id.source_id-1); } } break; #if HAL_WITH_ESC_TELEM case TELEMETRY_OBJ_ADDR: if (frame.dlc == 8 && (1UL << (id.source_id - ESC_NODE_ID_FIRST) & _init.detected_bitmask)) { const uint8_t idx = id.source_id - ESC_NODE_ID_FIRST; const uint8_t num_poles = _telemetry.num_poles > 0 ? _telemetry.num_poles : DEFAULT_NUM_POLES; update_rpm(idx, uint16_t(uint16_t(frame.data[4] << 8 | frame.data[5]) * 60UL * 2 / num_poles)); const TelemetryData t { .temperature_cdeg = int16_t(frame.data[6] * 100), .voltage = float(uint16_t(frame.data[0] << 8 | frame.data[1])) * 0.01f, .current = float(uint16_t(frame.data[2] << 8 | frame.data[3])) * 0.01f, }; update_telem_data(idx, t, AP_ESC_Telem_Backend::TelemetryType::CURRENT | AP_ESC_Telem_Backend::TelemetryType::VOLTAGE | AP_ESC_Telem_Backend::TelemetryType::TEMPERATURE); } break; #endif // HAL_WITH_ESC_TELEM } } void AP_KDECAN_Driver::update(const uint8_t num_poles) { if (_init.detected_bitmask == 0) { // nothing to do... return; } #if HAL_WITH_ESC_TELEM _telemetry.num_poles = num_poles; #endif WITH_SEMAPHORE(_output.sem); for (uint8_t i = 0; i < ARRAY_SIZE(_output.pwm); i++) { if ((_init.detected_bitmask & (1UL<get_output_pwm(); } _output.is_new = true; #if AP_KDECAN_USE_EVENTS if (_output.thread_ctx != nullptr) { // trigger the thread to wake up immediately chEvtSignal(_output.thread_ctx, 1); } #endif #if AP_KDECAN_DEBUG static uint32_t last_send_ms = 0; const uint32_t now_ms = AP_HAL::millis(); if (now_ms - last_send_ms > 1000) { last_send_ms = now_ms; GCS_SEND_TEXT(MAV_SEVERITY_INFO,"%u: %u, %u, %u, %u, %u, %u, %u, %u", (unsigned)_init.detected_bitmask, (unsigned)_output.pwm[0], (unsigned)_output.pwm[1], (unsigned)_output.pwm[2], (unsigned)_output.pwm[3], (unsigned)_output.pwm[4], (unsigned)_output.pwm[5], (unsigned)_output.pwm[6], (unsigned)_output.pwm[7]); } #endif } void AP_KDECAN_Driver::loop() { uint16_t pwm[ARRAY_SIZE(_output.pwm)] {}; #if AP_KDECAN_USE_EVENTS _output.thread_ctx = chThdGetSelfX(); #endif uint8_t broadcast_esc_info_boot_spam_count = 3; uint32_t broadcast_esc_info_next_interval_ms = 100; // spam a few at boot at this rate while (true) { #if AP_KDECAN_USE_EVENTS // sleep until we get new data, but also wake up at 400Hz to send the old data again chEvtWaitAnyTimeout(ALL_EVENTS, chTimeUS2I(2500)); #else hal.scheduler->delay_microseconds(2500); // 400Hz #endif const uint32_t now_ms = AP_HAL::millis(); // This should run at 400Hz { WITH_SEMAPHORE(_output.sem); if (_output.is_new) { _output.last_new_ms = now_ms; _output.is_new = false; memcpy(&pwm, &_output.pwm, sizeof(pwm)); } else if (_output.last_new_ms && now_ms - _output.last_new_ms > 1000) { // if we haven't gotten any PWM updates for a bit, zero it // out so we don't just keep sending the same values forever memset(&pwm, 0, sizeof(pwm)); _output.last_new_ms = 0; } } for (uint8_t i=0; i= TELEMETRY_INTERVAL_MS) { if (send_packet(TELEMETRY_OBJ_ADDR, BROADCAST_NODE_ID, 10)) { _telemetry.timer_ms = now_ms; } } #endif // HAL_WITH_ESC_TELEM if ((_init.detected_bitmask == 0 || broadcast_esc_info_boot_spam_count > 0) && (now_ms - _init.detected_bitmask_ms >= broadcast_esc_info_next_interval_ms)) { // broadcast an "anyone there?" quick at boot but then 1Hz forever until we see at least 1 esc respond if (broadcast_esc_info_boot_spam_count > 0) { broadcast_esc_info_boot_spam_count--; } else { broadcast_esc_info_next_interval_ms = 1000; } if (send_packet(ESC_INFO_OBJ_ADDR, BROADCAST_NODE_ID, 100)) { _init.detected_bitmask_ms = now_ms; } } } // while true } bool AP_KDECAN_Driver::send_packet_uint16(const uint8_t address, const uint8_t dest_id, const uint32_t timeout_ms, const uint16_t data) { const uint16_t data_be16 = htobe16(data); return send_packet(address, dest_id, timeout_ms, (uint8_t*)&data_be16, 2); } bool AP_KDECAN_Driver::send_packet(const uint8_t address, const uint8_t dest_id, const uint32_t timeout_ms, const uint8_t *data, const uint8_t data_len) { // broadcast telemetry request frame const frame_id_t id { { .object_address = address, .destination_id = dest_id, .source_id = AUTOPILOT_NODE_ID, .priority = 0, .unused = 0 } }; AP_HAL::CANFrame frame = AP_HAL::CANFrame((id.value | AP_HAL::CANFrame::FlagEFF), data, data_len, false); const uint64_t timeout_us = uint64_t(timeout_ms) * 1000UL; return write_frame(frame, timeout_us); } // singleton instance AP_KDECAN *AP_KDECAN::_singleton; namespace AP { AP_KDECAN *kdecan() { return AP_KDECAN::get_singleton(); } }; #endif // AP_KDECAN_ENABLED