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/>.
22 * This file implements the custom Crazyflie serial Rx protocol which
23 * consists of CRTP packets sent from the onboard NRF51 over UART using
24 * the syslink protocol.
26 * The implementation supports two types of commander packets:
27 * - RPYT on port CRTP_PORT_SETPOINT
28 * - CPPM emulation on port CRTP_PORT_SETPOINT_GENERIC using type cppmEmuType
30 * The CPPM emulation packet type is recommended for use with this target.
32 * The RPYT type is mainly for legacy support (various mobile apps, python PC client,
33 * etc) and can be used with the following restrictions to ensure angles are accurately
34 * translated into PWM values:
35 * - Max angles for pitch and roll must be set to 50 degrees
36 * - Max yaw rate must be set to 400 degrees
38 * This implementation has been ported from the Crazyflie source code.
40 * For more information, see the following Crazyflie wiki pages:
41 * CRTP: https://wiki.bitcraze.io/projects:crazyflie:crtp
42 * Syslink: https://wiki.bitcraze.io/doc:crazyflie:syslink:index
48 #include "io/serial.h"
53 #include "rx/targetcustomserial.h"
57 #define SYSLINK_BAUDRATE 1000000
59 // Variables for the Syslink Rx state machine
60 static syslinkRxState_e rxState
= waitForFirstStart
;
61 static syslinkPacket_t slp
;
62 static uint8_t dataIndex
= 0;
63 static uint8_t cksum
[2] = {0};
64 static uint8_t counter
= 0;
66 static rxRuntimeState_t
*rxRuntimeStatePtr
;
67 static serialPort_t
*serialPort
;
69 #define SUPPORTED_CHANNEL_COUNT (4 + CRTP_CPPM_EMU_MAX_AUX_CHANNELS)
70 static uint32_t channelData
[SUPPORTED_CHANNEL_COUNT
];
71 static bool rcFrameComplete
= false;
73 static void routeIncommingPacket(syslinkPacket_t
* slp
)
75 // Only support packets of type SYSLINK_RADIO_RAW
76 if (slp
->type
== SYSLINK_RADIO_RAW
) {
77 crtpPacket_t
*crtpPacket
= (crtpPacket_t
*)(slp
->data
);
79 switch (crtpPacket
->header
.port
) {
80 case CRTP_PORT_SETPOINT
:
82 crtpCommanderRPYT_t
*crtpRYPTPacket
=
83 (crtpCommanderRPYT_t
*)&crtpPacket
->data
[0];
85 // Write RPYT channels in TAER order
87 // Translate thrust from 0-MAX_UINT16 into a PWM_style value (1000-2000)
88 channelData
[0] = (crtpRYPTPacket
->thrust
* 1000 / UINT16_MAX
) + 1000;
90 // Translate RPY from an angle setpoint to a PWM-style value (1000-2000)
91 // For R and P, assume the client sends a max of -/+50 degrees (full range of 100)
92 // For Y, assume a max of -/+400 deg/s (full range of 800)
93 channelData
[1] = (uint16_t)((crtpRYPTPacket
->roll
/ 100 * 1000) + 1500);
94 channelData
[2] = (uint16_t)((-crtpRYPTPacket
->pitch
/ 100 * 1000) + 1500); // Pitch is inverted
95 channelData
[3] = (uint16_t)((crtpRYPTPacket
->yaw
/ 800 * 1000) + 1500);
97 rcFrameComplete
= true;
100 case CRTP_PORT_SETPOINT_GENERIC
:
101 // First byte of the packet is the type
102 // Only support the CPPM Emulation type
103 if (crtpPacket
->data
[0] == cppmEmuType
) {
104 crtpCommanderCPPMEmuPacket_t
*crtpCppmPacket
=
105 (crtpCommanderCPPMEmuPacket_t
*)&crtpPacket
->data
[1];
107 // Write RPYT channels in TAER order
108 channelData
[0] = crtpCppmPacket
->channelThrust
;
109 channelData
[1] = crtpCppmPacket
->channelRoll
;
110 channelData
[2] = crtpCppmPacket
->channelPitch
;
111 channelData
[3] = crtpCppmPacket
->channelYaw
;
113 // Write the rest of the auxiliary channels
115 for (i
= 0; i
< crtpCppmPacket
->hdr
.numAuxChannels
; i
++) {
116 channelData
[i
+ 4] = crtpCppmPacket
->channelAux
[i
];
119 rcFrameComplete
= true;
122 // Unsupported port - do nothing
128 // Receive ISR callback
129 static void dataReceive(uint16_t c
, void *data
)
135 case waitForFirstStart
:
136 rxState
= (c
== SYSLINK_START_BYTE1
) ? waitForSecondStart
: waitForFirstStart
;
138 case waitForSecondStart
:
139 rxState
= (c
== SYSLINK_START_BYTE2
) ? waitForType
: waitForFirstStart
;
145 rxState
= waitForLength
;
148 if (c
<= SYSLINK_MTU
) {
151 cksum
[1] += cksum
[0];
153 rxState
= (c
> 0) ? waitForData
: waitForChksum1
;
156 rxState
= waitForFirstStart
;
160 slp
.data
[dataIndex
] = c
;
162 cksum
[1] += cksum
[0];
164 if (dataIndex
== slp
.length
) {
165 rxState
= waitForChksum1
;
170 rxState
= waitForChksum2
;
173 rxState
= waitForFirstStart
; //Checksum error
178 routeIncommingPacket(&slp
);
181 rxState
= waitForFirstStart
; //Checksum error
183 rxState
= waitForFirstStart
;
190 static uint8_t frameStatus(rxRuntimeState_t
*rxRuntimeState
)
192 UNUSED(rxRuntimeState
);
194 if (!rcFrameComplete
) {
195 return RX_FRAME_PENDING
;
198 // Set rcFrameComplete to false so we don't process this one twice
199 rcFrameComplete
= false;
201 return RX_FRAME_COMPLETE
;
204 static float readRawRC(const rxRuntimeState_t
*rxRuntimeState
, uint8_t chan
)
206 if (chan
>= rxRuntimeState
->channelCount
) {
209 return channelData
[chan
];
213 bool targetCustomSerialRxInit(const rxConfig_t
*rxConfig
, rxRuntimeState_t
*rxRuntimeState
)
215 rxRuntimeStatePtr
= rxRuntimeState
;
217 if (rxConfig
->serialrx_provider
!= SERIALRX_TARGET_CUSTOM
)
222 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_RX_SERIAL
);
227 rxRuntimeState
->channelCount
= SUPPORTED_CHANNEL_COUNT
;
228 rxRuntimeState
->rxRefreshRate
= 20000; // Value taken from rx_spi.c (NRF24 is being used downstream)
229 rxRuntimeState
->rcReadRawFn
= readRawRC
;
230 rxRuntimeState
->rcFrameStatusFn
= frameStatus
;
232 serialPort
= openSerialPort(portConfig
->identifier
,
238 SERIAL_NOT_INVERTED
| SERIAL_STOPBITS_1
| SERIAL_PARITY_NO
241 return serialPort
!= NULL
;