diff --git a/src/config/config.h b/src/config/config.h index 16c940a3a..1b58c92a6 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -84,6 +84,8 @@ // #define RX_USART SERIAL_PORT5 // #define RX_USART SERIAL_PORT6 +#define SMART_AUDIO_USART SERIAL_PORT5 + // *************Serial Receiver Inversion Selection (Normally true for SBUS and FPORT) // #define INVERT_UART diff --git a/src/driver/vtx/vtx.c b/src/driver/vtx.c similarity index 98% rename from src/driver/vtx/vtx.c rename to src/driver/vtx.c index 99669784f..e6a56aef3 100644 --- a/src/driver/vtx/vtx.c +++ b/src/driver/vtx.c @@ -1,4 +1,4 @@ -#include "driver/vtx/vtx.h" +#include "driver/vtx.h" #include "core/debug.h" #include "driver/serial.h" diff --git a/src/driver/vtx/vtx.h b/src/driver/vtx.h similarity index 61% rename from src/driver/vtx/vtx.h rename to src/driver/vtx.h index 1d5194c86..c3c75a607 100644 --- a/src/driver/vtx/vtx.h +++ b/src/driver/vtx.h @@ -3,12 +3,11 @@ #include #include -typedef enum { - VTX_ERROR, - VTX_IDLE, - VTX_WAIT, - VTX_SUCCESS -} vtx_update_result_t; +extern uint32_t vtx_last_valid_read; +extern uint32_t vtx_last_request; + +extern uint8_t vtx_payload[32]; +extern uint8_t vtx_payload_offset; bool serial_vtx_is_ready(); bool serial_vtx_wait_for_ready(); diff --git a/src/driver/vtx/msp.c b/src/driver/vtx/msp.c deleted file mode 100644 index 266d06348..000000000 --- a/src/driver/vtx/msp.c +++ /dev/null @@ -1,158 +0,0 @@ - -#include "driver/vtx/msp.h" - -#include - -#include "core/debug.h" -#include "core/profile.h" -#include "driver/osd/displayport.h" -#include "driver/serial.h" -#include "io/msp.h" -#include "rx/unified_serial.h" -#include "util/crc.h" - -#ifdef USE_VTX - -extern msp_t *msp_vtx; - -extern uint32_t vtx_last_valid_read; -extern uint32_t vtx_last_request; - -static void serial_msp_send(msp_magic_t magic, uint8_t direction, uint16_t cmd, const uint8_t *data, uint16_t len) { - if (magic == MSP2_MAGIC) { - const uint32_t size = len + MSP2_HEADER_LEN + 1; - uint8_t vtx_frame[size]; - - vtx_frame[0] = '$'; - vtx_frame[1] = MSP2_MAGIC; - vtx_frame[2] = direction; - vtx_frame[3] = 0; // flag - vtx_frame[4] = (cmd >> 0) & 0xFF; - vtx_frame[5] = (cmd >> 8) & 0xFF; - vtx_frame[6] = (len >> 0) & 0xFF; - vtx_frame[7] = (len >> 8) & 0xFF; - - memcpy(vtx_frame + MSP2_HEADER_LEN, data, len); - vtx_frame[len + MSP2_HEADER_LEN] = crc8_dvb_s2_data(0, vtx_frame + 3, len + 5); - - serial_vtx_send_data(vtx_frame, size); - } else { - const uint32_t size = len + MSP_HEADER_LEN + 1; - uint8_t vtx_frame[size]; - - vtx_frame[0] = '$'; - vtx_frame[1] = MSP1_MAGIC; - vtx_frame[2] = direction; - vtx_frame[3] = len; - vtx_frame[4] = cmd; - - memcpy(vtx_frame + MSP_HEADER_LEN, data, len); - - uint8_t chksum = len; - for (uint8_t i = 4; i < (size - 1); i++) { - chksum ^= vtx_frame[i]; - } - vtx_frame[len + MSP_HEADER_LEN] = chksum; - - serial_vtx_send_data(vtx_frame, size); - } -} - -extern msp_t displayport_msp; -extern msp_t crsf_msp; - -static uint8_t msp_rx_buffer[128]; -static msp_t msp = { - .buffer = msp_rx_buffer, - .buffer_size = 128, - .buffer_offset = 0, - .send = serial_msp_send, - .device = MSP_DEVICE_VTX, -}; - -void serial_msp_vtx_init() { - if (serial_displayport.config.port != SERIAL_PORT_INVALID) { - // reuse existing msp for hdz - msp_vtx = &displayport_msp; - return; - } - - if (profile.serial.smart_audio == profile.serial.rx && - serial_rx_detected_protcol == RX_SERIAL_PROTOCOL_CRSF) { - msp_vtx = &crsf_msp; - return; - } - - const target_serial_port_t *dev = serial_get_dev(profile.serial.smart_audio); - if (!target_serial_port_valid(dev)) { - return; - } - - serial_port_config_t config; - config.port = profile.serial.smart_audio; - config.baudrate = 9600; - config.direction = SERIAL_DIR_TX_RX; - config.stop_bits = SERIAL_STOP_BITS_1; - config.invert = false; - config.half_duplex = true; - config.half_duplex_pp = false; - - serial_vtx_wait_for_ready(); - serial_init(&serial_vtx, config); - - msp_vtx = &msp; -} - -vtx_update_result_t serial_msp_vtx_update() { - if (serial_displayport.config.port != SERIAL_PORT_INVALID) { - if (!displayport_is_ready()) { - return VTX_WAIT; - } - return VTX_IDLE; - } - - if (profile.serial.smart_audio != SERIAL_PORT_INVALID && - profile.serial.smart_audio == profile.serial.rx && - serial_rx_detected_protcol == RX_SERIAL_PROTOCOL_CRSF) { - return VTX_IDLE; - } - - if (!serial_vtx_is_ready()) { - return VTX_WAIT; - } - - static bool in_progress = false; - static bool is_first_packet = true; - - uint8_t data = 0; - while (serial_vtx_read_byte(&data)) { - quic_debugf("MSP_VTX: read 0x%x %c", data, data); - - in_progress = true; - - msp_status_t status = msp_process_serial(msp_vtx, data); - switch (status) { - case MSP_ERROR: - case MSP_EOF: - break; - case MSP_SUCCESS: - in_progress = false; - is_first_packet = false; - return VTX_SUCCESS; - } - } - - if ((in_progress || is_first_packet) && (time_millis() - vtx_last_valid_read) > 500) { - quic_debugf("MSP_VTX: timeout waiting for packet"); - vtx_last_valid_read = time_millis(); - return VTX_ERROR; - } - - if (in_progress) { - return VTX_WAIT; - } - - return VTX_IDLE; -} - -#endif \ No newline at end of file diff --git a/src/driver/vtx/msp.h b/src/driver/vtx/msp.h deleted file mode 100644 index 332c71810..000000000 --- a/src/driver/vtx/msp.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include - -#include "driver/vtx/vtx.h" - -void serial_msp_vtx_init(); -vtx_update_result_t serial_msp_vtx_update(); diff --git a/src/driver/vtx/sa.c b/src/driver/vtx/sa.c deleted file mode 100644 index 1a57f57c7..000000000 --- a/src/driver/vtx/sa.c +++ /dev/null @@ -1,291 +0,0 @@ -#include "driver/vtx/sa.h" - -#include - -#include "core/debug.h" -#include "core/profile.h" -#include "driver/serial.h" -#include "driver/time.h" -#include "driver/vtx/vtx.h" -#include "io/usb_configurator.h" -#include "io/vtx.h" -#include "util/crc.h" -#include "util/ring_buffer.h" -#include "util/util.h" - -#ifdef USE_VTX - -#define SMART_AUDIO_BAUDRATE_MIN 4650 -#define SMART_AUDIO_BAUDRATE_MAX 5050 - -#define SA_HEADER_SIZE 5 -#define SA_MAGIC_1 0xaa -#define SA_MAGIC_2 0x55 - -typedef enum { - PARSER_IDLE, - PARSER_READ_MAGIC_1, - PARSER_READ_MAGIC_2, - PARSER_READ_CMD, - PARSER_READ_LENGTH, - PARSER_READ_PAYLOAD, - PARSER_READ_CRC, -} smart_audio_parser_state_t; - -smart_audio_settings_t smart_audio_settings; - -static uint32_t baud_rate = 4800; -static uint32_t packets_sent = 0; -static uint32_t packets_recv = 0; - -static smart_audio_parser_state_t parser_state = PARSER_IDLE; - -extern uint32_t vtx_last_valid_read; -extern uint32_t vtx_last_request; - -extern uint8_t vtx_payload[32]; -extern uint8_t vtx_payload_offset; - -const uint8_t default_dac_power_levels[4] = { - 7, - 16, - 25, - 40, -}; - -static void serial_smart_audio_reconfigure() { - serial_vtx_wait_for_ready(); - - const target_serial_port_t *dev = serial_get_dev(profile.serial.smart_audio); - if (!target_serial_port_valid(dev)) { - return; - } - - serial_port_config_t config; - config.port = profile.serial.smart_audio; - config.baudrate = baud_rate; - config.direction = SERIAL_DIR_TX_RX; - config.stop_bits = SERIAL_STOP_BITS_2; - config.invert = false; - config.half_duplex = true; - config.half_duplex_pp = true; - - serial_init(&serial_vtx, config); -} - -static void smart_audio_auto_baud() { - static uint8_t last_percent = 0; - - // move quickly while we have not yet found a working baud rate - const uint8_t current_percent = ((packets_recv * 100) / packets_sent); - if (packets_sent < (last_percent == 0 ? 3 : 10)) { - last_percent = current_percent; - return; - } - - if (current_percent < 70) { - static int8_t direction = 1; - - // if the percentage degraded, switch it up - if (last_percent > current_percent) { - direction = direction == 1 ? -1 : 1; - } - - if ((direction == 1) && (baud_rate == SMART_AUDIO_BAUDRATE_MAX)) { - direction = -1; - } else if ((direction == -1 && baud_rate == SMART_AUDIO_BAUDRATE_MIN)) { - direction = 1; - } - - baud_rate += direction * 50; - quic_debugf("SMART_AUDIO: auto baud %d (%d) change %d vs %d", baud_rate, direction * 50, last_percent, current_percent); - serial_smart_audio_reconfigure(); - } - - last_percent = current_percent; - packets_sent = 0; - packets_recv = 0; -} - -static uint8_t serial_smart_audio_parse_packet(uint8_t cmd, uint8_t *payload, uint32_t length) { - switch (cmd) { - case SA_CMD_GET_SETTINGS: - case SA_CMD_GET_SETTINGS_V2: - case SA_CMD_GET_SETTINGS_V21: - smart_audio_settings.version = (cmd == SA_CMD_GET_SETTINGS ? 1 : (cmd == SA_CMD_GET_SETTINGS_V2 ? 2 : 3)); - smart_audio_settings.channel = payload[0]; - smart_audio_settings.power = payload[1]; - smart_audio_settings.mode = payload[2]; - smart_audio_settings.frequency = (uint16_t)(((uint16_t)payload[3] << 8) | payload[4]); - - if (cmd == SA_CMD_GET_SETTINGS_V21) { - smart_audio_settings.power = payload[5]; - - smart_audio_settings.level_count = min(payload[6], VTX_POWER_LEVEL_MAX); - - // SmartAudio seems to report buf[8] + 1 power levels, but one of them is zero. - // zero is indeed a valid power level to set the vtx to, but it activates pit mode. - // crucially, after sending 0 dbm, the vtx does NOT report its power level to be 0 dbm. - // instead, it reports whatever value was set previously and it reports to be in pit mode. - for (uint8_t i = 0; i < smart_audio_settings.level_count; i++) { - smart_audio_settings.dac_power_levels[i] = payload[7 + i + 1]; //+ 1 to skip the first power level, as mentioned above - } - } else { - smart_audio_settings.level_count = 4; - for (uint8_t i = 0; i < smart_audio_settings.level_count; i++) { - smart_audio_settings.dac_power_levels[i] = default_dac_power_levels[i]; - } - } - break; - - case SA_CMD_SET_FREQUENCY: - smart_audio_settings.frequency = (uint16_t)(((uint16_t)payload[0] << 8) | payload[1]); - break; - - case SA_CMD_SET_CHANNEL: - smart_audio_settings.channel = payload[0]; - break; - - case SA_CMD_SET_POWER: { - smart_audio_settings.power = payload[0]; - break; - } - case SA_CMD_SET_MODE: { - const uint8_t mode = payload[0]; - - // in-range pitmode - smart_audio_settings.mode |= ((mode >> 0) & 0x1) << 2; - - // out-range pitmode - smart_audio_settings.mode |= ((mode >> 1) & 0x1) << 3; - - // pit mode runnig - smart_audio_settings.mode |= ((mode >> 2) & 0x1) << 1; - - // locked bit - smart_audio_settings.mode |= ((mode >> 3) & 0x1) << 4; - break; - } - default: - quic_debugf("SMART_AUDIO: invalid cmd %d (%d)", cmd, length); - return 0; - } - - packets_recv++; - return 1; -} - -void serial_smart_audio_init() { - serial_smart_audio_reconfigure(); -} - -vtx_update_result_t serial_smart_audio_update() { - if (!serial_vtx_is_ready()) { - return VTX_WAIT; - } - if (parser_state == PARSER_IDLE) { - return VTX_IDLE; - } - if ((time_millis() - vtx_last_valid_read) > 500) { - return VTX_ERROR; - } - - static uint8_t length = 0; - - while (true) { - uint8_t data = 0; - if (serial_vtx_read_byte(&data) == 0) { - return VTX_WAIT; - } - quic_debugf("VTX parser_state: %d 0x%x", parser_state, data); - switch (parser_state) { - case PARSER_IDLE: - return VTX_IDLE; - - case PARSER_READ_MAGIC_1: - if (data == SA_MAGIC_1) { - parser_state = PARSER_READ_MAGIC_2; - } - break; - - case PARSER_READ_MAGIC_2: - if (data != SA_MAGIC_2) { - parser_state = PARSER_READ_MAGIC_1; - } else { - parser_state = PARSER_READ_CMD; - } - break; - - case PARSER_READ_CMD: - vtx_payload[0] = data; - parser_state = PARSER_READ_LENGTH; - break; - - case PARSER_READ_LENGTH: - length = vtx_payload[1] = data; - vtx_payload_offset = 0; - parser_state = length ? PARSER_READ_PAYLOAD : PARSER_READ_CRC; - break; - - case PARSER_READ_PAYLOAD: - vtx_payload[vtx_payload_offset + 2] = data; - if (++vtx_payload_offset == length) { - parser_state = PARSER_READ_CRC; - } - break; - - case PARSER_READ_CRC: { - parser_state = PARSER_IDLE; - const uint8_t theirs = crc8_dvb_s2_data(0, vtx_payload, length + 2); - if (data != theirs) { - quic_debugf("VTX crc error 0x%x vs 0x%x", data, theirs); - return VTX_ERROR; - } - if (!serial_smart_audio_parse_packet(vtx_payload[0], vtx_payload + 2, length - 2)) { - return VTX_ERROR; - } - return VTX_SUCCESS; - } - } - } - - return VTX_ERROR; -} - -void serial_smart_audio_send_payload(uint8_t cmd, const uint8_t *payload, const uint32_t size) { - if (!serial_vtx_is_ready()) { - return; - } - -#ifdef USE_AKK_SA_WORKAROUND -#define EXTRA_DUMMY_BYTES 1 -#else -#define EXTRA_DUMMY_BYTES 0 -#endif - - const uint32_t len = size + 1 + SA_HEADER_SIZE + EXTRA_DUMMY_BYTES; - uint8_t vtx_frame[len]; - - vtx_frame[0] = 0x00; - vtx_frame[1] = 0xAA; - vtx_frame[2] = 0x55; - vtx_frame[3] = (cmd << 1) | 0x1; - vtx_frame[4] = size; - for (uint8_t i = 0; i < size; i++) { - vtx_frame[i + SA_HEADER_SIZE] = payload[i]; - } - vtx_frame[size + SA_HEADER_SIZE] = crc8_dvb_s2_data(0, vtx_frame + 1, len - 2 - EXTRA_DUMMY_BYTES); -#ifdef USE_AKK_SA_WORKAROUND - vtx_frame[size + 1 + SA_HEADER_SIZE] = 0x00; -#endif - - smart_audio_auto_baud(); - - serial_vtx_send_data(vtx_frame, len); - if (parser_state == PARSER_IDLE) { - parser_state = PARSER_READ_MAGIC_1; - } - packets_sent++; -} - -#endif \ No newline at end of file diff --git a/src/driver/vtx/sa.h b/src/driver/vtx/sa.h deleted file mode 100644 index f597cf612..000000000 --- a/src/driver/vtx/sa.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include - -#include "driver/vtx/vtx.h" - -typedef enum { - SA_CMD_GET_SETTINGS = 0x01, - SA_CMD_SET_POWER = 0x02, - SA_CMD_SET_CHANNEL = 0x03, - SA_CMD_SET_FREQUENCY = 0x04, - SA_CMD_SET_MODE = 0x05, - SA_CMD_GET_SETTINGS_V2 = 0x9, - SA_CMD_GET_SETTINGS_V21 = 0x11, -} smart_audio_cmd_t; - -typedef enum { - SA_MODE_FREQUENCY = 1 << 0, - SA_MODE_PIT = 1 << 1, - SA_MODE_IN_RANGE_PIT = 1 << 2, - SA_MODE_OUT_RANGE_PIT = 1 << 3, - SA_MODE_UNLOCKED = 1 << 4, -} smart_mode_t; - -typedef struct { - uint8_t version; - uint8_t channel; - uint8_t power; - uint8_t mode; - uint16_t frequency; - uint8_t level_count; - uint16_t dac_power_levels[8]; -} smart_audio_settings_t; - -void serial_smart_audio_init(); -vtx_update_result_t serial_smart_audio_update(); -void serial_smart_audio_send_payload(uint8_t cmd, const uint8_t *payload, const uint32_t size); \ No newline at end of file diff --git a/src/driver/vtx/tramp.c b/src/driver/vtx/tramp.c deleted file mode 100644 index 35a214e2c..000000000 --- a/src/driver/vtx/tramp.c +++ /dev/null @@ -1,176 +0,0 @@ -#include "driver/vtx/tramp.h" - -#include - -#include "core/debug.h" -#include "core/profile.h" -#include "driver/serial.h" -#include "driver/time.h" -#include "driver/vtx/vtx.h" -#include "util/ring_buffer.h" - -#ifdef USE_VTX - -typedef enum { - PARSER_IDLE, - PARSER_READ_MAGIC, - PARSER_READ_PAYLOAD, - PARSER_READ_CRC, - PRASER_WAIT_FOR_READY -} tramp_parser_state_t; - -tramp_settings_t tramp_settings; - -static tramp_parser_state_t parser_state = PARSER_IDLE; - -extern uint32_t vtx_last_valid_read; -extern uint32_t vtx_last_request; - -extern uint8_t vtx_payload[32]; -extern uint8_t vtx_payload_offset; - -static uint8_t crc8_data(const uint8_t *data) { - uint8_t crc = 0; - for (int i = 0; i < 13; i++) { - crc += data[i]; - } - return crc; -} - -void serial_tramp_init() { - serial_vtx_wait_for_ready(); - - const target_serial_port_t *dev = serial_get_dev(profile.serial.smart_audio); - if (!target_serial_port_valid(dev)) { - return; - } - - serial_port_config_t config; - config.port = profile.serial.smart_audio; - config.baudrate = 9600; - config.direction = SERIAL_DIR_TX_RX; - config.stop_bits = SERIAL_STOP_BITS_1; - config.invert = false; - config.half_duplex = true; - config.half_duplex_pp = false; - - serial_init(&serial_vtx, config); -} - -static bool tramp_is_query(uint8_t cmd) { - switch (cmd) { - case 'r': - case 'v': - case 's': - return true; - } - return false; -} - -static uint8_t tramp_parse_packet(uint8_t *payload) { - switch (payload[0]) { - case 'r': - tramp_settings.freq_min = payload[1] | (payload[2] << 8); - tramp_settings.freq_max = payload[3] | (payload[4] << 8); - tramp_settings.power_max = payload[5] | (payload[6] << 8); - break; - - case 'v': - tramp_settings.frequency = payload[1] | (payload[2] << 8); - tramp_settings.power = payload[3] | (payload[4] << 8); - tramp_settings.control_mode = payload[5]; - tramp_settings.pit_mode = payload[6]; - tramp_settings.current_power = payload[7] | (payload[8] << 8); - break; - - case 's': - tramp_settings.temp = payload[5] | (payload[6] << 8); - break; - } - - return 1; -} - -vtx_update_result_t serial_tramp_update() { - if (!serial_vtx_is_ready()) { - return VTX_WAIT; - } - if (parser_state > PARSER_IDLE && (time_millis() - vtx_last_valid_read) > 500) { - return VTX_ERROR; - } - -tramp_do_more: - switch (parser_state) { - case PARSER_IDLE: - return VTX_IDLE; - - case PARSER_READ_MAGIC: { - if (serial_vtx_read_byte(&vtx_payload[0]) == 0) { - return VTX_WAIT; - } - - if (vtx_payload[0] == 0x0F) { - parser_state = PARSER_READ_PAYLOAD; - vtx_payload_offset = 1; - } - goto tramp_do_more; - } - case PARSER_READ_PAYLOAD: { - if (serial_vtx_read_byte(&vtx_payload[vtx_payload_offset]) == 0) { - return VTX_WAIT; - } - - vtx_payload_offset++; - if (vtx_payload_offset >= 16) { - parser_state = PARSER_READ_CRC; - } - goto tramp_do_more; - } - case PARSER_READ_CRC: { - parser_state = PARSER_IDLE; - - const uint8_t crc = crc8_data(vtx_payload + 1); - if (vtx_payload[14] != crc || vtx_payload[15] != 0) { - return VTX_ERROR; - } - - if (!tramp_parse_packet(vtx_payload + 1)) { - return VTX_ERROR; - } - - return VTX_SUCCESS; - } - case PRASER_WAIT_FOR_READY: { - // dummy state for non querry packets - parser_state = PARSER_IDLE; - return VTX_SUCCESS; - } - } - - return VTX_ERROR; -} - -void serial_tramp_send_payload(uint8_t cmd, const uint16_t payload) { - if (!serial_vtx_is_ready()) { - return; - } - - uint8_t vtx_frame[16]; - memset(vtx_frame, 0, 16); - - vtx_frame[0] = 0x0F; - vtx_frame[1] = cmd; - vtx_frame[2] = payload & 0xff; - vtx_frame[3] = (payload >> 8) & 0xff; - vtx_frame[14] = crc8_data(vtx_frame + 1); - - serial_vtx_send_data(vtx_frame, 16); - - if (tramp_is_query(vtx_frame[1])) { - parser_state = PARSER_READ_MAGIC; - } else { - parser_state = PRASER_WAIT_FOR_READY; - } -} - -#endif \ No newline at end of file diff --git a/src/driver/vtx/tramp.h b/src/driver/vtx/tramp.h deleted file mode 100644 index 4eb7b3945..000000000 --- a/src/driver/vtx/tramp.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -#include "driver/vtx/vtx.h" - -typedef struct { - uint16_t freq_min; - uint16_t freq_max; - - uint16_t power_max; - uint16_t current_power; - - uint16_t temp; - - uint16_t frequency; - uint16_t power; - uint8_t pit_mode; - uint8_t control_mode; -} tramp_settings_t; - -void serial_tramp_init(); -vtx_update_result_t serial_tramp_update(); -void serial_tramp_send_payload(uint8_t cmd, const uint16_t payload); \ No newline at end of file diff --git a/src/io/msp.c b/src/io/msp.c index 5146fc614..926a1e8ed 100644 --- a/src/io/msp.c +++ b/src/io/msp.c @@ -27,7 +27,7 @@ enum { MSP_REBOOT_COUNT, }; -extern uint8_t msp_vtx_detected; +extern bool msp_vtx_detected; extern vtx_settings_t vtx_actual; extern char msp_vtx_band_letters[VTX_BAND_MAX]; extern uint8_t msp_vtx_band_is_factory[VTX_BAND_MAX]; @@ -65,6 +65,16 @@ static void msp_write_uint32_t(uint8_t *data, uint32_t val) { data[3] = val >> 24; } +static void msp_check_vtx_detected(msp_t *msp) { + if (msp_vtx_detected || msp->device != MSP_DEVICE_VTX) + return; + + if (vtx_actual.power_table.levels == 0) + return; + + msp_vtx_detected = true; +} + static void msp_process_serial_cmd(msp_t *msp, msp_magic_t magic, uint16_t cmd, uint8_t *payload, uint16_t size) { switch (cmd) { case MSP_API_VERSION: { @@ -333,6 +343,7 @@ static void msp_process_serial_cmd(msp_t *msp, msp_magic_t magic, uint16_t cmd, #endif #ifdef USE_VTX case MSP_VTX_CONFIG: { + msp_check_vtx_detected(msp); msp_vtx_send_config_reply(msp, magic); break; } @@ -404,6 +415,7 @@ static void msp_process_serial_cmd(msp_t *msp, msp_magic_t magic, uint16_t cmd, vtx_set(settings); } + msp_check_vtx_detected(msp); msp_send_reply(msp, magic, cmd, NULL, 0); break; } @@ -505,7 +517,7 @@ static void msp_process_serial_cmd(msp_t *msp, msp_magic_t magic, uint16_t cmd, case MSP_EEPROM_WRITE: { #ifdef USE_VTX if (msp->device == MSP_DEVICE_VTX) { - msp_vtx_detected = 1; + msp_check_vtx_detected(msp); } else #endif if (!flags.arm_state && msp->device != MSP_DEVICE_SPI_RX) { diff --git a/src/io/vtx.c b/src/io/vtx.c index 7df8a51c2..b79901b18 100644 --- a/src/io/vtx.c +++ b/src/io/vtx.c @@ -9,9 +9,6 @@ #include "driver/gpio.h" #include "driver/serial.h" #include "driver/time.h" -#include "driver/vtx/msp.h" -#include "driver/vtx/sa.h" -#include "driver/vtx/tramp.h" #include "flight/control.h" #include "rx/rx.h" #include "rx/unified_serial.h" @@ -33,28 +30,10 @@ static uint8_t apply_tries = 0; static uint32_t vtx_delay_start = 0; static uint32_t vtx_delay_ms = 100; -extern uint8_t smart_audio_detected; -extern smart_audio_settings_t smart_audio_settings; - -vtx_detect_status_t vtx_smart_audio_update(vtx_settings_t *actual); -void smart_audio_set_frequency(vtx_band_t band, vtx_channel_t channel); -void smart_audio_set_power_level(vtx_power_level_t power); -void smart_audio_set_pit_mode(vtx_pit_mode_t pit_mode); - -extern uint8_t tramp_detected; -extern tramp_settings_t tramp_settings; - -vtx_detect_status_t vtx_tramp_update(vtx_settings_t *actual); -void tramp_set_frequency(vtx_band_t band, vtx_channel_t channel); -void tramp_set_power_level(vtx_power_level_t power); -void tramp_set_pit_mode(vtx_pit_mode_t pit_mode); - -extern uint8_t msp_vtx_detected; - -vtx_detect_status_t vtx_msp_update(vtx_settings_t *actual); -void msp_vtx_set_frequency(vtx_band_t band, vtx_channel_t channel); -void msp_vtx_set_power_level(vtx_power_level_t power); -void msp_vtx_set_pit_mode(vtx_pit_mode_t pit_mode); +static const vtx_device_t *vtx_device = NULL; +extern const vtx_device_t msp_vtx_device; +extern const vtx_device_t smart_audio_vtx_device; +extern const vtx_device_t tramp_vtx_device; const uint16_t frequency_table[VTX_BAND_MAX][VTX_CHANNEL_MAX] = { {5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725}, // VTX_BAND_A @@ -89,25 +68,6 @@ vtx_power_level_t vtx_power_level_index(vtx_power_table_t *power_table, uint16_t return VTX_POWER_LEVEL_1; } -static vtx_detect_status_t vtx_update_protocol(vtx_protocol_t proto, vtx_settings_t *actual) { - switch (proto) { - case VTX_PROTOCOL_TRAMP: - return vtx_tramp_update(actual); - - case VTX_PROTOCOL_SMART_AUDIO: - return vtx_smart_audio_update(actual); - - case VTX_PROTOCOL_MSP_VTX: - return vtx_msp_update(actual); - - case VTX_PROTOCOL_INVALID: - case VTX_PROTOCOL_MAX: - return VTX_DETECT_ERROR; - } - - return VTX_DETECT_ERROR; -} - void vtx_init() { vtx_settings.detected = VTX_PROTOCOL_INVALID; vtx_delay_start = time_millis(); @@ -140,7 +100,7 @@ static void vtx_update_fpv_pin() { } static bool vtx_detect_protocol() { - static vtx_protocol_t protocol_to_check = VTX_PROTOCOL_MSP_VTX; + static vtx_protocol_t protocol_to_check = VTX_PROTOCOL_SMART_AUDIO; static uint8_t protocol_is_init = 0; if (vtx_settings.detected) { @@ -164,27 +124,26 @@ static bool vtx_detect_protocol() { if (!protocol_is_init) { switch (protocol_to_check) { case VTX_PROTOCOL_TRAMP: - serial_tramp_init(); + vtx_device = &tramp_vtx_device; break; case VTX_PROTOCOL_SMART_AUDIO: - serial_smart_audio_init(); + vtx_device = &smart_audio_vtx_device; break; case VTX_PROTOCOL_MSP_VTX: - serial_msp_vtx_init(); + vtx_device = &msp_vtx_device; break; - case VTX_PROTOCOL_INVALID: - case VTX_PROTOCOL_MAX: - break; + default: + vtx_device = NULL; + return false; } + vtx_device->init(); protocol_is_init = 1; - return false; } - const vtx_detect_status_t status = vtx_update_protocol(protocol_to_check, &vtx_actual); - + const vtx_detect_status_t status = vtx_device->update(&vtx_actual); if (status == VTX_DETECT_SUCCESS) { // detect success, save detected proto vtx_settings.protocol = protocol_to_check; @@ -202,7 +161,6 @@ static bool vtx_detect_protocol() { } } } - return false; } @@ -220,26 +178,9 @@ static bool vtx_update_frequency() { return true; } - switch (vtx_settings.detected) { - case VTX_PROTOCOL_TRAMP: - tramp_set_frequency(vtx_settings.band, vtx_settings.channel); - break; - - case VTX_PROTOCOL_SMART_AUDIO: - smart_audio_set_frequency(vtx_settings.band, vtx_settings.channel); - break; - - case VTX_PROTOCOL_MSP_VTX: - msp_vtx_set_frequency(vtx_settings.band, vtx_settings.channel); - break; - - case VTX_PROTOCOL_INVALID: - case VTX_PROTOCOL_MAX: - break; - } - - apply_tries++; + vtx_device->set_frequency(vtx_settings.band, vtx_settings.channel); vtx_delay_ms = 10; + apply_tries++; return false; } @@ -256,27 +197,9 @@ static bool vtx_update_powerlevel() { return true; } - switch (vtx_settings.detected) { - case VTX_PROTOCOL_TRAMP: - tramp_set_power_level(vtx_settings.power_level); - break; - - case VTX_PROTOCOL_SMART_AUDIO: - smart_audio_set_power_level(vtx_settings.power_level); - break; - - case VTX_PROTOCOL_MSP_VTX: - msp_vtx_set_power_level(vtx_settings.power_level); - break; - - case VTX_PROTOCOL_INVALID: - case VTX_PROTOCOL_MAX: - break; - } - - apply_tries++; + vtx_device->set_power_level(vtx_settings.power_level); vtx_delay_ms = 10; - + apply_tries++; return false; } @@ -293,27 +216,9 @@ static bool vtx_update_pitmode() { return true; } - switch (vtx_settings.detected) { - case VTX_PROTOCOL_TRAMP: - tramp_set_pit_mode(vtx_settings.pit_mode); - break; - - case VTX_PROTOCOL_SMART_AUDIO: - smart_audio_set_pit_mode(vtx_settings.pit_mode); - break; - - case VTX_PROTOCOL_MSP_VTX: - msp_vtx_set_pit_mode(vtx_settings.pit_mode); - break; - - case VTX_PROTOCOL_INVALID: - case VTX_PROTOCOL_MAX: - break; - } - - apply_tries++; + vtx_device->set_pit_mode(vtx_settings.pit_mode); vtx_delay_ms = 10; - + apply_tries++; return false; } @@ -348,8 +253,11 @@ void vtx_update() { if (!vtx_detect_protocol()) { return; } + if (vtx_device == NULL) { + return; + } - const vtx_detect_status_t status = vtx_update_protocol(vtx_settings.detected, &vtx_actual); + const vtx_detect_status_t status = vtx_device->update(&vtx_actual); if (status < VTX_DETECT_SUCCESS) { // we are in wait or error state, do nothing return; @@ -386,12 +294,6 @@ void vtx_set(vtx_settings_t *vtx) { vtx_settings.protocol = vtx->protocol; vtx_settings.magic = 0xFFFF; vtx_settings.power_table.levels = 0; - - smart_audio_settings.version = 0; - smart_audio_detected = 0; - tramp_settings.freq_min = 0; - tramp_detected = 0; - msp_vtx_detected = 0; } else { vtx_settings.magic = VTX_SETTINGS_MAGIC; memcpy(&vtx_settings.power_table, &vtx->power_table, sizeof(vtx_power_table_t)); diff --git a/src/io/vtx.h b/src/io/vtx.h index 84263a583..614b0772c 100644 --- a/src/io/vtx.h +++ b/src/io/vtx.h @@ -10,7 +10,6 @@ typedef enum { VTX_DETECT_WAIT, VTX_DETECT_ERROR, VTX_DETECT_SUCCESS, - VTX_DETECT_UPDATE } vtx_detect_status_t; typedef enum { @@ -103,6 +102,14 @@ typedef struct { MEMBER(power_level, uint8_t) \ MEMBER(power_table, vtx_power_table_t) +typedef struct { + void (*init)(void); + vtx_detect_status_t (*update)(vtx_settings_t *); + void (*set_frequency)(vtx_band_t, vtx_channel_t); + void (*set_power_level)(vtx_power_level_t); + void (*set_pit_mode)(vtx_pit_mode_t); +} vtx_device_t; + extern vtx_settings_t vtx_settings; void vtx_init(); diff --git a/src/io/vtx_msp.c b/src/io/vtx_msp.c index 35c41b053..50cdc871a 100644 --- a/src/io/vtx_msp.c +++ b/src/io/vtx_msp.c @@ -4,8 +4,10 @@ #include "core/profile.h" #include "driver/serial.h" -#include "driver/vtx/msp.h" +#include "driver/vtx.h" #include "io/msp.h" +#include "rx/unified_serial.h" +#include "util/crc.h" #ifdef USE_VTX @@ -32,7 +34,10 @@ typedef struct { extern uint8_t vtx_connect_tries; extern vtx_settings_t vtx_actual; -uint8_t msp_vtx_detected = 0; +extern msp_t displayport_msp; +extern msp_t crsf_msp; + +bool msp_vtx_detected = false; msp_t *msp_vtx; char msp_vtx_band_letters[VTX_BAND_MAX] = {'A', 'B', 'E', 'F', 'R', 'L'}; @@ -79,53 +84,159 @@ void msp_vtx_send_config_reply(msp_t *msp, msp_magic_t magic) { msp_send_reply(msp, magic, MSP_VTX_CONFIG, (uint8_t *)&config, sizeof(msp_vtx_config_t)); } -vtx_detect_status_t vtx_msp_update(vtx_settings_t *actual) { +static void serial_msp_send(msp_magic_t magic, uint8_t direction, uint16_t cmd, const uint8_t *data, uint16_t len) { + if (magic == MSP2_MAGIC) { + const uint32_t size = len + MSP2_HEADER_LEN + 1; + uint8_t vtx_frame[size]; + + vtx_frame[0] = '$'; + vtx_frame[1] = MSP2_MAGIC; + vtx_frame[2] = direction; + vtx_frame[3] = 0; // flag + vtx_frame[4] = (cmd >> 0) & 0xFF; + vtx_frame[5] = (cmd >> 8) & 0xFF; + vtx_frame[6] = (len >> 0) & 0xFF; + vtx_frame[7] = (len >> 8) & 0xFF; + + memcpy(vtx_frame + MSP2_HEADER_LEN, data, len); + vtx_frame[len + MSP2_HEADER_LEN] = crc8_dvb_s2_data(0, vtx_frame + 3, len + 5); + + serial_vtx_send_data(vtx_frame, size); + } else { + const uint32_t size = len + MSP_HEADER_LEN + 1; + uint8_t vtx_frame[size]; + + vtx_frame[0] = '$'; + vtx_frame[1] = MSP1_MAGIC; + vtx_frame[2] = direction; + vtx_frame[3] = len; + vtx_frame[4] = cmd; + + memcpy(vtx_frame + MSP_HEADER_LEN, data, len); + + uint8_t chksum = len; + for (uint8_t i = 4; i < (size - 1); i++) { + chksum ^= vtx_frame[i]; + } + vtx_frame[len + MSP_HEADER_LEN] = chksum; + + serial_vtx_send_data(vtx_frame, size); + } +} + +static uint8_t msp_rx_buffer[128]; +static msp_t msp = { + .buffer = msp_rx_buffer, + .buffer_size = 128, + .buffer_offset = 0, + .send = serial_msp_send, + .device = MSP_DEVICE_VTX, +}; + +static void msp_vtx_init() { + msp_vtx_detected = false; + + if (serial_displayport.config.port != SERIAL_PORT_INVALID) { + // reuse existing msp for hdz + msp_vtx = &displayport_msp; + return; + } + + if (profile.serial.smart_audio == profile.serial.rx && + serial_rx_detected_protcol == RX_SERIAL_PROTOCOL_CRSF) { + msp_vtx = &crsf_msp; + return; + } + + const target_serial_port_t *dev = serial_get_dev(profile.serial.smart_audio); + if (!target_serial_port_valid(dev)) { + return; + } + + serial_port_config_t config; + config.port = profile.serial.smart_audio; + config.baudrate = 9600; + config.direction = SERIAL_DIR_TX_RX; + config.stop_bits = SERIAL_STOP_BITS_1; + config.invert = false; + config.half_duplex = true; + config.half_duplex_pp = false; + + serial_vtx_wait_for_ready(); + serial_init(&serial_vtx, config); + + msp_vtx = &msp; +} + +static bool msp_vtx_feed_parser() { + if (serial_displayport.config.port != SERIAL_PORT_INVALID) { + // handled by digital vtx + return false; + } + if (profile.serial.smart_audio != SERIAL_PORT_INVALID && + profile.serial.smart_audio == profile.serial.rx && + serial_rx_detected_protcol == RX_SERIAL_PROTOCOL_CRSF) { + // handled by telemetry + return false; + } + if (!serial_vtx_is_ready()) { + return false; + } + + uint8_t data = 0; + while (serial_vtx_read_byte(&data)) { + msp_process_serial(msp_vtx, data); + } + + // did we time out? + return msp_vtx->buffer_offset && (time_millis() - vtx_last_valid_read) > 500; +} + +static vtx_detect_status_t msp_vtx_update(vtx_settings_t *actual) { if (vtx_connect_tries > MSP_VTX_DETECT_TRIES) { return VTX_DETECT_ERROR; } - - const vtx_update_result_t result = serial_msp_vtx_update(); - switch (result) { - case VTX_ERROR: + if (msp_vtx_feed_parser()) { vtx_connect_tries++; - case VTX_WAIT: return VTX_DETECT_WAIT; + } + if (!msp_vtx_detected) { + return VTX_DETECT_WAIT; + } - case VTX_SUCCESS: - case VTX_IDLE: - if (!msp_vtx_detected) { - return VTX_DETECT_WAIT; - } - + if (vtx_settings.detected != VTX_PROTOCOL_MSP_VTX) { if (vtx_settings.magic != VTX_SETTINGS_MAGIC) { vtx_set(actual); } - memcpy(&vtx_settings.power_table, &actual->power_table, sizeof(vtx_power_table_t)); - vtx_settings.detected = VTX_PROTOCOL_MSP_VTX; vtx_connect_tries = 0; - return VTX_DETECT_SUCCESS; } - - // wait otherwise - return VTX_DETECT_WAIT; + return VTX_DETECT_SUCCESS; } -void msp_vtx_set_frequency(vtx_band_t band, vtx_channel_t channel) { +static void msp_vtx_set_frequency(vtx_band_t band, vtx_channel_t channel) { vtx_actual.band = band; vtx_actual.channel = channel; msp_vtx_send_config_reply(msp_vtx, MSP2_MAGIC); } -void msp_vtx_set_power_level(vtx_power_level_t power) { +static void msp_vtx_set_power_level(vtx_power_level_t power) { vtx_actual.power_level = power; msp_vtx_send_config_reply(msp_vtx, MSP2_MAGIC); } -void msp_vtx_set_pit_mode(vtx_pit_mode_t pit_mode) { +static void msp_vtx_set_pit_mode(vtx_pit_mode_t pit_mode) { vtx_actual.pit_mode = pit_mode; msp_vtx_send_config_reply(msp_vtx, MSP2_MAGIC); } +const vtx_device_t msp_vtx_device = { + .init = msp_vtx_init, + .update = msp_vtx_update, + .set_frequency = msp_vtx_set_frequency, + .set_power_level = msp_vtx_set_power_level, + .set_pit_mode = msp_vtx_set_pit_mode, +}; + #endif \ No newline at end of file diff --git a/src/io/vtx_smartaudio.c b/src/io/vtx_smartaudio.c index 7701deaa4..b9e9bfe19 100644 --- a/src/io/vtx_smartaudio.c +++ b/src/io/vtx_smartaudio.c @@ -3,27 +3,74 @@ #include #include +#include "core/debug.h" +#include "core/profile.h" #include "driver/serial.h" -#include "driver/vtx/sa.h" +#include "driver/vtx.h" +#include "util/crc.h" #include "util/util.h" #ifdef USE_VTX +typedef enum { + SA_CMD_GET_SETTINGS = 0x01, + SA_CMD_SET_POWER = 0x02, + SA_CMD_SET_CHANNEL = 0x03, + SA_CMD_SET_FREQUENCY = 0x04, + SA_CMD_SET_MODE = 0x05, + SA_CMD_GET_SETTINGS_V2 = 0x9, + SA_CMD_GET_SETTINGS_V21 = 0x11, +} smart_audio_cmd_t; + +typedef enum { + SA_MODE_FREQUENCY = 1 << 0, + SA_MODE_PIT = 1 << 1, + SA_MODE_IN_RANGE_PIT = 1 << 2, + SA_MODE_OUT_RANGE_PIT = 1 << 3, + SA_MODE_UNLOCKED = 1 << 4, +} smart_mode_t; + +typedef enum { + PARSER_IDLE, + PARSER_READ_MAGIC_1, + PARSER_READ_MAGIC_2, + PARSER_READ_CMD, + PARSER_READ_LENGTH, + PARSER_READ_PAYLOAD, + PARSER_READ_CRC, +} smart_audio_parser_state_t; + #define SMART_AUDIO_DETECT_TRIES 30 -extern uint8_t vtx_connect_tries; +#define SMART_AUDIO_BAUDRATE_MIN 4650 +#define SMART_AUDIO_BAUDRATE_MAX 5050 -uint8_t smart_audio_detected = 0; -extern smart_audio_settings_t smart_audio_settings; +#define SA_HEADER_SIZE 5 +#define SA_MAGIC_1 0xaa +#define SA_MAGIC_2 0x55 -static bool smart_audio_needs_update = false; +static uint8_t smart_audio_version = 0; +static uint32_t baud_rate = 4800; +static uint32_t packets_sent = 0; +static uint32_t packets_recv = 0; + +extern vtx_settings_t vtx_actual; +extern uint8_t vtx_connect_tries; -static const char smart_audio_power_level_labels[4][VTX_POWER_LABEL_LEN] = { +static bool smart_audio_needs_update = false; +static smart_audio_parser_state_t parser_state = PARSER_IDLE; +static const char default_power_level_labels[4][VTX_POWER_LABEL_LEN] = { "25 ", "200 ", "500 ", "800 ", }; +static const uint8_t default_dac_power_levels[4] = { + 7, + 16, + 25, + 40, +}; static uint32_t smart_audio_dbi_to_mw(uint16_t dbi) { uint16_t mw = (uint16_t)powf(10.0f, dbi / 10.0f); @@ -48,58 +95,147 @@ static void smart_audio_write_mw(char *buf, uint32_t val) { *ptr++ = ' '; } -vtx_detect_status_t vtx_smart_audio_update(vtx_settings_t *actual) { - if (smart_audio_settings.version == 0 && vtx_connect_tries > SMART_AUDIO_DETECT_TRIES) { - return VTX_DETECT_ERROR; +static void smart_audio_init() { + serial_vtx_wait_for_ready(); + + const target_serial_port_t *dev = serial_get_dev(profile.serial.smart_audio); + if (!target_serial_port_valid(dev)) { + return; } - const vtx_update_result_t result = serial_smart_audio_update(); + serial_port_config_t config; + config.port = profile.serial.smart_audio; + config.baudrate = baud_rate; + config.direction = SERIAL_DIR_TX_RX; + config.stop_bits = SERIAL_STOP_BITS_2; + config.invert = false; + config.half_duplex = true; + config.half_duplex_pp = true; - if ((result == VTX_IDLE || result == VTX_ERROR) && smart_audio_settings.version == 0 && vtx_connect_tries <= SMART_AUDIO_DETECT_TRIES) { - // no smart audio detected, try again - serial_smart_audio_send_payload(SA_CMD_GET_SETTINGS, NULL, 0); - vtx_connect_tries++; - return VTX_DETECT_WAIT; + serial_init(&serial_vtx, config); +} + +static void smart_audio_auto_baud() { + static uint8_t last_percent = 0; + + // move quickly while we have not yet found a working baud rate + const uint8_t current_percent = ((packets_recv * 100) / packets_sent); + if (packets_sent < (last_percent == 0 ? 3 : 10)) { + last_percent = current_percent; + return; } - if (result == VTX_SUCCESS) { - int8_t channel_index = -1; - if (smart_audio_settings.mode & SA_MODE_FREQUENCY) { - channel_index = vtx_find_frequency_index(smart_audio_settings.frequency); - } else { - channel_index = smart_audio_settings.channel; + if (current_percent < 70) { + static int8_t direction = 1; + + // if the percentage degraded, switch it up + if (last_percent > current_percent) { + direction = direction == 1 ? -1 : 1; } - if (channel_index >= 0) { - actual->band = channel_index / VTX_CHANNEL_MAX; - actual->channel = channel_index % VTX_CHANNEL_MAX; + if ((direction == 1) && (baud_rate == SMART_AUDIO_BAUDRATE_MAX)) { + direction = -1; + } else if ((direction == -1 && baud_rate == SMART_AUDIO_BAUDRATE_MIN)) { + direction = 1; } - actual->power_table.levels = smart_audio_settings.level_count; - memcpy(actual->power_table.values, smart_audio_settings.dac_power_levels, sizeof(smart_audio_settings.dac_power_levels)); - if (smart_audio_settings.version == 3) { - for (uint32_t i = 0; i < smart_audio_settings.level_count; i++) { - smart_audio_write_mw(actual->power_table.labels[i], smart_audio_dbi_to_mw(smart_audio_settings.dac_power_levels[i])); + baud_rate += direction * 50; + quic_debugf("SMART_AUDIO: auto baud %d (%d) change %d vs %d", baud_rate, direction * 50, last_percent, current_percent); + smart_audio_init(); + } + + last_percent = current_percent; + packets_sent = 0; + packets_recv = 0; +} + +static void smart_audio_send_payload(uint8_t cmd, const uint8_t *payload, const uint32_t size) { + if (!serial_vtx_is_ready()) { + return; + } + +#ifdef USE_AKK_SA_WORKAROUND +#define EXTRA_DUMMY_BYTES 1 +#else +#define EXTRA_DUMMY_BYTES 0 +#endif + + const uint32_t len = size + 1 + SA_HEADER_SIZE + EXTRA_DUMMY_BYTES; + uint8_t vtx_frame[len]; + + vtx_frame[0] = 0x00; + vtx_frame[1] = 0xAA; + vtx_frame[2] = 0x55; + vtx_frame[3] = (cmd << 1) | 0x1; + vtx_frame[4] = size; + for (uint8_t i = 0; i < size; i++) { + vtx_frame[i + SA_HEADER_SIZE] = payload[i]; + } + vtx_frame[size + SA_HEADER_SIZE] = crc8_dvb_s2_data(0, vtx_frame + 1, len - 2 - EXTRA_DUMMY_BYTES); +#ifdef USE_AKK_SA_WORKAROUND + vtx_frame[size + 1 + SA_HEADER_SIZE] = 0x00; +#endif + + smart_audio_auto_baud(); + + serial_vtx_send_data(vtx_frame, len); + if (parser_state == PARSER_IDLE) { + parser_state = PARSER_READ_MAGIC_1; + } + packets_sent++; +} + +static void smart_audio_parse_packet(vtx_settings_t *actual, uint8_t cmd, uint8_t *payload, uint32_t length) { + switch (cmd) { + case SA_CMD_GET_SETTINGS: + case SA_CMD_GET_SETTINGS_V2: + case SA_CMD_GET_SETTINGS_V21: { + smart_audio_version = (cmd == SA_CMD_GET_SETTINGS ? 1 : (cmd == SA_CMD_GET_SETTINGS_V2 ? 2 : 3)); + + const uint8_t channel = payload[0]; + uint8_t power = payload[1]; + const uint8_t mode = payload[2]; + const uint16_t frequency = (uint16_t)(((uint16_t)payload[3] << 8) | payload[4]); + + const uint8_t channel_index = mode & SA_MODE_FREQUENCY ? vtx_find_frequency_index(frequency) : channel; + actual->band = channel_index / VTX_CHANNEL_MAX; + actual->channel = channel_index % VTX_CHANNEL_MAX; + + if (cmd == SA_CMD_GET_SETTINGS_V21) { + power = payload[5]; + + // SmartAudio seems to report buf[8] + 1 power levels, but one of them is zero. + // zero is indeed a valid power level to set the vtx to, but it activates pit mode. + // crucially, after sending 0 dbm, the vtx does NOT report its power level to be 0 dbm. + // instead, it reports whatever value was set previously and it reports to be in pit mode. + actual->power_table.levels = min(payload[6], VTX_POWER_LEVEL_MAX); + for (uint8_t i = 0; i < actual->power_table.levels; i++) { + actual->power_table.values[i] = payload[7 + i + 1]; //+ 1 to skip the first power level, as mentioned above } } else { - memcpy(actual->power_table.labels, smart_audio_power_level_labels, sizeof(smart_audio_power_level_labels)); + actual->power_table.levels = 4; + for (uint8_t i = 0; i < actual->power_table.levels; i++) { + actual->power_table.values[i] = default_dac_power_levels[i]; + } } - if (smart_audio_settings.version == 2) { - actual->power_level = min(smart_audio_settings.power, VTX_POWER_LEVEL_MAX - 1); + if (smart_audio_version >= 3) { + for (uint32_t i = 0; i < actual->power_table.levels; i++) { + smart_audio_write_mw(actual->power_table.labels[i], smart_audio_dbi_to_mw(actual->power_table.values[i])); + } + actual->pit_mode = mode & SA_MODE_PIT ? 1 : 0; } else { - actual->power_level = vtx_power_level_index(&actual->power_table, smart_audio_settings.power); + memcpy(actual->power_table.labels, default_power_level_labels, sizeof(default_power_level_labels)); + actual->pit_mode = VTX_PIT_MODE_NO_SUPPORT; } - if (smart_audio_settings.version >= 3) { - actual->pit_mode = smart_audio_settings.mode & SA_MODE_PIT ? 1 : 0; + if (smart_audio_version == 2) { + actual->power_level = min(power, VTX_POWER_LEVEL_MAX - 1); } else { - actual->pit_mode = VTX_PIT_MODE_NO_SUPPORT; + actual->power_level = vtx_power_level_index(&actual->power_table, power); } - if (smart_audio_settings.version != 0 && smart_audio_detected == 0) { - smart_audio_detected = 1; - + if (vtx_settings.detected != VTX_PROTOCOL_SMART_AUDIO) { if (vtx_settings.magic != VTX_SETTINGS_MAGIC) { vtx_set(actual); } @@ -107,26 +243,128 @@ vtx_detect_status_t vtx_smart_audio_update(vtx_settings_t *actual) { vtx_settings.detected = VTX_PROTOCOL_SMART_AUDIO; vtx_connect_tries = 0; } + break; + } + case SA_CMD_SET_FREQUENCY: { + const uint8_t channel_index = vtx_find_frequency_index((uint16_t)(((uint16_t)payload[0] << 8) | payload[1])); + actual->band = channel_index / VTX_CHANNEL_MAX; + actual->channel = channel_index % VTX_CHANNEL_MAX; + break; + } + + case SA_CMD_SET_CHANNEL: { + const uint8_t channel_index = payload[0]; + actual->band = channel_index / VTX_CHANNEL_MAX; + actual->channel = channel_index % VTX_CHANNEL_MAX; + break; + } + + case SA_CMD_SET_POWER: { + const uint8_t power = payload[0]; + if (smart_audio_version == 2) { + actual->power_level = min(power, VTX_POWER_LEVEL_MAX - 1); + } else { + actual->power_level = vtx_power_level_index(&actual->power_table, power); + } + break; + } + case SA_CMD_SET_MODE: { + // const uint8_t mode = payload[0]; + break; + } + default: + break; + } + + packets_recv++; +} + +static vtx_detect_status_t smart_audio_update(vtx_settings_t *actual) { + if (!serial_vtx_is_ready()) { + return VTX_DETECT_WAIT; + } + if (vtx_connect_tries > SMART_AUDIO_DETECT_TRIES) { + return VTX_DETECT_ERROR; + } + + static uint8_t length = 0; + + while (parser_state > PARSER_IDLE) { + if ((time_millis() - vtx_last_valid_read) > 500) { + parser_state = PARSER_IDLE; + vtx_connect_tries++; + return VTX_DETECT_WAIT; + } - if (smart_audio_needs_update) { - serial_smart_audio_send_payload(SA_CMD_GET_SETTINGS, NULL, 0); - smart_audio_needs_update = false; + uint8_t data = 0; + if (serial_vtx_read_byte(&data) == 0) { return VTX_DETECT_WAIT; } - return VTX_DETECT_SUCCESS; + switch (parser_state) { + case PARSER_IDLE: + break; + + case PARSER_READ_MAGIC_1: + if (data == SA_MAGIC_1) { + parser_state = PARSER_READ_MAGIC_2; + } + break; + + case PARSER_READ_MAGIC_2: + if (data != SA_MAGIC_2) { + parser_state = PARSER_READ_MAGIC_1; + } else { + parser_state = PARSER_READ_CMD; + } + break; + + case PARSER_READ_CMD: + vtx_payload[0] = data; + parser_state = PARSER_READ_LENGTH; + break; + + case PARSER_READ_LENGTH: + length = vtx_payload[1] = data; + vtx_payload_offset = 0; + parser_state = length ? PARSER_READ_PAYLOAD : PARSER_READ_CRC; + break; + + case PARSER_READ_PAYLOAD: + vtx_payload[vtx_payload_offset + 2] = data; + if (++vtx_payload_offset == length) { + parser_state = PARSER_READ_CRC; + } + break; + + case PARSER_READ_CRC: { + parser_state = PARSER_IDLE; + const uint8_t theirs = crc8_dvb_s2_data(0, vtx_payload, length + 2); + if (data == theirs) { + smart_audio_parse_packet(actual, vtx_payload[0], vtx_payload + 2, length - 2); + } + break; + } + } } - if ((result == VTX_IDLE || result == VTX_ERROR) && smart_audio_detected) { - // we are detected and vtx is in idle, we can update stuff - return VTX_DETECT_UPDATE; + if (vtx_settings.detected != VTX_PROTOCOL_SMART_AUDIO && vtx_connect_tries <= SMART_AUDIO_DETECT_TRIES) { + // no smart audio detected, try again + smart_audio_send_payload(SA_CMD_GET_SETTINGS, NULL, 0); + vtx_connect_tries++; + return VTX_DETECT_WAIT; } - // wait otherwise - return VTX_DETECT_WAIT; + if (smart_audio_needs_update) { + smart_audio_send_payload(SA_CMD_GET_SETTINGS, NULL, 0); + smart_audio_needs_update = false; + return VTX_DETECT_WAIT; + } + + return VTX_DETECT_SUCCESS; } -void smart_audio_set_frequency(vtx_band_t band, vtx_channel_t channel) { +static void smart_audio_set_frequency(vtx_band_t band, vtx_channel_t channel) { #ifdef SA_USE_SET_FREQUENCY if (smart_audio_settings.mode & SA_MODE_FREQUENCY) { const uint16_t frequency = frequency_table[band][channel]; @@ -134,33 +372,33 @@ void smart_audio_set_frequency(vtx_band_t band, vtx_channel_t channel) { (frequency >> 8) & 0xFF, frequency & 0xFF, }; - serial_smart_audio_send_payload(SA_CMD_SET_FREQUENCY, payload, 2); + smart_audio_send_payload(SA_CMD_SET_FREQUENCY, payload, 2); } else #endif { - smart_audio_settings.mode &= ~SA_MODE_FREQUENCY; + // smart_audio_settings.mode &= ~SA_MODE_FREQUENCY; const uint8_t index = band * VTX_CHANNEL_MAX + channel; const uint8_t payload[1] = {index}; - serial_smart_audio_send_payload(SA_CMD_SET_CHANNEL, payload, 1); + smart_audio_send_payload(SA_CMD_SET_CHANNEL, payload, 1); smart_audio_needs_update = true; } } -void smart_audio_set_power_level(vtx_power_level_t power) { +static void smart_audio_set_power_level(vtx_power_level_t power) { uint8_t level = power; - if (smart_audio_settings.version != 2) { - level = smart_audio_settings.dac_power_levels[power]; + if (smart_audio_version != 2) { + level = vtx_actual.power_table.values[power]; } - if (smart_audio_settings.version == 3) { + if (smart_audio_version == 3) { level |= 0x80; } - serial_smart_audio_send_payload(SA_CMD_SET_POWER, &level, 1); + smart_audio_send_payload(SA_CMD_SET_POWER, &level, 1); smart_audio_needs_update = true; } -void smart_audio_set_pit_mode(vtx_pit_mode_t pit_mode) { - if (smart_audio_settings.version >= 3) { +static void smart_audio_set_pit_mode(vtx_pit_mode_t pit_mode) { + if (smart_audio_version >= 3) { uint8_t mode = 0x0; if (pit_mode == VTX_PIT_MODE_OFF) { @@ -170,11 +408,19 @@ void smart_audio_set_pit_mode(vtx_pit_mode_t pit_mode) { mode |= 0x09; } - serial_smart_audio_send_payload(SA_CMD_SET_MODE, &mode, 1); + smart_audio_send_payload(SA_CMD_SET_MODE, &mode, 1); smart_audio_needs_update = true; } else { vtx_settings.pit_mode = VTX_PIT_MODE_NO_SUPPORT; } } +const vtx_device_t smart_audio_vtx_device = { + .init = smart_audio_init, + .update = smart_audio_update, + .set_frequency = smart_audio_set_frequency, + .set_power_level = smart_audio_set_power_level, + .set_pit_mode = smart_audio_set_pit_mode, +}; + #endif \ No newline at end of file diff --git a/src/io/vtx_tramp.c b/src/io/vtx_tramp.c index 640e2b531..a4cbd7071 100644 --- a/src/io/vtx_tramp.c +++ b/src/io/vtx_tramp.c @@ -2,19 +2,26 @@ #include +#include "core/profile.h" #include "driver/serial.h" -#include "driver/vtx/tramp.h" +#include "driver/vtx.h" #ifdef USE_VTX +typedef enum { + PARSER_IDLE, + PARSER_READ_MAGIC, + PARSER_READ_PAYLOAD, + PARSER_READ_CRC, + PRASER_WAIT_FOR_READY +} tramp_parser_state_t; + #define TRAMP_DETECT_TRIES 5 extern uint8_t vtx_connect_tries; extern const uint16_t frequency_table[VTX_BAND_MAX][VTX_CHANNEL_MAX]; -uint8_t tramp_detected = 0; -extern tramp_settings_t tramp_settings; - +static tramp_parser_state_t parser_state = PARSER_IDLE; static const uint16_t tramp_power_level_values[VTX_POWER_LEVEL_MAX] = { 25, 100, @@ -25,7 +32,6 @@ static const uint16_t tramp_power_level_values[VTX_POWER_LEVEL_MAX] = { 0, 0, }; - static const char tramp_power_level_labels[VTX_POWER_LEVEL_MAX][VTX_POWER_LABEL_LEN] = { "25 ", "100 ", @@ -37,32 +43,60 @@ static const char tramp_power_level_labels[VTX_POWER_LEVEL_MAX][VTX_POWER_LABEL_ " ", }; -vtx_detect_status_t vtx_tramp_update(vtx_settings_t *actual) { - if (tramp_settings.freq_min == 0 && vtx_connect_tries > TRAMP_DETECT_TRIES) { - return VTX_DETECT_ERROR; +static uint8_t crc8_data(const uint8_t *data) { + uint8_t crc = 0; + for (int i = 0; i < 13; i++) { + crc += data[i]; } + return crc; +} - vtx_update_result_t result = serial_tramp_update(); +static void tramp_init() { + serial_vtx_wait_for_ready(); - if ((result == VTX_IDLE || result == VTX_ERROR) && tramp_settings.freq_min == 0 && vtx_connect_tries <= TRAMP_DETECT_TRIES) { - // no tramp detected, try again - serial_tramp_send_payload('r', 0); - vtx_connect_tries++; - return VTX_DETECT_WAIT; + const target_serial_port_t *dev = serial_get_dev(profile.serial.smart_audio); + if (!target_serial_port_valid(dev)) { + return; } - if (result == VTX_SUCCESS) { - if (tramp_settings.freq_min == 0) { - // tramp reset was successful but returned empty data - return VTX_DETECT_WAIT; - } + serial_port_config_t config; + config.port = profile.serial.smart_audio; + config.baudrate = 9600; + config.direction = SERIAL_DIR_TX_RX; + config.stop_bits = SERIAL_STOP_BITS_1; + config.invert = false; + config.half_duplex = true; + config.half_duplex_pp = false; - if (tramp_settings.frequency == 0) { - serial_tramp_send_payload('v', 0); - return VTX_DETECT_WAIT; - } + serial_init(&serial_vtx, config); +} - int8_t channel_index = vtx_find_frequency_index(tramp_settings.frequency); +static bool tramp_is_query(uint8_t cmd) { + switch (cmd) { + case 'r': + case 'v': + case 's': + return true; + } + return false; +} + +static void tramp_parse_packet(vtx_settings_t *actual, uint8_t *payload) { + switch (payload[0]) { + case 'r': + // freq_min = payload[1] | (payload[2] << 8); + // freq_max = payload[3] | (payload[4] << 8); + // power_max = payload[5] | (payload[6] << 8); + break; + + case 'v': { + const uint16_t frequency = payload[1] | (payload[2] << 8); + const uint16_t power = payload[3] | (payload[4] << 8); + // const uint8_t control_mode = payload[5]; + const uint8_t pit_mode = payload[6]; + // const uint16_t current_power = payload[7] | (payload[8] << 8); + + int8_t channel_index = vtx_find_frequency_index(frequency); if (channel_index >= 0) { actual->band = channel_index / VTX_CHANNEL_MAX; actual->channel = channel_index % VTX_CHANNEL_MAX; @@ -72,19 +106,10 @@ vtx_detect_status_t vtx_tramp_update(vtx_settings_t *actual) { memcpy(actual->power_table.values, tramp_power_level_values, sizeof(tramp_power_level_values)); memcpy(actual->power_table.labels, tramp_power_level_labels, sizeof(tramp_power_level_labels)); - actual->power_level = vtx_power_level_index(&actual->power_table, tramp_settings.power); - actual->pit_mode = tramp_settings.pit_mode; - - // not all vtxes seem to return a non-zero value. :( - // as its unused lets just drop it. - // if (tramp_settings.temp == 0) { - // serial_tramp_send_payload('s', 0); - // return VTX_DETECT_WAIT; - // } - - if (tramp_settings.freq_min != 0 && tramp_settings.frequency != 0 && tramp_detected == 0) { - tramp_detected = 1; + actual->power_level = vtx_power_level_index(&actual->power_table, power); + actual->pit_mode = pit_mode; + if (vtx_settings.detected != VTX_PROTOCOL_TRAMP) { if (vtx_settings.magic != VTX_SETTINGS_MAGIC) { vtx_set(actual); } @@ -92,32 +117,124 @@ vtx_detect_status_t vtx_tramp_update(vtx_settings_t *actual) { vtx_settings.detected = VTX_PROTOCOL_TRAMP; vtx_connect_tries = 0; } - return VTX_DETECT_SUCCESS; + break; + } + case 's': + // temp = payload[5] | (payload[6] << 8); + break; } +} - if ((result == VTX_IDLE || result == VTX_ERROR) && tramp_detected) { - // we are detected and vtx is in idle, we can update stuff - return VTX_DETECT_UPDATE; +static void tramp_send_payload(uint8_t cmd, const uint16_t payload) { + if (!serial_vtx_is_ready()) { + return; } - // wait otherwise - return VTX_DETECT_WAIT; + uint8_t vtx_frame[16]; + memset(vtx_frame, 0, 16); + + vtx_frame[0] = 0x0F; + vtx_frame[1] = cmd; + vtx_frame[2] = payload & 0xff; + vtx_frame[3] = (payload >> 8) & 0xff; + vtx_frame[14] = crc8_data(vtx_frame + 1); + + serial_vtx_send_data(vtx_frame, 16); + + if (tramp_is_query(vtx_frame[1])) { + parser_state = PARSER_READ_MAGIC; + } else { + parser_state = PRASER_WAIT_FOR_READY; + } } -void tramp_set_frequency(vtx_band_t band, vtx_channel_t channel) { +static vtx_detect_status_t tramp_update(vtx_settings_t *actual) { + if (!serial_vtx_is_ready()) { + return VTX_DETECT_WAIT; + } + if (vtx_connect_tries > TRAMP_DETECT_TRIES) { + return VTX_DETECT_ERROR; + } + + while (parser_state > PARSER_IDLE) { + if ((time_millis() - vtx_last_valid_read) > 500) { + parser_state = PARSER_IDLE; + vtx_connect_tries++; + return VTX_DETECT_WAIT; + } + + switch (parser_state) { + case PARSER_IDLE: + break; + + case PARSER_READ_MAGIC: { + if (serial_vtx_read_byte(&vtx_payload[0]) == 0) { + return VTX_DETECT_WAIT; + } + + if (vtx_payload[0] == 0x0F) { + parser_state = PARSER_READ_PAYLOAD; + vtx_payload_offset = 1; + } + break; + } + case PARSER_READ_PAYLOAD: { + if (serial_vtx_read_byte(&vtx_payload[vtx_payload_offset]) == 0) { + return VTX_DETECT_WAIT; + } + + vtx_payload_offset++; + if (vtx_payload_offset >= 16) { + parser_state = PARSER_READ_CRC; + } + break; + } + case PARSER_READ_CRC: { + parser_state = PARSER_IDLE; + + const uint8_t crc = crc8_data(vtx_payload + 1); + if (vtx_payload[14] == crc && vtx_payload[15] == 0) { + tramp_parse_packet(actual, vtx_payload + 1); + } + break; + } + case PRASER_WAIT_FOR_READY: { + // dummy state for non querry packets + parser_state = PARSER_IDLE; + break; + } + } + } + + if (vtx_settings.detected != VTX_PROTOCOL_TRAMP && vtx_connect_tries <= TRAMP_DETECT_TRIES) { + // no tramp detected, try again + tramp_send_payload('v', 0); + vtx_connect_tries++; + return VTX_DETECT_WAIT; + } + + return VTX_DETECT_SUCCESS; +} + +static void tramp_set_frequency(vtx_band_t band, vtx_channel_t channel) { const uint16_t frequency = frequency_table[band][channel]; - serial_tramp_send_payload('F', frequency); - tramp_settings.frequency = 0; + tramp_send_payload('F', frequency); } -void tramp_set_power_level(vtx_power_level_t power) { - serial_tramp_send_payload('P', tramp_power_level_values[power]); - tramp_settings.frequency = 0; +static void tramp_set_power_level(vtx_power_level_t power) { + tramp_send_payload('P', tramp_power_level_values[power]); } -void tramp_set_pit_mode(vtx_pit_mode_t pit_mode) { - serial_tramp_send_payload('I', pit_mode == VTX_PIT_MODE_ON ? 0 : 1); - tramp_settings.frequency = 0; +static void tramp_set_pit_mode(vtx_pit_mode_t pit_mode) { + tramp_send_payload('I', pit_mode == VTX_PIT_MODE_ON ? 0 : 1); } +const vtx_device_t tramp_vtx_device = { + .init = tramp_init, + .update = tramp_update, + .set_frequency = tramp_set_frequency, + .set_power_level = tramp_set_power_level, + .set_pit_mode = tramp_set_pit_mode, +}; + #endif \ No newline at end of file