You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1415 lines
54 KiB
1415 lines
54 KiB
/* |
|
driver for Beken_2425 radio |
|
*/ |
|
#include <AP_HAL/AP_HAL.h> |
|
|
|
//#pragma GCC optimize("O0") |
|
|
|
#if defined(HAL_RCINPUT_WITH_AP_RADIO) && CONFIG_HAL_BOARD_SUBTYPE == HAL_BOARD_SUBTYPE_CHIBIOS_SKYVIPER_F412 |
|
|
|
#include <AP_Math/AP_Math.h> |
|
#include "AP_Radio_bk2425.h" |
|
#include <utility> |
|
#include <stdio.h> |
|
#include <StorageManager/StorageManager.h> |
|
#include <AP_Notify/AP_Notify.h> |
|
#include <GCS_MAVLink/GCS_MAVLink.h> |
|
|
|
// 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 |
|
|
|
|