/* driver for Beken_2425 radio */ #include //#pragma GCC optimize("O0") #if defined(HAL_RCINPUT_WITH_AP_RADIO) && CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_CHIBIOS_SKYVIPER_F412 #include #include "AP_Radio_bk2425.h" #include #include #include #include #include // start of 12 byte CPU ID #ifndef UDID_START #define UDID_START 0x1FFF7A10 #endif #define TIMEOUT_PRIORITY 250 // Right above timer thread #define EVT_TIMEOUT EVENT_MASK(0) // Event in the irq handler thread triggered by a timeout interrupt #define EVT_IRQ EVENT_MASK(1) // Event in the irq handler thread triggered by a radio IRQ (Tx finished, Rx finished, MaxRetries limit) #define EVT_BIND EVENT_MASK(2) // (not used yet) The user has clicked on the "start bind" button in the web interface (or equivalent). extern const AP_HAL::HAL& hal; // Output debug information on the UART, wrapped in MavLink packets #define Debug(level, fmt, args...) do { if ((level) <= get_debug_level()) { hal.console->printf(fmt, ##args); }} while (0) // Output fast debug information on the UART, in raw format. MavLink should be disabled if you want to understand these messages. // This is for debugging issues with frequency hopping and synchronisation. #define DebugPrintf(level, fmt, args...) do { if (AP_Radio_beken::radio_singleton && ((level) <= AP_Radio_beken::radio_singleton->get_debug_level())) { printf(fmt, ##args); }} while (0) // Output debug information on the mavlink to the UART connected to the WiFi, wrapped in MavLink packets #define DebugMavlink(level, fmt, args...) do { if ((level) <= get_debug_level()) { gcs().send_text(MAV_SEVERITY_INFO, fmt, ##args); }} while (0) // object instance for trampoline AP_Radio_beken *AP_Radio_beken::radio_singleton; thread_t *AP_Radio_beken::_irq_handler_ctx; virtual_timer_t AP_Radio_beken::timeout_vt; // See variable definitions in AP_Radio_bk2425.h for comments uint32_t AP_Radio_beken::isr_irq_time_us; uint32_t AP_Radio_beken::isr_timeout_time_us; uint32_t AP_Radio_beken::next_switch_us; uint32_t AP_Radio_beken::bind_time_ms; SyncTiming AP_Radio_beken::synctm; // Let the IRQ see the interpacket timing // ----------------------------------------------------------------------------- // We have received a packet // Sort out our timing relative to the tx to avoid clock drift void SyncTiming::Rx(uint32_t when) { uint32_t ld = delta_rx_time_us; uint32_t d = when - rx_time_us; if ((d > ld - DIFF_DELTA_RX) && (d < ld + DIFF_DELTA_RX)) { // Two deltas are similar to each other if ((d > TARGET_DELTA_RX-SLOP_DELTA_RX) && (d < TARGET_DELTA_RX+SLOP_DELTA_RX)) { // delta is within range of single packet distance // Use filter to change the estimate of the time in microseconds between the transmitters packet (according to OUR clock) sync_time_us = ((sync_time_us * (256-16)) + (d * 16)) / 256; } } rx_time_us = when; delta_rx_time_us = d; last_delta_rx_time_us = ld; } // ----------------------------------------------------------------------------- // Implement queuing (a 92 byte packet) in the circular buffer void FwUpload::queue(const uint8_t *pSrc, uint8_t len) { if (len == 0 || len > free_length()) { return; // Safety check for out of space error } if (pending_head + len > SZ_BUFFER) { uint8_t n = SZ_BUFFER-pending_head; memcpy(&pending_data[pending_head], pSrc, n); memcpy(&pending_data[0], pSrc+n, len-n); } else { memcpy(&pending_data[pending_head], pSrc, len); } pending_head = (pending_head + len) & (SZ_BUFFER-1); added += len; } // ----------------------------------------------------------------------------- // Implement dequeing (a 16 byte packet) void FwUpload::dequeue(uint8_t *pDst, uint8_t len) { if (len == 0 || len > pending_length()) { return; // Safety check for underflow error } if (pending_tail + len > SZ_BUFFER) { uint8_t n = SZ_BUFFER-pending_tail; memcpy(pDst, &pending_data[pending_tail], n); memcpy(pDst+n, &pending_data[0], len-n); } else { memcpy(pDst, &pending_data[pending_tail], len); } pending_tail = (pending_tail + len) & (SZ_BUFFER-1); sent += len; } // ----------------------------------------------------------------------------- /* constructor */ AP_Radio_beken::AP_Radio_beken(AP_Radio &_radio) : AP_Radio_backend(_radio), beken(hal.spi->get_device("beken")) // trace this later - its on libraries/AP_HAL_ChibiOS/SPIDevice.cpp:92 { // link to instance for irq_trampoline // (temporary) go into test mode radio_singleton = this; beken.fcc.fcc_mode = 0; beken.fcc.channel = 23; beken.fcc.power = 7+1; // Full power } /* initialise radio */ bool AP_Radio_beken::init(void) { if (_irq_handler_ctx != nullptr) { AP_HAL::panic("AP_Radio_beken: double instantiation of irq_handler\n"); } chVTObjectInit(&timeout_vt); _irq_handler_ctx = chThdCreateFromHeap(NULL, THD_WORKING_AREA_SIZE(2048), "radio_bk2425", TIMEOUT_PRIORITY, /* Initial priority. */ irq_handler_thd, /* Thread function. */ NULL); /* Thread parameter. */ return reset(); } /* reset radio */ bool AP_Radio_beken::reset(void) { if (!beken.lock_bus()) { return false; } radio_init(); beken.unlock_bus(); return true; } /* return statistics structure from radio */ const AP_Radio::stats &AP_Radio_beken::get_stats(void) { return stats; } /* read one pwm channel from radio */ uint16_t AP_Radio_beken::read(uint8_t chan) { if (chan >= BEKEN_MAX_CHANNELS) { return 0; } if (!valid_connection) { return (chan < 4) ? 1500u : 0u; } return pwm_channels[chan]; } /* update status - called from main thread */ void AP_Radio_beken::update(void) { check_fw_ack(); } /* return number of active channels, and updates the data */ uint8_t AP_Radio_beken::num_channels(void) { uint32_t now = AP_HAL::millis(); uint8_t chan = get_rssi_chan(); if ((chan > 0) && ((chan-1) < BEKEN_MAX_CHANNELS)) { uint8_t value = BK_RSSI_DEFAULT; // Fixed value that will not update (halfway in the RSSI range for Cypress chips, 0..31) if (beken.fcc.enable_cd) { if (beken.fcc.last_cd) { value += 4; } else { value -= 4; } } if (t_status.pps == 0) { value = BK_RSSI_MIN; // No packets = no RSSI } pwm_channels[chan-1] = value; chan_count = MAX(chan_count, chan); } chan = get_pps_chan(); if ((chan > 0) && ((chan-1) < BEKEN_MAX_CHANNELS)) { pwm_channels[chan-1] = t_status.pps; // How many packets received per second chan_count = MAX(chan_count, chan); } chan = get_tx_rssi_chan(); if ((chan > 0) && ((chan-1) < BEKEN_MAX_CHANNELS)) { pwm_channels[chan-1] = BK_RSSI_DEFAULT; // Fixed value that will not update (halfway in the RSSI range for Cypress chips, 0..31) chan_count = MAX(chan_count, chan); } chan = get_tx_pps_chan(); if ((chan > 0) && ((chan-1) < BEKEN_MAX_CHANNELS)) { pwm_channels[chan-1] = tx_pps; chan_count = MAX(chan_count, chan); } // Every second, update the statistics if (now - last_pps_ms > 1000) { last_pps_ms = now; t_status.pps = stats.recv_packets - last_stats.recv_packets; last_stats = stats; if (stats.lost_packets != 0 || stats.timeouts != 0) { Debug(3,"lost=%lu timeouts=%lu\n", stats.lost_packets, stats.timeouts); } stats.lost_packets=0; stats.timeouts=0; if (have_tx_pps == 1) { // Have we had tx pps recently? tx_pps = 0; } if (have_tx_pps == 2) { // We have had it at some time have_tx_pps = 1; // Not recently } } return chan_count; } /* return time of last receive in microseconds */ uint32_t AP_Radio_beken::last_recv_us(void) { return synctm.packet_timer; } /* send len bytes as a single packet */ bool AP_Radio_beken::send(const uint8_t *pkt, uint16_t len) { // disabled for now return false; } // Borrow the CRC32 algorithm from AP_HAL_SITL // Not exactly fast algorithm as it is bit based #define CRC32_POLYNOMIAL 0xEDB88320L static uint32_t CRC32Value(uint32_t icrc) { int i; uint32_t crc = icrc; for ( i = 8 ; i > 0; i-- ) { if ( crc & 1 ) { crc = ( crc >> 1 ) ^ CRC32_POLYNOMIAL; } else { crc >>= 1; } } return crc; } static uint32_t CalculateBlockCRC32(uint32_t length, const uint8_t *buffer, uint32_t crc) { while ( length-- != 0 ) { crc = ((crc >> 8) & 0x00FFFFFFL) ^ (CRC32Value(((uint32_t) crc ^ *buffer++) & 0xff)); } return ( crc ); } /* initialise the radio */ void AP_Radio_beken::radio_init(void) { DebugPrintf(1, "radio_init\r\n"); beken.SetRBank(1); uint8_t id = beken.ReadReg(BK2425_R1_WHOAMI); // id is now 99 beken.SetRBank(0); // Reset to default register bank. if (id != BK_CHIP_ID_BK2425) { Debug(1, "bk2425: radio not found\n"); // We have to keep trying because it takes time to initialise return; // Failure } { uint8_t serialid[12]; memcpy(serialid, (const void *)UDID_START, 12); // 0x1FFF7A10ul on STM32F412 (see Util::get_system_id) uint32_t drone_crc = CalculateBlockCRC32(12, serialid, 0xfffffffful); if ((drone_crc & 0xff) == 0) { ++drone_crc; // Ensure that the first byte (LSB) is non-zero for all drone CRC, for benefit of old (buggy) tx code. } myDroneId[0] = drone_crc; myDroneId[1] = drone_crc >> 8; myDroneId[2] = drone_crc >> 16; myDroneId[3] = drone_crc >> 24; DebugPrintf(1, "DroneCrc:%08x\r\n", drone_crc); } Debug(1, "beken: radio_init starting\n"); beken.bkReady = 0; spd = beken.gTxSpeed; beken.SwitchToIdleMode(); hal.scheduler->delay(100); // delay more than 50ms. // Initialise Beken registers beken.SetRBank(0); beken.InitBank0Registers(beken.gTxSpeed); beken.SetRBank(1); beken.InitBank1Registers(beken.gTxSpeed); hal.scheduler->delay(100); // delay more than 50ms. beken.SetRBank(0); beken.SwitchToRxMode(); // switch to RX mode beken.bkReady = 1; hal.scheduler->delay_microseconds(10*1000); // 10ms seconds delay // setup handler for rising edge of IRQ pin hal.gpio->attach_interrupt(HAL_GPIO_RADIO_IRQ, trigger_irq_radio_event, AP_HAL::GPIO::INTERRUPT_FALLING); if (load_bind_info()) { // See if we already have bound to the address of a tx Debug(3,"Loaded bind info\n"); nextChannel(1); } beken.EnableCarrierDetect(true); // For autobinding isr_irq_time_us = isr_timeout_time_us = AP_HAL::micros(); next_switch_us = isr_irq_time_us + 10000; chVTSet(&timeout_vt, chTimeMS2I(10), trigger_timeout_event, nullptr); // Initial timeout? if (3 <= get_debug_level()) { beken.DumpRegisters(); } } // ---------------------------------------------------------------------------- void AP_Radio_beken::trigger_irq_radio_event() { //we are called from ISR context // DEBUG2_HIGH(); chSysLockFromISR(); isr_irq_time_us = AP_HAL::micros(); chEvtSignalI(_irq_handler_ctx, EVT_IRQ); chSysUnlockFromISR(); // DEBUG2_LOW(); } // ---------------------------------------------------------------------------- void AP_Radio_beken::trigger_timeout_event(void *arg) { (void)arg; //we are called from ISR context // DEBUG2_HIGH(); // DEBUG2_LOW(); // DEBUG2_HIGH(); isr_timeout_time_us = AP_HAL::micros(); chSysLockFromISR(); chEvtSignalI(_irq_handler_ctx, EVT_TIMEOUT); chSysUnlockFromISR(); // DEBUG2_LOW(); } // ---------------------------------------------------------------------------- // The user has clicked on the "Start Bind" button on the web interface void AP_Radio_beken::start_recv_bind(void) { chan_count = 0; synctm.packet_timer = AP_HAL::micros(); radio_singleton->bind_time_ms = AP_HAL::millis(); chEvtSignal(_irq_handler_ctx, EVT_BIND); Debug(1,"Starting bind\n"); } // ---------------------------------------------------------------------------- // handle a data96 mavlink packet for fw upload void AP_Radio_beken::handle_data_packet(mavlink_channel_t chan, const mavlink_data96_t &m) { if (sem.take_nonblocking()) { fwupload.chan = chan; fwupload.need_ack = false; if (m.type == 43) { // sending a tune to play - for development testing Debug(4, "got tune data96 of len %u from chan %u\n", m.len, chan); fwupload.reset(); fwupload.fw_type = TELEM_PLAY; fwupload.file_length = MIN(m.len, 90); fwupload.file_length_round = (fwupload.file_length + 1 + 0x0f) & ~0x0f; // Round up to multiple of 16 (with nul-terminator) fwupload.queue(&m.data[0], fwupload.file_length); if (fwupload.file_length_round > fwupload.file_length) { uint8_t pad[16] = {0}; fwupload.queue(&pad[0], fwupload.file_length_round - fwupload.file_length); } } else { // m.type == 42 // sending DFU uint32_t ofs=0; memcpy(&ofs, &m.data[0], 4); // Assumes the endianness of the data! Debug(4, "got data96 of len %u from chan %u at offset %u\n", m.len, chan, unsigned(ofs)); if (ofs == 0) { fwupload.reset(); // Typically file_length = 0x3906; file_length_round = 0x3980; fwupload.file_length = ((uint16_t(m.data[4]) << 8) | (m.data[5])) + 6; // Add the header to the length fwupload.file_length_round = (fwupload.file_length + 0x7f) & ~0x7f; // Round up to multiple of 128 } if (ofs != fwupload.added) { fwupload.need_ack = true; // We want more data } else { // sending a chunk of firmware OTA upload fwupload.fw_type = TELEM_FW; fwupload.queue(&m.data[4], MIN(m.len-4, 92)); // This might fail if mavlink sends it too fast to me, in which case it will retry later } } sem.give(); } } // ---------------------------------------------------------------------------- // Update the telemetry status variable; can be called in irq thread // since the functions it calls are lightweight void AP_Radio_beken::update_SRT_telemetry(void) { t_status.flags = 0; t_status.flags |= AP_Notify::flags.gps_status >= 3?TELEM_FLAG_GPS_OK:0; t_status.flags |= AP_Notify::flags.pre_arm_check?TELEM_FLAG_ARM_OK:0; t_status.flags |= AP_Notify::flags.failsafe_battery?0:TELEM_FLAG_BATT_OK; t_status.flags |= hal.util->get_soft_armed()?TELEM_FLAG_ARMED:0; t_status.flags |= AP_Notify::flags.have_pos_abs?TELEM_FLAG_POS_OK:0; t_status.flags |= AP_Notify::flags.video_recording?TELEM_FLAG_VIDEO:0; t_status.flight_mode = AP_Notify::flags.flight_mode; t_status.tx_max = get_tx_max_power(); t_status.note_adjust = get_tx_buzzer_adjust(); } // ---------------------------------------------------------------------------- // Update a radio control packet // Called from IRQ context. // Returns true for DFU or TUNE, false for telemetry bool AP_Radio_beken::UpdateTxData(void) { // send reboot command if appropriate fwupload.counter++; if ((fwupload.acked >= fwupload.file_length_round) && (fwupload.fw_type == TELEM_FW) && // Not a tune request (fwupload.rx_ack) && (fwupload.acked >= 0x1000)) { // Sanity check fwupload.rx_reboot = true; } if (fwupload.rx_reboot && // Sanity check ((fwupload.counter & 0x01) != 0) && // Avoid starvation of telemetry sem.take_nonblocking()) { // Is the other threads busy with fwupload data? fwupload.rx_ack = false; // Tell the Tx to reboot packetFormatDfu* tx = &beken.pktDataDfu; tx->packetType = BK_PKT_TYPE_DFU; uint16_t addr = 0x0002; // Command to reboot tx->address_lo = addr & 0xff; tx->address_hi = (addr >> 8); DebugPrintf(2, "reboot %u %u\r\n", fwupload.acked, fwupload.file_length_round); sem.give(); return true; } else if ((fwupload.acked >= fwupload.file_length_round) && (fwupload.fw_type == TELEM_PLAY) && // Atune request (fwupload.rx_ack) && ((fwupload.counter & 0x01) != 0) && // Avoid starvation of telemetry (fwupload.acked > 0) && // Sanity check sem.take_nonblocking()) { // Is the other threads busy with fwupload data? fwupload.reset(); // Tell the Tx the tune is complete packetFormatDfu* tx = &beken.pktDataDfu; tx->packetType = BK_PKT_TYPE_TUNE; uint16_t addr = 0x0004; // Command to finalise the tune tx->address_lo = addr & 0xff; tx->address_hi = (addr >> 8); sem.give(); return true; } // send firmware update packet for 7/8 of packets if any data pending else if ((fwupload.added >= (fwupload.acked + SZ_DFU)) && // Do we have a new packet to upload? ((fwupload.counter & 0x07) != 0) && // Avoid starvation of telemetry sem.take_nonblocking()) { // Is the other threads busy with fwupload data? // Send DFU packet packetFormatDfu* tx = &beken.pktDataDfu; if (fwupload.sent > fwupload.acked) { // Resend the last tx packet until it is acknowledged DebugPrintf(4, "resend %u %u %u\r\n", fwupload.added, fwupload.sent, fwupload.acked); } else if (fwupload.pending_length() >= SZ_DFU) { // safety check // Send firmware update packet uint16_t addr = fwupload.sent; tx->address_lo = addr & 0xff; tx->address_hi = (addr >> 8); fwupload.dequeue(&tx->data[0], SZ_DFU); // (updated sent, pending_tail) DebugPrintf(4, "send %u %u %u\r\n", fwupload.added, fwupload.sent, fwupload.acked); if (fwupload.fw_type == TELEM_PLAY) { tx->packetType = BK_PKT_TYPE_TUNE; } else if (fwupload.fw_type == TELEM_FW) { tx->packetType = BK_PKT_TYPE_DFU; if (fwupload.free_length() > 96) { fwupload.need_ack = true; // Request a new mavlink packet } } } sem.give(); return true; } else { // Send telemetry packet packetFormatTx* tx = &beken.pktDataTx; update_SRT_telemetry(); tx->packetType = BK_PKT_TYPE_TELEMETRY; ///< The packet type tx->pps = t_status.pps; tx->flags = t_status.flags; tx->droneid[0] = myDroneId[0]; tx->droneid[1] = myDroneId[1]; tx->droneid[2] = myDroneId[2]; tx->droneid[3] = myDroneId[3]; tx->flight_mode = t_status.flight_mode; tx->wifi = t_status.wifi_chan + (24 * t_status.tx_max); tx->note_adjust = t_status.note_adjust; // CPM bodge - use "Radio Protocol>0" to mean "Adaptive Frequency hopping disabled" // This should move to a different parameter. // Also the thresholds for swapping should move to be parameters. if (get_protocol()) { tx->hopping = 0; // Adaptive frequency hopping disabled } else { tx->hopping = adaptive.hopping; // Tell the tx what we want to use } telem_send_count++; return false; } } // ---------------------------------------------------------------------------- // When (most of) a 92 byte packet has been sent to the Tx, ask for another one // called from main thread void AP_Radio_beken::check_fw_ack(void) { if (fwupload.need_ack && sem.take_nonblocking()) { // ack the send of a DATA96 fw packet to TX if (fwupload.added < fwupload.file_length) { fwupload.need_ack = false; uint8_t data16[16] {}; uint32_t ack_to = fwupload.added; memcpy(&data16[0], &ack_to, 4); // Assume endianness matches mavlink_msg_data16_send(fwupload.chan, 42, 4, data16); } else if (fwupload.added & 0x7f) { // Are we on a boundary // Pad out some bytes at the end uint8_t data16[16]; memset(&data16[0], 0, sizeof(data16)); if (fwupload.free_length() > 16) { fwupload.queue(&data16[0], 16-(fwupload.added & 15)); } DebugPrintf(4, "Pad to %d\r\n", fwupload.added); } else if (fwupload.acked < fwupload.added) { // Keep sending to the tx until it is acked DebugPrintf(4, "PadResend %u %u %u\r\n", fwupload.added, fwupload.sent, fwupload.acked); } else { fwupload.need_ack = false; // All done DebugPrintf(3, "StopUpload\r\n"); uint8_t data16[16] {}; uint32_t ack_to = fwupload.file_length; // Finished memcpy(&data16[0], &ack_to, 4); // Assume endianness matches mavlink_msg_data16_send(fwupload.chan, 42, 4, data16); } sem.give(); } } // ---------------------------------------------------------------------------- /* support all 4 rc input modes by swapping channels. */ void AP_Radio_beken::map_stick_mode(void) { switch (get_stick_mode()) { case 1: { // mode1 = swap throttle and pitch uint16_t tmp = pwm_channels[1]; pwm_channels[1] = pwm_channels[2]; pwm_channels[2] = tmp; break; } case 3: { // mode3 = swap throttle and pitch, swap roll and yaw uint16_t tmp = pwm_channels[1]; pwm_channels[1] = pwm_channels[2]; pwm_channels[2] = tmp; tmp = pwm_channels[0]; pwm_channels[0] = pwm_channels[3]; pwm_channels[3] = tmp; break; } case 4: { // mode4 = swap roll and yaw uint16_t tmp = pwm_channels[0]; pwm_channels[0] = pwm_channels[3]; pwm_channels[3] = tmp; break; } case 2: default: // nothing to do, transmitter is natively mode2 break; } // reverse pitch input to match ArduPilot default pwm_channels[1] = 3000 - pwm_channels[1]; } // ---------------------------------------------------------------------------- // This is a valid manual/auto binding packet. // The type of binding is valid now, and it came with the right address. // Lets check to see if it wants to be for another drone though // Return 1 on double binding uint8_t AP_Radio_beken::ProcessBindPacket(const packetFormatRx * rx) { // Did the tx pick a drone yet? uint32_t did = ((uint32_t)rx->u.bind.droneid[0]) | ((uint32_t)rx->u.bind.droneid[1] << 8) | ((uint32_t)rx->u.bind.droneid[2] << 16) | ((uint32_t)rx->u.bind.droneid[3] << 24); uint32_t mid = ((uint32_t)myDroneId[0]) | ((uint32_t)myDroneId[1] << 8) | ((uint32_t)myDroneId[2] << 16) | ((uint32_t)myDroneId[3] << 24); if (did & 0xff) { // If the first byte is zero, the drone id is not set (compatibility with old tx code) // Is it me or someone else? if (did != mid) { // This tx is not for us! if (!valid_connection && !already_bound) { // Keep searching! Debug(1, "WrongDroneId: %08lx vs %08lx\n", did, mid); BadDroneId(); return 1; } } } // Set the address on which we are receiving the control data syncch.SetChannel(rx->channel); // Can be factory test channels if wanted if (get_factory_test() == 0) { // Final check that we are not in factory mode adaptive.Invalidate(); syncch.SetHopping(0, rx->u.bind.hopping); beken.SetAddresses(&rx->u.bind.bind_address[0]); Debug(1, " Bound to %x %x %x %x %x\r\n", rx->u.bind.bind_address[0], rx->u.bind.bind_address[1], rx->u.bind.bind_address[2], rx->u.bind.bind_address[3], rx->u.bind.bind_address[4]); save_bind_info(); // May take some time } return 0; } // ---------------------------------------------------------------------------- // Handle receiving a packet (we are still in an interrupt!) // Return 1 if we want to stay on the current radio frequency instead of hopping (double binding) uint8_t AP_Radio_beken::ProcessPacket(const uint8_t* packet, uint8_t rxaddr) { uint8_t result = 0; const packetFormatRx * rx = (const packetFormatRx *) packet; // Interpret the packet data switch (rx->packetType) { case BK_PKT_TYPE_CTRL_FOUND: case BK_PKT_TYPE_CTRL_LOST: // We haz data if (rxaddr == 0) { syncch.SetChannelIfSafe(rx->channel); synctm.packet_timer = AP_HAL::micros(); // This is essential for letting the channels update if (!already_bound) { already_bound = true; // Do not autobind to a different tx unless we power off // test rssi beken.EnableCarrierDetect(false); // Save 1ma of power beken.WriteReg(BK_WRITE_REG|BK_EN_RXADDR, 0x01); // Ignore the binding channel, which might be from competing siren txs. } adaptive.Get(rx->channel); // Give the good news to the adaptive logic // Put the data into the control values (assuming mode2) pwm_channels[0] = 1000 + rx->u.ctrl.roll + (uint16_t(rx->u.ctrl.msb & 0xC0) << 2); // Roll pwm_channels[1] = 1000 + rx->u.ctrl.pitch + (uint16_t(rx->u.ctrl.msb & 0x30) << 4); // Pitch pwm_channels[2] = 1000 + rx->u.ctrl.throttle + (uint16_t(rx->u.ctrl.msb & 0x0C) << 6); // Throttle pwm_channels[3] = 1000 + rx->u.ctrl.yaw + (uint16_t(rx->u.ctrl.msb & 0x03) << 8); // Yaw pwm_channels[4] = 1000 + ((rx->u.ctrl.buttons_held & 0x07) >> 0) * 100; // SW1, SW2, SW3 pwm_channels[5] = 1000 + ((rx->u.ctrl.buttons_held & 0x38) >> 3) * 100; // SW4, SW5, SW6 // cope with mode1/mode2/mode3/mode4 map_stick_mode(); chan_count = MAX(chan_count, 7); switch (rx->u.ctrl.data_type) { case BK_INFO_FW_VER: break; case BK_INFO_DFU_RX: { uint16_t ofs = rx->u.ctrl.data_value_hi; ofs <<= 8; ofs |= rx->u.ctrl.data_value_lo; if (ofs == fwupload.acked + SZ_DFU) { fwupload.acked = ofs; } if ((ofs == fwupload.acked) && (ofs > 0)) { fwupload.rx_ack = true; } if ((ofs == 0) && fwupload.rx_reboot) { fwupload.reset(); } } break; case BK_INFO_FW_CRC_LO: break; case BK_INFO_FW_CRC_HI: break; case BK_INFO_FW_YM: tx_date.firmware_year = rx->u.ctrl.data_value_hi; tx_date.firmware_month = rx->u.ctrl.data_value_lo; break; case BK_INFO_FW_DAY: tx_date.firmware_day = rx->u.ctrl.data_value_hi; break; case BK_INFO_MODEL: break; case BK_INFO_PPS: tx_pps = rx->u.ctrl.data_value_lo; // Remember pps from tx if (!have_tx_pps) { have_tx_pps = 2; if (tx_pps == 0) { // Has the tx not been receiving telemetry from someone else recently? valid_connection = true; } else { // the TX has received more telemetry packets in the last second // than we have ever sent. There must be another RX sending // telemetry packets. We will reset our mfg_id and go back waiting // for a new bind packet, hopefully with the right TX Debug(1, "Double-bind detected via PPS %d\n", (int) tx_pps); BadDroneId(); result = 1; } } else { have_tx_pps = 2; } break; case BK_INFO_BATTERY: // "voltage from TX is in 0.025 volt units". Convert to 0.01 volt units for easier display // The CC2500 code (and this) actually assumes it is in 0.04 volt units, hence the tx scaling by 23/156 (38/256) instead of 60/256) // Which means a maximum value is 152 units representing 6.0v rather than 240 units representing 6.0v pwm_channels[6] = rx->u.ctrl.data_value_lo * 4; break; case BK_INFO_COUNTDOWN: if (get_factory_test() == 0) { if (rx->u.ctrl.data_value_lo) { syncch.SetCountdown(rx->u.ctrl.data_value_lo+1, rx->u.ctrl.data_value_hi); adaptive.Invalidate(); DebugPrintf(2, "(%d) ", rx->u.ctrl.data_value_lo); } } break; case BK_INFO_HOPPING0: if (get_factory_test() == 0) { syncch.SetHopping(rx->u.ctrl.data_value_lo, rx->u.ctrl.data_value_hi); // DebugPrintf(2, "[%d] ", rx->u.ctrl.data_value_lo); } break; case BK_INFO_HOPPING1: // Ignored so far break; case BK_INFO_DRONEID0: // Does this Tx even want to talk to me? if (rx->u.ctrl.data_value_lo || rx->u.ctrl.data_value_hi) { if ((rx->u.ctrl.data_value_lo != myDroneId[0]) || (rx->u.ctrl.data_value_hi != myDroneId[1])) { Debug(1, "Bad DroneID0 %02x %02x\n", rx->u.ctrl.data_value_lo, rx->u.ctrl.data_value_hi); BadDroneId(); // Bad drone id - disconnect from this tx result = 1; } } break; case BK_INFO_DRONEID1: // Does this Tx even want to talk to me? if (rx->u.ctrl.data_value_lo || rx->u.ctrl.data_value_hi) { if ((rx->u.ctrl.data_value_lo != myDroneId[2]) || (rx->u.ctrl.data_value_hi != myDroneId[3])) { Debug(1, "Bad DroneID1 %02x %02x\n", rx->u.ctrl.data_value_lo, rx->u.ctrl.data_value_hi); BadDroneId(); // Bad drone id - disconnect from this tx result = 1; } } break; default: break; }; } break; case BK_PKT_TYPE_BIND_AUTO: if (rxaddr == 1) { if (get_autobind_rssi() > BK_RSSI_DEFAULT) { // Have we disabled autobind using fake RSSI parameter? Debug(2, "X0"); break; } if (get_autobind_time() == 0) { // Have we disabled autobind using zero time parameter? Debug(2, "X1"); break; } if (already_bound) { // Do not auto-bind (i.e. to another tx) until we reboot. Debug(2, "X2"); break; } uint32_t now = AP_HAL::millis(); if (now < get_autobind_time() * 1000) { // Is this too soon from rebooting/powering up to autobind? Debug(2, "X3"); break; } // Check the carrier detect to see if the drone is too far away to auto-bind if (!beken.CarrierDetect()) { Debug(2, "X4"); break; } result = ProcessBindPacket(rx); } break; case BK_PKT_TYPE_BIND_MANUAL: // Sent by the tx for a few seconds after power-up when a button is held down if (rxaddr == 1) { if (bind_time_ms == 0) { // We have never receiving a binding click Debug(2, "X5"); break; // Do not bind } if (already_bound) { // Do not manually-bind (i.e. to another tx) until we reboot. Debug(2, "X6"); break; } // if (uint32_t(AP_HAL::millis() - bind_time_ms) > 1000ul * 60u) // Have we pressed the button to bind recently? One minute timeout // break; // Do not bind result = ProcessBindPacket(rx); } break; case BK_PKT_TYPE_TELEMETRY: case BK_PKT_TYPE_DFU: default: // This is one of our packets! Ignore it. break; } return result; } // ---------------------------------------------------------------------------- // Prepare to send a FCC packet void AP_Radio_beken::UpdateFccScan(void) { // Support scan mode if (beken.fcc.scan_mode) { beken.fcc.scan_count++; if (beken.fcc.scan_count >= 200) { beken.fcc.scan_count = 0; beken.fcc.channel += 2; // Go up by 2Mhz if (beken.fcc.channel >= CHANNEL_FCC_HIGH) { beken.fcc.channel = CHANNEL_FCC_LOW; } } } } // ---------------------------------------------------------------------------- // main IRQ handler void AP_Radio_beken::irq_handler(uint32_t when) { if (beken.fcc.fcc_mode) { // don't process interrupts in FCCTEST mode beken.WriteReg(BK_WRITE_REG | BK_STATUS, (BK_STATUS_RX_DR | BK_STATUS_TX_DS | BK_STATUS_MAX_RT)); // clear RX_DR or TX_DS or MAX_RT interrupt flag return; } // Determine which state fired the interrupt bool bNext = false; bool bRx = false; uint8_t bk_sta = beken.ReadStatus(); if (bk_sta & BK_STATUS_TX_DS) { DEBUG1_LOW(); DEBUG1_HIGH(); DEBUG1_LOW(); DEBUG1_HIGH(); DEBUG1_LOW(); DEBUG1_HIGH(); // Packet was sent towards the Tx board synctm.tx_time_us = when; beken.SwitchToIdleMode(); if (beken.fcc.disable_crc_mode && !beken.fcc.disable_crc) { beken.SetCrcMode(true); } bNext = bRx = true; } if (bk_sta & BK_STATUS_MAX_RT) { // We have had a "max retries" error } bool bReply = false; if (bk_sta & BK_STATUS_RX_DR) { DEBUG1_LOW(); DEBUG1_HIGH(); DEBUG1_LOW(); DEBUG1_HIGH(); // We have received a packet uint8_t rxstd = 0; // Which pipe (address) have we received this packet on? if ((bk_sta & BK_STATUS_RX_MASK) == BK_STATUS_RX_P_0) { rxstd = 0; } else if ((bk_sta & BK_STATUS_RX_MASK) == BK_STATUS_RX_P_1) { rxstd = 1; } else { stats.recv_errors++; } bNext = true; uint8_t len, fifo_sta; uint8_t packet[32]; do { stats.recv_packets++; len = beken.ReadReg(BK_R_RX_PL_WID_CMD); // read received packet length in bytes if (len <= PACKET_LENGTH_RX_MAX) { bReply = true; synctm.Rx(when); // printf("R%d ", when - next_switch_us); next_switch_us = when + synctm.sync_time_us + 1500; // Switch channels if we miss the next packet // This includes short packets (e.g. where no telemetry was sent) beken.ReadRegisterMulti(BK_RD_RX_PLOAD, packet, len); // read receive payload from RX_FIFO buffer // DebugPrintf(3, "Packet %d(%d) %d %d %d %d %d %d %d %d ...\r\n", rxstd, len, // packet[0], packet[1], packet[2], packet[3], packet[4], packet[5], packet[6], packet[7]); } else { // Packet was too long beken.ReadRegisterMulti(BK_RD_RX_PLOAD, packet, 32); // read receive payload from RX_FIFO buffer beken.Strobe(BK_FLUSH_RX); // flush Rx } fifo_sta = beken.ReadReg(BK_FIFO_STATUS); // read register FIFO_STATUS's value } while (!(fifo_sta & BK_FIFO_STATUS_RX_EMPTY)); // while not empty beken.WriteReg(BK_WRITE_REG | BK_STATUS, (BK_STATUS_RX_DR | BK_STATUS_TX_DS | BK_STATUS_MAX_RT)); // clear RX_DR or TX_DS or MAX_RT interrupt flag if (1 == ProcessPacket(packet, rxstd)) { bNext = false; // Because double binding detected } if (beken.fcc.enable_cd) { beken.fcc.last_cd = beken.CarrierDetect(); // Detect if close or not } else { beken.fcc.last_cd = true; // Assumed to be close } } // Clear the bits beken.WriteReg((BK_WRITE_REG|BK_STATUS), (BK_STATUS_MAX_RT | BK_STATUS_TX_DS | BK_STATUS_RX_DR)); if (bReply) { uint32_t now = AP_HAL::micros(); uint32_t delta = chTimeUS2I(800 + next_switch_us - now); // Do not use US2ST since that will overflow 32 bits chSysLock(); chVTResetI(&timeout_vt); // Stop the normal timeout chVTSetI(&timeout_vt, delta, trigger_timeout_event, nullptr); // Timeout after 7ms chSysUnlock(); if (get_telem_enable() && have_tx_pps) { // Note that the user can disable telemetry, but the transmitter will be less functional in this case. bNext = bRx = false; // Send the telemetry reply to the controller beken.Strobe(BK_FLUSH_TX); // flush Tx beken.ClearAckOverflow(); bool txDfu = UpdateTxData(); if (txDfu) { beken.pktDataDfu.channel = syncch.channel; } else { beken.pktDataTx.channel = syncch.channel; } if (beken.fcc.disable_crc_mode) { // Only disable the CRC on reception, not transmission, so the connection remains. beken.SwitchToIdleMode(); beken.SetCrcMode(false); } beken.SwitchToTxMode(); DEBUG1_LOW(); hal.scheduler->delay_microseconds(200); // delay to give the (remote) tx a chance to switch to receive mode DEBUG1_HIGH(); if (txDfu) { beken.SendPacket(BK_W_TX_PAYLOAD_NOACK_CMD, (uint8_t *)&beken.pktDataDfu, PACKET_LENGTH_TX_DFU); } else { beken.SendPacket(BK_W_TX_PAYLOAD_NOACK_CMD, (uint8_t *)&beken.pktDataTx, PACKET_LENGTH_TX_TELEMETRY); } } else { // Try to still work when telemetry is disabled bNext = true; } } if (bNext) { nextChannel(1); } if (bRx) { beken.SwitchToRxMode(); // Prepare to receive next packet (on the next channel) } } // ---------------------------------------------------------------------------- // handle timeout IRQ (called when we need to switch channels) void AP_Radio_beken::irq_timeout(uint32_t when) { DEBUG1_LOW(); DEBUG1_HIGH(); DEBUG1_LOW(); DEBUG1_HIGH(); DEBUG1_LOW(); DEBUG1_HIGH(); DEBUG1_LOW(); DEBUG1_HIGH(); DEBUG1_LOW(); DEBUG1_HIGH(); if (beken.bkReady) { // We are not reinitialising the chip in the main thread static uint8_t check_params_timer = 0; if (++check_params_timer >= 10) { // We don't need to test the parameter logic every ms. // Every 50ms get here bool bOldReady = beken.bkReady; beken.bkReady = false; check_params_timer = 0; // Set the transmission power uint8_t pwr = get_transmit_power(); // 1..8 if (pwr != beken.fcc.power + 1) { if ((pwr > 0) && (pwr <= 8)) { beken.SwitchToIdleMode(); beken.SetPower(pwr-1); // (this will set beken.fcc.power) } } // Set CRC mode uint8_t crc = get_disable_crc(); if (crc != beken.fcc.disable_crc_mode) { beken.SwitchToIdleMode(); beken.SetCrcMode(crc); beken.fcc.disable_crc_mode = crc; } // Do we need to change our factory test mode? uint8_t factory = get_factory_test(); if (factory != beken.fcc.factory_mode) { beken.SwitchToIdleMode(); // Set frequency syncch.channel = factory ? (factory-1) + CHANNEL_COUNT_LOGICAL*CHANNEL_NUM_TABLES : 0; // Set address beken.SetFactoryMode(factory); } // Do we need to change our fcc test mode status? uint8_t fcc = get_fcc_test(); if (fcc != beken.fcc.fcc_mode) { beken.Strobe(BK_FLUSH_TX); if (fcc == 0) { // Turn off fcc test mode if (beken.fcc.CW_mode) { beken.SwitchToIdleMode(); beken.SetCwMode(false); } } else { if (fcc > 3) { if (!beken.fcc.CW_mode) { beken.SwitchToIdleMode(); beken.SetCwMode(true); beken.DumpRegisters(); } } else { if (beken.fcc.CW_mode) { beken.SwitchToIdleMode(); beken.SetCwMode(false); } } switch (fcc) { case 1: case 4: default: beken.fcc.channel = CHANNEL_FCC_LOW; break; case 2: case 5: beken.fcc.channel = CHANNEL_FCC_MID; break; case 3: case 6: beken.fcc.channel = CHANNEL_FCC_HIGH; break; }; } beken.fcc.fcc_mode = fcc; DebugPrintf(1, "\r\nFCC mode %d\r\n", fcc); } beken.bkReady = bOldReady; } // For fcc mode, just send packets on timeouts (every 5ms) if (beken.fcc.fcc_mode) { beken.SwitchToTxMode(); beken.ClearAckOverflow(); UpdateFccScan(); beken.SetChannel(beken.fcc.channel); UpdateTxData(); beken.pktDataTx.channel = 0; if (!beken.fcc.CW_mode) { beken.SendPacket(BK_WR_TX_PLOAD, (uint8_t *)&beken.pktDataTx, PACKET_LENGTH_TX_TELEMETRY); } } else { // Normal modes - we have timed out for channel hopping int32_t d = synctm.sync_time_us; // Time between packets, e.g. 5100 us uint32_t dt = when - synctm.rx_time_us; if (dt > 50*d) { // We have lost sync (missed 50 packets) so slow down the channel hopping until we resync d *= 5; // 3 or 5 are relatively prime to the table size of 16. DebugPrintf(2, "C"); if (dt > 120*d) { // We have missed 3 seconds - try the safe WiFi table DebugPrintf(2, "S"); syncch.SafeTable(); } } else { // DebugPrintf(2, "c%d ", AP_HAL::micros() - next_switch_us); DebugPrintf(2, "c"); adaptive.Miss(syncch.channel); } { uint8_t fifo_sta = radio_singleton->beken.ReadReg(BK_FIFO_STATUS); // read register FIFO_STATUS's value if (!(fifo_sta & BK_FIFO_STATUS_RX_EMPTY)) { // while not empty DEBUG1_LOW(); DEBUG1_HIGH(); DebugPrintf(2, "#"); // We have received a packet, but the interrupt was not triggered! radio_singleton->irq_handler(next_switch_us); // Use this broken time DEBUG1_LOW(); DEBUG1_HIGH(); } else { next_switch_us += d; // Switch channels if we miss the next packet } } int32_t ss = int32_t(next_switch_us - when); if (ss < 1000) { // Not enough time next_switch_us = when + d; // Switch channels if we miss the next packet DebugPrintf(2, "j"); } beken.SwitchToIdleMode(); nextChannel(1); // Switch to the next channel beken.SwitchToRxMode(); beken.ClearAckOverflow(); } } // Ask for another timeout uint32_t now = AP_HAL::micros(); if (int32_t(next_switch_us - when) < 300) { // Too late for that one next_switch_us = now + synctm.sync_time_us; } if (int32_t(next_switch_us - now) < 250) { // Too late for this one next_switch_us = now + synctm.sync_time_us; } uint32_t delta = chTimeUS2I(next_switch_us - now); // Do not use US2ST since that will overflow 32 bits. chSysLock(); chVTSetI(&timeout_vt, delta, trigger_timeout_event, nullptr); // Timeout every 5 ms chSysUnlock(); } // ---------------------------------------------------------------------------- // Thread that supports Beken Radio work triggered by interrupts // This is the only thread that should access the Beken radio chip via SPI. void AP_Radio_beken::irq_handler_thd(void *arg) { (void) arg; while (true) { DEBUG1_LOW(); eventmask_t evt = chEvtWaitAny(ALL_EVENTS); DEBUG1_HIGH(); if (_irq_handler_ctx != nullptr) { // Sanity check _irq_handler_ctx->name = "RadioBeken"; // Only useful to be done once but here is done often } radio_singleton->beken.lock_bus(); switch (evt) { case EVT_IRQ: if (radio_singleton->beken.fcc.fcc_mode != 0) { DebugPrintf(3, "IRQ FCC\n"); } radio_singleton->irq_handler(isr_irq_time_us); break; case EVT_TIMEOUT: radio_singleton->irq_timeout(isr_timeout_time_us); break; case EVT_BIND: // The user has clicked on the "Start Bind" button on the web interface DebugPrintf(2, "\r\nBtnStartBind\r\n"); break; default: break; } radio_singleton->beken.unlock_bus(); } } void AP_Radio_beken::setChannel(uint8_t channel) { beken.SetChannel(channel); } const uint8_t bindHopData[256] = { #if 0 // Support single frequency mode (no channel hopping) // Normal frequencies 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Normal 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 1,2,3,4,5 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 6 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 7 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 8 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 9,10,11 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Test mode channels 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Reserved // Alternative frequencies 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Normal 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 1,2,3,4,5 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 6 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 7 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 8 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Wifi channel 9,10,11 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Test mode channels 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23, // Reserved #else // Frequency hopping // Normal frequencies 47,21,31,52,36,13,72,41, 69,56,16,26,61,10,45,66, // Normal 57,62,67,72,58,63,68,59, 64,69,60,65,70,61,66,71, // Wifi channel 1,2,3,4,5 (2457..2472MHz) 62,10,67,72,63,68,11,64, 69,60,65,70,12,61,66,71, // Wifi channel 6 10,67,11,72,12,68,13,69, 14,65,15,70,16,66,17,71, // Wifi channel 7 10,70,15,20,14,71,16,21, 12,17,22,72,13,18,11,19, // Wifi channel 8 10,15,20,25,11,16,21,12, 17,22,13,18,23,14,19,24, // Wifi channel 9,10,11 46,41,31,52,36,13,72,69, 21,56,16,26,61,66,10,43, // Test mode channels 46,41,31,52,36,13,72,69, 21,56,16,26,61,66,10,43, // Reserved // Alternative frequencies 17,11,63,19,67,44,43,38, 50,54,70,58,29,35,25,14, // Normal 18,10,23,21,33,44,41,38, 52,45,47,25,30,35,49,14, // Wifi channel 1,2,3,4,5 18,56,23,21,33,44,41,38, 52,45,47,25,30,35,49,14, // Wifi channel 6 18,56,23,21,33,44,41,38, 52,45,47,25,30,35,49,61, // Wifi channel 7 68,56,24,53,33,44,41,38, 28,45,47,65,30,35,49,61, // Wifi channel 8 68,56,72,53,33,44,41,38, 28,45,47,65,30,35,49,61, // Wifi channel 9,10,11 46,41,31,52,36,13,72,69, 21,56,16,26,61,66,10,43, // Test mode channels (as normal) 46,41,31,52,36,13,72,69, 21,56,16,26,61,66,10,43, // Reserved (as normal) #endif }; void AP_Radio_beken::nextChannel(uint8_t skip) { if (skip) { syncch.NextChannel(); } setChannel(bindHopData[syncch.channel]); } /* save bind info */ void AP_Radio_beken::save_bind_info(void) { // access to storage for bind information StorageAccess bind_storage(StorageManager::StorageBindInfo); struct bind_info info; info.magic = bind_magic; info.bindTxId[0] = beken.TX_Address[0]; info.bindTxId[1] = beken.TX_Address[1]; info.bindTxId[2] = beken.TX_Address[2]; info.bindTxId[3] = beken.TX_Address[3]; info.bindTxId[4] = beken.TX_Address[4]; bind_storage.write_block(0, &info, sizeof(info)); } /* load bind info */ bool AP_Radio_beken::load_bind_info(void) { // access to storage for bind information StorageAccess bind_storage(StorageManager::StorageBindInfo); struct bind_info info; if (!bind_storage.read_block(&info, 0, sizeof(info)) || info.magic != bind_magic) { return false; } beken.SetAddresses(&info.bindTxId[0]); return true; } // ---------------------------------------------------------------------------- void AP_Radio_beken::BadDroneId(void) { if (stats.recv_packets >= 1000) { // We are already chatting to this TX for some time. return; // Do not disconnect from it. } // clear the current bind information valid_connection = false; // with luck we will connect to another tx beken.SwitchToIdleMode(); beken.SetFactoryMode(0); // Reset the tx address adaptive.Invalidate(); syncch.SetHopping(0,0); already_bound = false; // Not already solidly bound to a drone stats.recv_packets = 0; beken.WriteReg(BK_WRITE_REG|BK_EN_RXADDR, 0x02); have_tx_pps = false; } // ---------------------------------------------------------------------------- // Which bits correspond to each channel within a table, for adaptive frequencies static const uint8_t channel_bit_table[CHANNEL_COUNT_LOGICAL] = { 0x01, 0, 0x02, 0, 0x04, 0, 0x08, 0, 0x10, 0, 0x20, 0, 0x40, 0, 0x80, 0 }; // Step through the channels void SyncChannel::NextChannel(void) { channel &= 0x7f; if (channel >= CHANNEL_COUNT_LOGICAL*CHANNEL_NUM_TABLES) { // We are in the factory test modes. Keep the channel as is. } else { if (countdown != countdown_invalid) { if (--countdown == 0) { channel = countdown_chan; countdown = countdown_invalid; hopping_current = hopping_wanted = 0; return; } } else if (hopping_countdown != countdown_invalid) { if (--hopping_countdown == 0) { hopping_current = hopping_wanted; hopping_countdown = countdown_invalid; // printf("{Use %d} ", hopping_current); } } uint8_t table = channel / CHANNEL_COUNT_LOGICAL; channel = (channel + 1) % CHANNEL_COUNT_LOGICAL; channel += table * CHANNEL_COUNT_LOGICAL; // Support adaptive frequency hopping if (hopping_current & channel_bit_table[channel % CHANNEL_COUNT_LOGICAL]) { channel |= 0x80; } } } // If we have not received any packets for ages, try a WiFi table that covers all frequencies void SyncChannel::SafeTable(void) { channel &= 0x7f; if (channel >= CHANNEL_COUNT_LOGICAL*CHANNEL_NUM_TABLES) { // We are in the factory test modes. Reset to default table. channel = 0; } else { uint8_t table = channel / CHANNEL_COUNT_LOGICAL; if ((table != CHANNEL_BASE_TABLE) && (table != CHANNEL_SAFE_TABLE)) { // Are we using a table that is high end or low end only? channel %= CHANNEL_COUNT_LOGICAL; channel += CHANNEL_SAFE_TABLE * CHANNEL_COUNT_LOGICAL; } } } // Check if valid channel index; we have received a packet describing the current channel index void SyncChannel::SetChannelIfSafe(uint8_t chan) { if (channel != chan) { DebugPrintf(2, "{{%d}} ", chan); } chan &= 0x7f; // Disregard hopping if (chan >= CHANNEL_COUNT_LOGICAL*CHANNEL_NUM_TABLES) { if (chan == lastchan) { channel = chan; // Allow test mode channels if two in a row } else { chan = 0; // Disallow test mode tables unless followed by each other } lastchan = chan; } else { lastchan = 0; } channel = chan; } // We have received a packet on this channel void SyncAdaptive::Get(uint8_t channel) { uint8_t f = bindHopData[channel]; rx[f]++; } enum { ADAPT_THRESHOLD = 50 }; // Missed packets threshold for adapting the hopping // We have missed a packet on this channel. Consider adapting. void SyncAdaptive::Miss(uint8_t channel) { uint8_t f1 = bindHopData[channel]; missed[f1]++; uint8_t f2 = bindHopData[channel ^ 0x80]; int32_t delta1 = missed[f1] - rx[f1]; int32_t delta2 = missed[f2] - rx[f2]; if ((delta1 > ADAPT_THRESHOLD) && // Worse than 50% reception on this channel (delta1 > delta2)) { // Ok consider swapping this channel uint8_t bit = channel_bit_table[channel % CHANNEL_COUNT_LOGICAL]; if (bit) { // Is an even packet uint8_t oh = hopping; if (channel & 0x80) { // Swap back from alternative hopping &= ~bit; } else { // Swap to alternative hopping |= bit; } if (hopping != oh) { // Have we changed? missed[f2] = rx[f2] = 0; // Reset the values // printf("{%d->%d:%d} ", f1+2400, f2+2400, hopping); } } } } #endif // HAL_RCINPUT_WITH_AP_RADIO