5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
24 // for the MULTI protocol definition
25 // see https://github.com/pascallanger/DIY-Multiprotocol-TX-Module
26 // file Multiprotocol/multiprotocol.h
28 #define MULTI_SEND_BIND (1 << 7)
29 #define MULTI_SEND_RANGECHECK (1 << 5)
30 #define MULTI_SEND_AUTOBIND (1 << 6)
32 #define MULTI_CHANS 16
33 #define MULTI_CHAN_BITS 11
35 #define MULTI_NORMAL 0x00
36 #define MULTI_FAILSAFE 0x01
37 #define MULTI_DATA 0x02
39 static void sendFrameProtocolHeader(uint8_t moduleIdx
, bool failsafe
);
40 void sendChannels(uint8_t moduleIdx
);
42 static void sendSport(uint8_t moduleIdx
);
43 static void sendHott(uint8_t moduleIdx
);
46 void multiPatchCustom(uint8_t moduleIdx
)
48 if (g_model
.moduleData
[moduleIdx
].multi
.customProto
) {
49 uint8_t type
= g_model
.moduleData
[moduleIdx
].getMultiProtocol() - 1; // custom where starting at 1, otx list at 0
50 int subtype
= g_model
.moduleData
[moduleIdx
].subType
;
52 g_model
.moduleData
[moduleIdx
].multi
.customProto
= 0;
54 if (type
== 2) { // multi PROTO_FRSKYD
55 g_model
.moduleData
[moduleIdx
].subType
= 1; // D8
58 else if (type
== 14) { // multi PROTO_FRSKYX
59 g_model
.moduleData
[moduleIdx
].setMultiProtocol(2);
62 g_model
.moduleData
[moduleIdx
].subType
= 0;
65 g_model
.moduleData
[moduleIdx
].subType
= 2;
68 g_model
.moduleData
[moduleIdx
].subType
= 4;
71 g_model
.moduleData
[moduleIdx
].subType
= 5;
76 else if (type
== 24) { // multi PROTO_FRSKYV
77 g_model
.moduleData
[moduleIdx
].setMultiProtocol(2);
78 g_model
.moduleData
[moduleIdx
].subType
= 3;
85 g_model
.moduleData
[moduleIdx
].setMultiProtocol(type
);
89 static void sendMulti(uint8_t moduleIdx
, uint8_t b
)
91 #if defined(HARDWARE_INTERNAL_MODULE)
92 if (moduleIdx
== INTERNAL_MODULE
) {
93 intmodulePulsesData
.multi
.sendByte(b
);
100 static void sendFailsafeChannels(uint8_t moduleIdx
)
103 uint8_t bitsavailable
= 0;
105 for (int i
= 0; i
< MULTI_CHANS
; i
++) {
106 int16_t failsafeValue
= g_model
.failsafeChannels
[i
];
109 if (g_model
.moduleData
[moduleIdx
].failsafeMode
== FAILSAFE_HOLD
) {
112 else if (g_model
.moduleData
[moduleIdx
].failsafeMode
== FAILSAFE_NOPULSES
) {
116 failsafeValue
+= 2 * PPM_CH_CENTER(g_model
.moduleData
[moduleIdx
].channelsStart
+ i
) - 2 * PPM_CENTER
;
117 pulseValue
= limit(1, (failsafeValue
* 800 / 1000) + 1024, 2047);
120 bits
|= pulseValue
<< bitsavailable
;
121 bitsavailable
+= MULTI_CHAN_BITS
;
122 while (bitsavailable
>= 8) {
123 sendMulti(moduleIdx
, (uint8_t) (bits
& 0xff));
130 void setupPulsesMulti(uint8_t moduleIdx
)
132 static int counter
[2] = {0,0}; //TODO
133 static uint8_t invert
[2] = {0x00, //internal
134 #if defined(PCBTARANIS) || defined(PCBHORUS)
140 uint8_t type
=MULTI_NORMAL
;
143 if (counter
[moduleIdx
] % 1000 == 0 && g_model
.moduleData
[moduleIdx
].failsafeMode
!= FAILSAFE_NOT_SET
&& g_model
.moduleData
[moduleIdx
].failsafeMode
!= FAILSAFE_RECEIVER
) {
144 type
|=MULTI_FAILSAFE
;
147 // Invert telemetry if needed
148 if (invert
[moduleIdx
] & 0x80 && !g_model
.moduleData
[moduleIdx
].multi
.disableTelemetry
) {
149 if (getMultiModuleStatus(moduleIdx
).isValid()) {
150 invert
[moduleIdx
] &= 0x08; // Telemetry received, stop searching
152 else if (counter
[moduleIdx
] % 100 == 0) {
153 invert
[moduleIdx
] ^= 0x08; // Try inverting telemetry
157 counter
[moduleIdx
]++;
160 sendFrameProtocolHeader(moduleIdx
, type
&MULTI_FAILSAFE
);
163 if (type
& MULTI_FAILSAFE
)
164 sendFailsafeChannels(moduleIdx
);
166 sendChannels(moduleIdx
);
168 // Multi V1.3.X.X -> Send byte 26, Protocol (bits 7 & 6), RX_Num (bits 5 & 4), invert, not used, disable telemetry, disable mapping
169 sendMulti(moduleIdx
, (uint8_t) (((g_model
.moduleData
[moduleIdx
].getMultiProtocol()+3)&0xC0)
170 | (g_model
.header
.modelId
[moduleIdx
] & 0x30)
171 | (invert
[moduleIdx
] & 0x08)
172 //| 0x04 // Future use
173 | (g_model
.moduleData
[moduleIdx
].multi
.disableTelemetry
<< 1)
174 | g_model
.moduleData
[moduleIdx
].multi
.disableMapping
));
176 // Multi V1.3.X.X -> Send protocol additional data: max 9 bytes
178 if (getMultiModuleStatus(moduleIdx
).isValid()) {
179 MultiModuleStatus
&status
= getMultiModuleStatus(moduleIdx
);
180 if (status
.minor
>= 3 && !(status
.flags
& 0x80)) { //Version 1.3.x.x or more and Buffer not full
182 if (IS_D16_MULTI(moduleIdx
) && outputTelemetryBuffer
.destination
== TELEMETRY_ENDPOINT_SPORT
&& outputTelemetryBuffer
.size
) {
183 sendSport(moduleIdx
); //8 bytes of additional data
185 else if (IS_HOTT_MULTI(moduleIdx
)) {
186 sendHott(moduleIdx
); //1 byte of additional data
193 void setupPulsesMultiExternalModule()
195 #if defined(PPM_PIN_SERIAL)
196 extmodulePulsesData
.dsm2
.serialByte
= 0 ;
197 extmodulePulsesData
.dsm2
.serialBitCount
= 0 ;
199 extmodulePulsesData
.dsm2
.rest
= getMultiSyncStatus(EXTERNAL_MODULE
).getAdjustedRefreshRate();
200 extmodulePulsesData
.dsm2
.index
= 0;
203 extmodulePulsesData
.dsm2
.ptr
= extmodulePulsesData
.dsm2
.pulses
;
205 setupPulsesMulti(EXTERNAL_MODULE
);
209 #if defined(INTERNAL_MODULE_MULTI)
210 void setupPulsesMultiInternalModule()
212 intmodulePulsesData
.multi
.initFrame();
213 setupPulsesMulti(INTERNAL_MODULE
);
217 void sendChannels(uint8_t moduleIdx
)
220 uint8_t bitsavailable
= 0;
222 // byte 4-25, channels 0..2047
223 // Range for pulses (channelsOutputs) is [-1024:+1024] for [-100%;100%]
224 // Multi uses [204;1843] as [-100%;100%]
225 for (int i
= 0; i
< MULTI_CHANS
; i
++) {
226 int channel
= g_model
.moduleData
[moduleIdx
].channelsStart
+ i
;
227 int value
= channelOutputs
[channel
] + 2 * PPM_CH_CENTER(channel
) - 2 * PPM_CENTER
;
230 value
= value
* 800 / 1000 + 1024;
231 value
= limit(0, value
, 2047);
233 bits
|= value
<< bitsavailable
;
234 bitsavailable
+= MULTI_CHAN_BITS
;
235 while (bitsavailable
>= 8) {
236 sendMulti(moduleIdx
, (uint8_t) (bits
& 0xff));
243 void sendFrameProtocolHeader(uint8_t moduleIdx
, bool failsafe
)
244 {// byte 1+2, protocol information
246 // Our enumeration starts at 0
247 int type
= g_model
.moduleData
[moduleIdx
].getMultiProtocol() + 1;
248 int subtype
= g_model
.moduleData
[moduleIdx
].subType
;
249 int8_t optionValue
= g_model
.moduleData
[moduleIdx
].multi
.optionValue
;
251 uint8_t protoByte
= 0;
253 if (moduleState
[moduleIdx
].mode
== MODULE_MODE_SPECTRUM_ANALYSER
) {
254 sendMulti(moduleIdx
, (uint8_t) 0x54); // Header byte
255 sendMulti(moduleIdx
, (uint8_t) 54); // Spectrum custom protocol
256 sendMulti(moduleIdx
, (uint8_t) 0);
257 sendMulti(moduleIdx
, (uint8_t) 0);
261 if (moduleState
[moduleIdx
].mode
== MODULE_MODE_BIND
)
262 protoByte
|= MULTI_SEND_BIND
;
263 else if (moduleState
[moduleIdx
].mode
== MODULE_MODE_RANGECHECK
)
264 protoByte
|= MULTI_SEND_RANGECHECK
;
267 if (g_model
.moduleData
[moduleIdx
].getMultiProtocol() == MODULE_SUBTYPE_MULTI_DSM2
) {
269 // Autobinding should always be done in DSMX 11ms
270 if (g_model
.moduleData
[moduleIdx
].multi
.autoBindMode
&& moduleState
[moduleIdx
].mode
== MODULE_MODE_BIND
)
271 subtype
= MM_RF_DSM2_SUBTYPE_AUTO
;
273 // Multi module in DSM mode wants the number of channels to be used as option value
275 optionValue
= 0x80 | sentModuleChannels(moduleIdx
); // Max throw
277 optionValue
= sentModuleChannels(moduleIdx
);
280 // 15 for Multimodule is FrskyX or D16 which we map as a subprotocol of 3 (FrSky)
281 // all protos > frskyx are therefore also off by one
285 // 25 is again a FrSky protocol (FrskyV) so shift again
289 if (g_model
.moduleData
[moduleIdx
].getMultiProtocol() == MODULE_SUBTYPE_MULTI_FRSKY
) {
290 if (subtype
== MM_RF_FRSKY_SUBTYPE_D8
) {
294 } else if (subtype
== MM_RF_FRSKY_SUBTYPE_V8
) {
300 if (subtype
== MM_RF_FRSKY_SUBTYPE_D16_8CH
) // D16 8ch
302 else if (subtype
== MM_RF_FRSKY_SUBTYPE_D16
)
304 else if (subtype
== MM_RF_FRSKY_SUBTYPE_D16_LBT
)
307 subtype
= 3; // MM_RF_FRSKY_SUBTYPE_D16_LBT_8CH
311 // Set the highest bit of option byte in AFHDS2A protocol to instruct MULTI to passthrough telemetry bytes instead
312 // of sending Frsky D telemetry
313 if (g_model
.moduleData
[moduleIdx
].getMultiProtocol() == MODULE_SUBTYPE_MULTI_FS_AFHDS2A
)
314 optionValue
= optionValue
| 0x80;
316 // For custom protocol send unmodified type byte
317 if (g_model
.moduleData
[moduleIdx
].getMultiProtocol() == MM_RF_CUSTOM_SELECTED
)
318 type
= g_model
.moduleData
[moduleIdx
].getMultiProtocol();
321 uint8_t headerByte
= 0x55;
322 // header, byte 0, 0x55 for proto 0-31, 0x54 for proto 32-63
329 sendMulti(moduleIdx
, headerByte
);
332 protoByte
|= (type
& 0x1f);
333 if (g_model
.moduleData
[moduleIdx
].getMultiProtocol() != MODULE_SUBTYPE_MULTI_DSM2
)
334 protoByte
|= (g_model
.moduleData
[moduleIdx
].multi
.autoBindMode
<< 6);
336 sendMulti(moduleIdx
, protoByte
);
338 // byte 2, subtype, powermode, model id
339 sendMulti(moduleIdx
, (uint8_t) ((g_model
.header
.modelId
[moduleIdx
] & 0x0f)
340 | ((subtype
& 0x7) << 4)
341 | (g_model
.moduleData
[moduleIdx
].multi
.lowPowerMode
<< 7))
345 sendMulti(moduleIdx
, (uint8_t) optionValue
);
349 void sendSport(uint8_t moduleIdx
)
351 // example: B7 30 30 0C 80 00 00 00 13
354 // unstuff and remove crc
355 for (uint8_t i
= 0; i
< outputTelemetryBuffer
.size
- 1 && j
< 8; i
++, j
++) {
356 if (outputTelemetryBuffer
.data
[i
] == BYTE_STUFF
) {
358 sendMulti(moduleIdx
, outputTelemetryBuffer
.data
[i
] ^ STUFF_MASK
);
361 sendMulti(moduleIdx
, outputTelemetryBuffer
.data
[i
]);
365 outputTelemetryBuffer
.reset(); // empty buffer
368 void sendHott(uint8_t moduleIdx
)
370 if (Multi_Buffer
&& memcmp(Multi_Buffer
, "HoTT", 4) == 0 && Multi_Buffer
[5] >= 0xD7 && Multi_Buffer
[5] <= 0xDF) {
371 // HoTT Lua script is running
372 sendMulti(moduleIdx
, Multi_Buffer
[5]);