2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 /* Created by jflyper */
31 #if defined(USE_VTX_SMARTAUDIO) && defined(USE_VTX_CONTROL)
33 #include "build/debug.h"
37 #include "common/log.h"
38 #include "common/maths.h"
39 #include "common/printf.h"
40 #include "common/utils.h"
41 #include "common/typeconversion.h"
43 #include "drivers/time.h"
44 #include "drivers/vtx_common.h"
46 #include "fc/settings.h"
48 #include "io/serial.h"
50 #include "io/vtx_control.h"
51 #include "io/vtx_smartaudio.h"
52 #include "io/vtx_string.h"
56 // Note that vtxSAProcess() is normally called at 200ms interval
57 #define SMARTAUDIO_CMD_TIMEOUT 120 // Time until the command is considered lost
58 #define SMARTAUDIO_POLLING_INTERVAL 150 // Minimum time between state polling
59 #define SMARTAUDIO_POLLING_WINDOW 1000 // Time window after command polling for state change
61 static serialPort_t
*smartAudioSerialPort
= NULL
;
63 uint8_t saPowerCount
= VTX_SMARTAUDIO_DEFAULT_POWER_COUNT
;
64 const char * saPowerNames
[VTX_SMARTAUDIO_MAX_POWER_COUNT
+ 1] = {
65 "----", "25 ", "200 ", "500 ", "800 ", " "
68 // Save powerlevels reported from SA 2.1 devices here
69 char sa21PowerNames
[VTX_SMARTAUDIO_MAX_POWER_COUNT
][5];
71 static const vtxVTable_t saVTable
; // Forward
72 static vtxDevice_t vtxSmartAudio
= {
74 .capability
.bandCount
= VTX_SMARTAUDIO_BAND_COUNT
,
75 .capability
.channelCount
= VTX_SMARTAUDIO_CHANNEL_COUNT
,
76 .capability
.powerCount
= VTX_SMARTAUDIO_MAX_POWER_COUNT
, // Should this be VTX_SMARTAUDIO_DEFAULT_POWER_COUNT?
77 .capability
.bandNames
= (char **)vtx58BandNames
,
78 .capability
.channelNames
= (char **)vtx58ChannelNames
,
79 .capability
.powerNames
= (char**)saPowerNames
82 // SmartAudio command and response codes
85 SA_CMD_GET_SETTINGS
= 0x01,
90 SA_CMD_GET_SETTINGS_V2
= 0x09, // Response only
91 SA_CMD_GET_SETTINGS_V21
= 0x11,
92 } smartAudioCommand_e
;
94 // This is not a good design; can't distinguish command from response this way.
95 #define SACMD(cmd) (((cmd) << 1) | 1)
98 #define SA_IS_PITMODE(n) ((n) & SA_MODE_GET_PITMODE)
99 #define SA_IS_PIRMODE(n) (((n) & SA_MODE_GET_PITMODE) && ((n) & SA_MODE_GET_IN_RANGE_PITMODE))
100 #define SA_IS_PORMODE(n) (((n) & SA_MODE_GET_PITMODE) && ((n) & SA_MODE_GET_OUT_RANGE_PITMODE))
103 // convert between 'saDevice.channel' and band/channel values
104 #define SA_DEVICE_CHVAL_TO_BAND(val) ((val) / (uint8_t)8)
105 #define SA_DEVICE_CHVAL_TO_CHANNEL(val) ((val) % (uint8_t)8)
106 #define SA_BANDCHAN_TO_DEVICE_CHVAL(band, channel) ((band) * (uint8_t)8 + (channel))
109 // Statistical counters, for user side trouble shooting.
111 smartAudioStat_t saStat
= {
121 // Fill table with standard values for SA 1.0 and 2.0
122 saPowerTable_t saPowerTable
[VTX_SMARTAUDIO_MAX_POWER_COUNT
] = {
127 { 0, 0 }, // Placeholders
133 // Last received device ('hard') states
135 smartAudioDevice_t saDevice
= {
136 .version
= SA_UNKNOWN
,
137 .channel
= SETTING_VTX_CHANNEL_DEFAULT
,
138 .power
= SETTING_VTX_POWER_DEFAULT
,
142 .willBootIntoPitMode
= false
145 static smartAudioDevice_t saDevicePrev
= {
149 // XXX Possible compliance problem here. Need LOCK/UNLOCK menu?
150 static uint8_t saLockMode
= SA_MODE_SET_UNLOCK
; // saCms variable?
152 // XXX Should be configurable by user?
153 bool saDeferred
= true; // saCms variable?
155 // Receive frame reassembly buffer
156 #define SA_MAX_RCVLEN 21
157 static uint8_t sa_rbuf
[SA_MAX_RCVLEN
+4]; // XXX delete 4 byte guard
165 static uint8_t CRC8(const uint8_t *data
, const int8_t len
)
167 uint8_t crc
= 0; /* start with 0 so first byte can be 'xored' in */
170 for (int i
= 0 ; i
< len
; i
++) {
173 crc
^= currByte
; /* XOR-in the next input byte */
175 for (int i
= 0; i
< 8; i
++) {
176 if ((crc
& 0x80) != 0) {
177 crc
= (uint8_t)((crc
<< 1) ^ POLYGEN
);
187 static void saPrintSettings(void)
189 LOG_DEBUG(VTX
, "Current status: version: %d", saDevice
.version
);
190 LOG_DEBUG(VTX
, " mode(0x%x): fmode=%s", saDevice
.mode
, (saDevice
.mode
& 1) ? "freq" : "chan");
191 LOG_DEBUG(VTX
, " pit=%s ", (saDevice
.mode
& 2) ? "on " : "off");
192 LOG_DEBUG(VTX
, " inb=%s", (saDevice
.mode
& 4) ? "on " : "off");
193 LOG_DEBUG(VTX
, " outb=%s", (saDevice
.mode
& 8) ? "on " : "off");
194 LOG_DEBUG(VTX
, " lock=%s", (saDevice
.mode
& 16) ? "unlocked" : "locked");
195 LOG_DEBUG(VTX
, " deferred=%s", (saDevice
.mode
& 32) ? "on" : "off");
196 LOG_DEBUG(VTX
, " channel: %d ", saDevice
.channel
);
197 LOG_DEBUG(VTX
, "freq: %d ", saDevice
.freq
);
198 LOG_DEBUG(VTX
, "power: %d ", saDevice
.power
);
199 LOG_DEBUG(VTX
, "pitfreq: %d ", saDevice
.orfreq
);
200 LOG_DEBUG(VTX
, "BootIntoPitMode: %s", saDevice
.willBootIntoPitMode
? "yes" : "no");
203 int saDacToPowerIndex(int dac
)
205 for (int idx
= saPowerCount
- 1 ; idx
>= 0 ; idx
--) {
206 if (saPowerTable
[idx
].dbi
<= dac
) {
213 int saDbiToMw(uint16_t dbi
) {
215 uint16_t mw
= (uint16_t)pow(10.0, dbi
/ 10.0);
218 // For powers greater than 25mW round up to a multiple of 50 to match expectations
219 mw
= 50 * ((mw
+ 25) / 50);
229 #define SMARTBAUD_MIN 4800
230 #define SMARTBAUD_MAX 4950
231 uint16_t sa_smartbaud
= SMARTBAUD_MIN
;
232 static int sa_adjdir
= 1; // -1=going down, 1=going up
233 static int sa_baudstep
= 50;
235 static void saAutobaud(void)
237 if (saStat
.pktsent
< 10) {
238 // Not enough samples collected
242 if (((saStat
.pktrcvd
* 100) / saStat
.pktsent
) >= 70) {
244 saStat
.pktsent
= 0; // Should be more moderate?
249 LOG_DEBUG(VTX
, "autobaud: adjusting");
251 if ((sa_adjdir
== 1) && (sa_smartbaud
== SMARTBAUD_MAX
)) {
253 LOG_DEBUG(VTX
, "autobaud: now going down");
254 } else if ((sa_adjdir
== -1 && sa_smartbaud
== SMARTBAUD_MIN
)) {
256 LOG_DEBUG(VTX
, "autobaud: now going up");
259 sa_smartbaud
+= sa_baudstep
* sa_adjdir
;
261 LOG_DEBUG(VTX
, "autobaud: %d", sa_smartbaud
);
263 smartAudioSerialPort
->vTable
->serialSetBaudRate(smartAudioSerialPort
, sa_smartbaud
);
269 // Transport level variables
271 static timeUs_t sa_lastTransmissionMs
= 0;
272 static uint8_t sa_outstanding
= SA_CMD_NONE
; // Outstanding command
273 static uint8_t sa_osbuf
[32]; // Outstanding comamnd frame for retransmission
274 static int sa_oslen
; // And associate length
276 static void saProcessResponse(uint8_t *buf
, int len
)
278 uint8_t resp
= buf
[0];
280 if (resp
== sa_outstanding
) {
281 sa_outstanding
= SA_CMD_NONE
;
282 } else if ((resp
== SA_CMD_GET_SETTINGS_V2
|| resp
== SA_CMD_GET_SETTINGS_V21
) && (sa_outstanding
== SA_CMD_GET_SETTINGS
)) {
283 sa_outstanding
= SA_CMD_NONE
;
286 LOG_DEBUG(VTX
, "processResponse: outstanding %d got %d", sa_outstanding
, resp
);
290 case SA_CMD_GET_SETTINGS_V21
: // Version 2.1 Get Settings
291 case SA_CMD_GET_SETTINGS_V2
: // Version 2 Get Settings
292 case SA_CMD_GET_SETTINGS
: // Version 1 Get Settings
297 // From spec: "Bit 7-3 is holding the Smart audio version where 0 is V1, 1 is V2, 2 is V2.1"
298 // saDevice.version = 0 means unknown, 1 means Smart audio V1, 2 means Smart audio V2 and 3 means Smart audio V2.1
299 saDevice
.version
= (buf
[0] == SA_CMD_GET_SETTINGS
) ? 1 : ((buf
[0] == SA_CMD_GET_SETTINGS_V2
) ? 2 : 3);
300 saDevice
.channel
= buf
[2];
301 uint8_t rawPowerValue
= buf
[3];
302 saDevice
.mode
= buf
[4];
303 saDevice
.freq
= (buf
[5] << 8) | buf
[6];
305 // read pir and por flags to detect if the device will boot into pitmode.
306 // note that "quit pitmode without unsetting the pitmode flag" clears pir and por flags but the device will still boot into pitmode.
307 // therefore we ignore the pir and por flags while the device is not in pitmode
308 // actually, this is the whole reason the variable saDevice.willBootIntoPitMode exists.
309 // otherwise we could use saDevice.mode directly
310 if (saDevice
.mode
& SA_MODE_GET_PITMODE
) {
311 bool newBootMode
= (saDevice
.mode
& SA_MODE_GET_IN_RANGE_PITMODE
) || (saDevice
.mode
& SA_MODE_GET_OUT_RANGE_PITMODE
);
312 if (newBootMode
!= saDevice
.willBootIntoPitMode
) {
313 LOG_DEBUG(VTX
, "saProcessResponse: willBootIntoPitMode is now %s\r\n", newBootMode
? "true" : "false");
315 saDevice
.willBootIntoPitMode
= newBootMode
;
318 if(saDevice
.version
== SA_2_1
) {
319 //read dbm based power levels
320 if(len
< 10) { //current power level in dbm field missing or power level length field missing or zero power levels reported
321 LOG_DEBUG(VTX
, "processResponse: V2.1 vtx didn't report any power levels\r\n");
324 saPowerCount
= constrain((int8_t)buf
[8], 0, VTX_SMARTAUDIO_MAX_POWER_COUNT
);
325 vtxSmartAudio
.capability
.powerCount
= saPowerCount
;
326 //SmartAudio seems to report buf[8] + 1 power levels, but one of them is zero.
327 //zero is indeed a valid power level to set the vtx to, but it activates pit mode.
328 //crucially, after sending 0 dbm, the vtx does NOT report its power level to be 0 dbm.
329 //instead, it reports whatever value was set previously and it reports to be in pit mode.
330 //for this reason, zero shouldn't be used as a normal power level in INAV.
331 for (int8_t i
= 0; i
< saPowerCount
; i
++ ) {
332 saPowerTable
[i
].dbi
= buf
[9 + i
+ 1]; //+ 1 to skip the first power level, as mentioned above
333 saPowerTable
[i
].mW
= saDbiToMw(saPowerTable
[i
].dbi
);
334 if (i
<= VTX_SMARTAUDIO_MAX_POWER_COUNT
) {
336 itoa(saPowerTable
[i
].mW
, strbuf
, 10);
337 strcpy(sa21PowerNames
[i
], strbuf
);
338 saPowerNames
[i
+ 1] = sa21PowerNames
[i
];
342 LOG_DEBUG(VTX
, "processResponse: %d power values: %d, %d, %d, %d\r\n",
343 saPowerCount
, saPowerTable
[0].dbi
, saPowerTable
[1].dbi
,
344 saPowerTable
[2].dbi
, saPowerTable
[3].dbi
);
345 //LOG_DEBUG(VTX, "processResponse: V2.1 received vtx power value %d\r\n",buf[7]);
346 rawPowerValue
= buf
[7];
348 saDevice
.power
= 0; //set to unknown power level if the reported one doesnt match any of the known ones
349 LOG_DEBUG(VTX
, "processResponse: rawPowerValue is %d, legacy power is %d\r\n", rawPowerValue
, buf
[3]);
350 for (int8_t i
= 0; i
< saPowerCount
; i
++) {
351 if (rawPowerValue
== saPowerTable
[i
].dbi
) {
352 saDevice
.power
= i
+ 1;
356 saDevice
.power
= rawPowerValue
+ 1;
359 DEBUG_SET(DEBUG_SMARTAUDIO
, 0, saDevice
.version
* 100 + saDevice
.mode
);
360 DEBUG_SET(DEBUG_SMARTAUDIO
, 1, saDevice
.channel
);
361 DEBUG_SET(DEBUG_SMARTAUDIO
, 2, saDevice
.freq
);
362 DEBUG_SET(DEBUG_SMARTAUDIO
, 3, saDevice
.power
);
365 case SA_CMD_SET_POWER
: // Set Power
368 case SA_CMD_SET_CHAN
: // Set Channel
371 case SA_CMD_SET_FREQ
: // Set Frequency
376 const uint16_t freq
= (buf
[2] << 8)|buf
[3];
378 if (freq
& SA_FREQ_GETPIT
) {
379 saDevice
.orfreq
= freq
& SA_FREQ_MASK
;
380 LOG_DEBUG(VTX
, "saProcessResponse: GETPIT freq %d", saDevice
.orfreq
);
381 } else if (freq
& SA_FREQ_SETPIT
) {
382 saDevice
.orfreq
= freq
& SA_FREQ_MASK
;
383 LOG_DEBUG(VTX
, "saProcessResponse: SETPIT freq %d", saDevice
.orfreq
);
385 saDevice
.freq
= freq
;
386 LOG_DEBUG(VTX
, "saProcessResponse: SETFREQ freq %d", freq
);
390 case SA_CMD_SET_MODE
: // Set Mode
391 LOG_DEBUG(VTX
, "saProcessResponse: SET_MODE 0x%x, (pir %s, por %s, pitdsbl %s, %s)\r\n",
392 buf
[2], (buf
[2] & 1) ? "on" : "off", (buf
[2] & 2) ? "on" : "off", (buf
[3] & 4) ? "on" : "off",
393 (buf
[4] & 8) ? "unlocked" : "locked");
401 if (memcmp(&saDevice
, &saDevicePrev
, sizeof(smartAudioDevice_t
))) {
406 saDevicePrev
= saDevice
;
413 static void saReceiveFramer(uint8_t c
)
416 static enum saFramerState_e
{
417 S_WAITPRE1
, // Waiting for preamble 1 (0xAA)
418 S_WAITPRE2
, // Waiting for preamble 2 (0x55)
419 S_WAITRESP
, // Waiting for response code
420 S_WAITLEN
, // Waiting for length
421 S_DATA
, // Receiving data
422 S_WAITCRC
, // Waiting for CRC
423 } state
= S_WAITPRE1
;
433 state
= S_WAITPRE1
; // Don't need this (no change)
455 if (len
> SA_MAX_RCVLEN
- 2) {
458 } else if (len
== 0) {
467 // XXX Should check buffer overflow (-> saerr_overflow)
468 sa_rbuf
[2 + dlen
] = c
;
475 if (CRC8(sa_rbuf
, 2 + len
) == c
) {
477 saProcessResponse(sa_rbuf
, len
+ 2);
479 } else if (sa_rbuf
[0] & 1) {
481 // XXX There is an exceptional case (V2 response)
482 // XXX Should check crc in the command format?
491 static void saSendFrame(uint8_t *buf
, int len
)
493 if ( (vtxConfig()->smartAudioAltSoftSerialMethod
&&
494 (smartAudioSerialPort
->identifier
== SERIAL_PORT_SOFTSERIAL1
|| smartAudioSerialPort
->identifier
== SERIAL_PORT_SOFTSERIAL2
))
496 // TBS SA definition requires that the line is low before frame is sent
497 // (for both soft and hard serial). It can be done by sending first 0x00
498 serialWrite(smartAudioSerialPort
, 0x00);
501 for (int i
= 0 ; i
< len
; i
++) {
502 serialWrite(smartAudioSerialPort
, buf
[i
]);
505 // XXX: Workaround for early AKK SAudio-enabled VTX bug,
506 // shouldn't cause any problems with VTX with properly
507 // implemented SAudio.
508 //Update: causes problem with new AKK AIO camera connected to SoftUART
509 if (vtxConfig()->smartAudioEarlyAkkWorkaroundEnable
) serialWrite(smartAudioSerialPort
, 0x00);
511 sa_lastTransmissionMs
= millis();
516 * Retransmission and command queuing
518 * The transport level support includes retransmission on response timeout
519 * and command queueing.
522 * The smartaudio returns response for valid command frames in no less
523 * than 60msec, which we can't wait. So there's a need for a resend buffer.
526 * The driver autonomously sends GetSettings command for auto-bauding,
527 * asynchronous to user initiated commands; commands issued while another
528 * command is outstanding must be queued for later processing.
529 * The queueing also handles the case in which multiple commands are
530 * required to implement a user level command.
535 static void saResendCmd(void)
537 saSendFrame(sa_osbuf
, sa_oslen
);
540 static void saSendCmd(uint8_t *buf
, int len
)
542 for (int i
= 0 ; i
< len
; i
++) {
543 sa_osbuf
[i
] = buf
[i
];
547 sa_outstanding
= (buf
[2] >> 1);
549 saSendFrame(sa_osbuf
, sa_oslen
);
552 // Command queue management
554 typedef struct saCmdQueue_s
{
559 #define SA_QSIZE 6 // 1 heartbeat (GetSettings) + 2 commands + 1 slack
560 static saCmdQueue_t sa_queue
[SA_QSIZE
];
561 static uint8_t sa_qhead
= 0;
562 static uint8_t sa_qtail
= 0;
564 static bool saQueueEmpty(void)
566 return sa_qhead
== sa_qtail
;
569 static bool saQueueFull(void)
571 return ((sa_qhead
+ 1) % SA_QSIZE
) == sa_qtail
;
574 static void saQueueCmd(uint8_t *buf
, int len
)
580 sa_queue
[sa_qhead
].buf
= buf
;
581 sa_queue
[sa_qhead
].len
= len
;
582 sa_qhead
= (sa_qhead
+ 1) % SA_QSIZE
;
585 static void saSendQueue(void)
587 if (saQueueEmpty()) {
591 saSendCmd(sa_queue
[sa_qtail
].buf
, sa_queue
[sa_qtail
].len
);
592 sa_qtail
= (sa_qtail
+ 1) % SA_QSIZE
;
595 // Individual commands
597 static void saGetSettings(void)
599 static uint8_t bufGetSettings
[5] = {0xAA, 0x55, SACMD(SA_CMD_GET_SETTINGS
), 0x00, 0x9F};
601 LOG_DEBUG(VTX
, "smartAudioGetSettings\r\n");
602 saQueueCmd(bufGetSettings
, 5);
605 void saSetFreq(uint16_t freq
)
607 static uint8_t buf
[7] = { 0xAA, 0x55, SACMD(SA_CMD_SET_FREQ
), 2 };
608 static uint8_t switchBuf
[7];
610 if (freq
& SA_FREQ_GETPIT
) {
611 LOG_DEBUG(VTX
, "smartAudioSetFreq: GETPIT");
612 } else if (freq
& SA_FREQ_SETPIT
) {
613 LOG_DEBUG(VTX
, "smartAudioSetFreq: SETPIT %d", freq
& SA_FREQ_MASK
);
615 LOG_DEBUG(VTX
, "smartAudioSetFreq: SET %d", freq
);
618 buf
[4] = (freq
>> 8) & 0xff;
619 buf
[5] = freq
& 0xff;
620 buf
[6] = CRC8(buf
, 6);
622 // Need to work around apparent SmartAudio bug when going from 'channel'
623 // to 'user-freq' mode, where the set-freq command will fail if the freq
624 // value is unchanged from the previous 'user-freq' mode
625 if ((saDevice
.mode
& SA_MODE_GET_FREQ_BY_FREQ
) == 0 && freq
== saDevice
.freq
) {
626 memcpy(&switchBuf
, &buf
, sizeof(buf
));
627 const uint16_t switchFreq
= freq
+ ((freq
== VTX_SMARTAUDIO_MAX_FREQUENCY_MHZ
) ? -1 : 1);
628 switchBuf
[4] = (switchFreq
>> 8);
629 switchBuf
[5] = switchFreq
& 0xff;
630 switchBuf
[6] = CRC8(switchBuf
, 6);
632 saQueueCmd(switchBuf
, 7);
638 void saSetPitFreq(uint16_t freq
)
640 saSetFreq(freq
| SA_FREQ_SETPIT
);
643 static bool saValidateBandAndChannel(uint8_t band
, uint8_t channel
)
645 return (band
>= VTX_SMARTAUDIO_MIN_BAND
&& band
<= VTX_SMARTAUDIO_MAX_BAND
&&
646 channel
>= VTX_SMARTAUDIO_MIN_CHANNEL
&& channel
<= VTX_SMARTAUDIO_MAX_CHANNEL
);
649 void saSetBandAndChannel(uint8_t band
, uint8_t channel
)
651 static uint8_t buf
[6] = { 0xAA, 0x55, SACMD(SA_CMD_SET_CHAN
), 1 };
653 buf
[4] = SA_BANDCHAN_TO_DEVICE_CHVAL(band
, channel
);
654 buf
[5] = CRC8(buf
, 5);
655 LOG_DEBUG(VTX
, "vtxSASetBandAndChannel set index band %d channel %d value sent 0x%x\r\n", band
, channel
, buf
[4]);
657 //this will clear saDevice.mode & SA_MODE_GET_FREQ_BY_FREQ
661 void saSetMode(int mode
)
663 static uint8_t buf
[6] = { 0xAA, 0x55, SACMD(SA_CMD_SET_MODE
), 1 };
665 buf
[4] = (mode
& 0x3f) | saLockMode
;
666 if (saDevice
.version
>= SA_2_1
&& (mode
& SA_MODE_CLR_PITMODE
) &&
667 ((mode
& SA_MODE_SET_IN_RANGE_PITMODE
) || (mode
& SA_MODE_SET_OUT_RANGE_PITMODE
))) {
668 saDevice
.willBootIntoPitMode
= true;//quit pitmode without unsetting flag.
669 //the response will just say pit=off but the device will still go into pitmode on reboot.
670 //therefore we have to memorize this change here.
672 LOG_DEBUG(VTX
, "saSetMode(0x%x): pir=%s por=%s pitdsbl=%s %s\r\n", mode
, (mode
& 1) ? "on " : "off", (mode
& 2) ? "on " : "off",
673 (mode
& 4)? "on " : "off", (mode
& 8) ? "locked" : "unlocked");
675 buf
[5] = CRC8(buf
, 5);
679 bool vtxSmartAudioInit(void)
681 serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_VTX_SMARTAUDIO
);
683 portOptions_t portOptions
= (vtxConfig()->smartAudioStopBits
== 2 ? SERIAL_STOPBITS_2
: SERIAL_STOPBITS_1
) | SERIAL_BIDIR_NOPULL
;
684 portOptions
= portOptions
| (vtxConfig()->halfDuplex
? SERIAL_BIDIR
| SERIAL_BIDIR_PP
: SERIAL_UNIDIR
);
685 smartAudioSerialPort
= openSerialPort(portConfig
->identifier
, FUNCTION_VTX_SMARTAUDIO
, NULL
, NULL
, 4800, MODE_RXTX
, portOptions
);
688 if (!smartAudioSerialPort
) {
692 vtxCommonSetDevice(&vtxSmartAudio
);
697 #define SA_INITPHASE_START 0
698 #define SA_INITPHASE_WAIT_SETTINGS 1 // SA_CMD_GET_SETTINGS was sent and waiting for reply.
699 #define SA_INITPHASE_WAIT_PITFREQ 2 // SA_FREQ_GETPIT sent and waiting for reply.
700 #define SA_INITPHASE_DONE 3
702 static void vtxSAProcess(vtxDevice_t
*vtxDevice
, timeUs_t currentTimeUs
)
705 UNUSED(currentTimeUs
);
707 static char initPhase
= SA_INITPHASE_START
;
709 if (smartAudioSerialPort
== NULL
) {
713 while (serialRxBytesWaiting(smartAudioSerialPort
) > 0) {
714 uint8_t c
= serialRead(smartAudioSerialPort
);
715 saReceiveFramer((uint16_t)c
);
718 // Re-evaluate baudrate after each frame reception
722 case SA_INITPHASE_START
:
725 initPhase
= SA_INITPHASE_WAIT_SETTINGS
;
728 case SA_INITPHASE_WAIT_SETTINGS
:
729 // Don't send SA_FREQ_GETPIT to V1 device; it act as plain SA_CMD_SET_FREQ,
730 // and put the device into user frequency mode with uninitialized freq.
731 if (saDevice
.version
) {
732 if (saDevice
.version
== SA_2_0
) {
733 saSetFreq(SA_FREQ_GETPIT
);
734 initPhase
= SA_INITPHASE_WAIT_PITFREQ
;
736 initPhase
= SA_INITPHASE_DONE
;
738 if (saDevice
.version
>= SA_2_0
) {
739 //did the device boot up in pit mode on its own?
740 saDevice
.willBootIntoPitMode
= (saDevice
.mode
& SA_MODE_GET_PITMODE
) ? true : false;
741 LOG_DEBUG(VTX
, "sainit: willBootIntoPitMode is %s\r\n", saDevice
.willBootIntoPitMode
? "true" : "false");
746 case SA_INITPHASE_WAIT_PITFREQ
:
747 if (saDevice
.orfreq
) {
748 initPhase
= SA_INITPHASE_DONE
;
752 case SA_INITPHASE_DONE
:
756 // Command queue control
758 timeMs_t nowMs
= millis(); // Don't substitute with "currentTimeUs / 1000"; sa_lastTransmissionMs is based on millis().
759 static timeMs_t lastCommandSentMs
= 0; // Last non-GET_SETTINGS sent
761 if ((sa_outstanding
!= SA_CMD_NONE
) && (nowMs
- sa_lastTransmissionMs
> SMARTAUDIO_CMD_TIMEOUT
)) {
762 // Last command timed out
763 // LOG_DEBUG(VTX, "process: resending 0x%x", sa_outstanding);
764 // XXX Todo: Resend termination and possible offline transition
766 lastCommandSentMs
= nowMs
;
767 } else if (!saQueueEmpty()) {
768 // Command pending. Send it.
769 // LOG_DEBUG(VTX, "process: sending queue");
771 lastCommandSentMs
= nowMs
;
772 } else if ((nowMs
- lastCommandSentMs
< SMARTAUDIO_POLLING_WINDOW
) && (nowMs
- sa_lastTransmissionMs
>= SMARTAUDIO_POLLING_INTERVAL
)) {
773 //LOG_DEBUG(VTX, "process: sending status change polling");
779 // Interface to common VTX API
781 vtxDevType_e
vtxSAGetDeviceType(const vtxDevice_t
*vtxDevice
)
784 return VTXDEV_SMARTAUDIO
;
787 static bool vtxSAIsReady(const vtxDevice_t
*vtxDevice
)
789 return vtxDevice
!= NULL
&& !(saDevice
.power
== 0);
790 //wait until power reading exists
793 void vtxSASetBandAndChannel(vtxDevice_t
*vtxDevice
, uint8_t band
, uint8_t channel
)
796 if (saValidateBandAndChannel(band
, channel
)) {
797 saSetBandAndChannel(band
- 1, channel
- 1);
800 static void vtxSASetPowerByIndex(vtxDevice_t
*vtxDevice
, uint8_t index
)
802 static uint8_t buf
[6] = { 0xAA, 0x55, SACMD(SA_CMD_SET_POWER
), 1 };
804 if (!vtxSAIsReady(vtxDevice
)) {
809 LOG_DEBUG(VTX
, "SmartAudio doesn't support power off");
813 if (index
> saPowerCount
) {
814 LOG_DEBUG(VTX
, "Invalid power level");
818 LOG_DEBUG(VTX
, "saSetPowerByIndex: index %d, value %d\r\n", index
, buf
[4]);
821 switch (saDevice
.version
) {
823 buf
[4] = saPowerTable
[index
].dbi
;
829 buf
[4] = saPowerTable
[index
].dbi
;
830 buf
[4] |= 128; //set MSB to indicate set power by dbm
836 buf
[5] = CRC8(buf
, 5);
840 static void vtxSASetPitMode(vtxDevice_t
*vtxDevice
, uint8_t onoff
)
842 if (!vtxSAIsReady(vtxDevice
) || saDevice
.version
< SA_1_0
) {
846 if (onoff
&& saDevice
.version
< SA_2_1
) {
847 // Smart Audio prior to V2.1 can not turn pit mode on by software.
851 if (saDevice
.version
>= SA_2_1
&& !saDevice
.willBootIntoPitMode
) {
853 // enable pitmode using SET_POWER command with 0 dbm.
854 // This enables pitmode without causing the device to boot into pitmode next power-up
855 static uint8_t buf
[6] = { 0xAA, 0x55, SACMD(SA_CMD_SET_POWER
), 1 };
857 buf
[5] = CRC8(buf
, 5);
859 LOG_DEBUG(VTX
, "vtxSASetPitMode: set power to 0 dbm\r\n");
861 saSetMode(SA_MODE_CLR_PITMODE
);
862 LOG_DEBUG(VTX
, "vtxSASetPitMode: clear pitmode permanently");
867 uint8_t newMode
= onoff
? 0 : SA_MODE_CLR_PITMODE
;
869 if (saDevice
.mode
& SA_MODE_GET_OUT_RANGE_PITMODE
) {
870 newMode
|= SA_MODE_SET_OUT_RANGE_PITMODE
;
873 if ((saDevice
.mode
& SA_MODE_GET_IN_RANGE_PITMODE
) || (onoff
&& newMode
== 0)) {
874 // ensure when turning on pit mode that pit mode gets actually enabled
875 newMode
|= SA_MODE_SET_IN_RANGE_PITMODE
;
877 LOG_DEBUG(VTX
, "vtxSASetPitMode %s with stored mode 0x%x por %s, pir %s, newMode 0x%x\r\n", onoff
? "on" : "off", saDevice
.mode
,
878 (saDevice
.mode
& SA_MODE_GET_OUT_RANGE_PITMODE
) ? "on" : "off",
879 (saDevice
.mode
& SA_MODE_GET_IN_RANGE_PITMODE
) ? "on" : "off" , newMode
);
887 static bool vtxSAGetBandAndChannel(const vtxDevice_t
*vtxDevice
, uint8_t *pBand
, uint8_t *pChannel
)
889 if (!vtxSAIsReady(vtxDevice
)) {
893 // if in user-freq mode then report band as zero
894 *pBand
= (saDevice
.mode
& SA_MODE_GET_FREQ_BY_FREQ
) ? 0 :
895 (SA_DEVICE_CHVAL_TO_BAND(saDevice
.channel
) + 1);
896 *pChannel
= SA_DEVICE_CHVAL_TO_CHANNEL(saDevice
.channel
) + 1;
901 static bool vtxSAGetPowerIndex(const vtxDevice_t
*vtxDevice
, uint8_t *pIndex
)
903 if (!vtxSAIsReady(vtxDevice
)) {
907 *pIndex
= ((saDevice
.version
== SA_1_0
) ? saDacToPowerIndex(saDevice
.power
) : saDevice
.power
);
911 static bool vtxSAGetPitMode(const vtxDevice_t
*vtxDevice
, uint8_t *pOnOff
)
913 if (!(vtxSAIsReady(vtxDevice
) && (saDevice
.version
< SA_2_0
))) {
917 *pOnOff
= (saDevice
.mode
& SA_MODE_GET_PITMODE
) ? 1 : 0;
921 static bool vtxSAGetFreq(const vtxDevice_t
*vtxDevice
, uint16_t *pFreq
)
923 if (!vtxSAIsReady(vtxDevice
)) {
927 // if not in user-freq mode then convert band/chan to frequency
928 *pFreq
= (saDevice
.mode
& SA_MODE_GET_FREQ_BY_FREQ
) ? saDevice
.freq
:
929 vtx58_Bandchan2Freq(SA_DEVICE_CHVAL_TO_BAND(saDevice
.channel
) + 1,
930 SA_DEVICE_CHVAL_TO_CHANNEL(saDevice
.channel
) + 1);
934 static bool vtxSAGetPower(const vtxDevice_t
*vtxDevice
, uint8_t *pIndex
, uint16_t *pPowerMw
)
938 if (!vtxSAGetPowerIndex(vtxDevice
, &powerIndex
)) {
942 *pIndex
= powerIndex
;
943 *pPowerMw
= (powerIndex
> 0) ? saPowerTable
[powerIndex
- 1].mW
: 0;
947 static bool vtxSAGetOsdInfo(const vtxDevice_t
*vtxDevice
, vtxDeviceOsdInfo_t
* pOsdInfo
)
952 uint8_t band
, channel
;
954 if (!vtxSAGetBandAndChannel(vtxDevice
, &band
, &channel
)) {
958 if (!vtxSAGetFreq(vtxDevice
, &freq
)) {
962 if (!vtxSAGetPower(vtxDevice
, &powerIndex
, &powerMw
)) {
966 pOsdInfo
->band
= band
;
967 pOsdInfo
->channel
= channel
;
968 pOsdInfo
->frequency
= freq
;
969 pOsdInfo
->powerIndex
= powerIndex
;
970 pOsdInfo
->powerMilliwatt
= powerMw
;
971 pOsdInfo
->bandLetter
= vtx58BandNames
[band
][0];
972 pOsdInfo
->bandName
= vtx58BandNames
[band
];
973 pOsdInfo
->channelName
= vtx58ChannelNames
[channel
];
974 pOsdInfo
->powerIndexLetter
= '0' + powerIndex
;
978 static const vtxVTable_t saVTable
= {
979 .process
= vtxSAProcess
,
980 .getDeviceType
= vtxSAGetDeviceType
,
981 .isReady
= vtxSAIsReady
,
982 .setBandAndChannel
= vtxSASetBandAndChannel
,
983 .setPowerByIndex
= vtxSASetPowerByIndex
,
984 .setPitMode
= vtxSASetPitMode
,
985 .getBandAndChannel
= vtxSAGetBandAndChannel
,
986 .getPowerIndex
= vtxSAGetPowerIndex
,
987 .getPitMode
= vtxSAGetPitMode
,
988 .getFrequency
= vtxSAGetFreq
,
989 .getPower
= vtxSAGetPower
,
990 .getOsdInfo
= vtxSAGetOsdInfo
,
994 #endif // VTX_SMARTAUDIO