mirror of
https://github.com/pascallanger/DIY-Multiprotocol-TX-Module.git
synced 2025-02-04 18:38:13 +00:00
4a626eaf14
Loads of protocols have been touched by this change. Some testing has been done but please test on all your models. The XN297 emulation selects in this order: - the CC2500 if it is available and bitrate=250K. Configure the option field automatically for RF tune. - the NRF for all bitrates if it is available - if NRF is not available and bitrate=1M then an invalid protocol is sent automatically to the radio. CC2500 @250K can now receive normal and enhanced payloads. OMP protocol supports telemetry on CC2500 and is also for NRF only modules including telemetry. Separation of E016H (new protocol) from E01X due to different structure. MJXQ, MT99XX, Q303 and XK: some sub protocols available on CC2500 only.
347 lines
9.1 KiB
C++
347 lines
9.1 KiB
C++
/*
|
|
This project is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Multiprotocol is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Multiprotocol. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
// compatible with MJX WLH08, X600, X800, H26D, Eachine E010
|
|
// Last sync with hexfet new_protocols/mjxq_nrf24l01.c dated 2016-01-17
|
|
|
|
#if defined(MJXQ_CCNRF_INO)
|
|
|
|
#include "iface_xn297.h"
|
|
|
|
#define MJXQ_BIND_COUNT 150
|
|
#define MJXQ_PACKET_PERIOD 4000 // Timeout for callback in uSec
|
|
#define MJXQ_INITIAL_WAIT 500
|
|
#define MJXQ_PACKET_SIZE 16
|
|
#define MJXQ_RF_NUM_CHANNELS 4
|
|
#define MJXQ_ADDRESS_LENGTH 5
|
|
|
|
// haven't figured out txid<-->rf channel mapping for MJX models
|
|
const uint8_t PROGMEM MJXQ_map_txid[][3] = {
|
|
{0xF8, 0x4F, 0x1C},
|
|
{0xC8, 0x6E, 0x02},
|
|
{0x48, 0x6A, 0x40} };
|
|
const uint8_t PROGMEM MJXQ_map_rfchan[][4] = {
|
|
{0x0A, 0x46, 0x3A, 0x42},
|
|
{0x0A, 0x3C, 0x36, 0x3F},
|
|
{0x0A, 0x43, 0x36, 0x3F} };
|
|
|
|
const uint8_t PROGMEM E010_map_txid[][2] = {
|
|
{0x4F, 0x1C},
|
|
{0x90, 0x1C},
|
|
{0x24, 0x36},
|
|
{0x7A, 0x40},
|
|
{0x61, 0x31},
|
|
{0x5D, 0x37},
|
|
{0xFD, 0x4F},
|
|
{0x86, 0x3C},
|
|
{0x41, 0x22},
|
|
{0xEE, 0xB3},
|
|
{0x9A, 0xB2},
|
|
{0xC0, 0x44},
|
|
{0x2A, 0xFE},
|
|
{0xD7, 0x6E},
|
|
{0x3C, 0xCD}, // for this ID rx_tx_addr[2]=0x01
|
|
{0xF5, 0x2B} // for this ID rx_tx_addr[2]=0x02
|
|
};
|
|
const uint8_t PROGMEM E010_map_rfchan[][2] = {
|
|
{0x3A, 0x35},
|
|
{0x2E, 0x36},
|
|
{0x32, 0x3E},
|
|
{0x2E, 0x3C},
|
|
{0x2F, 0x3B},
|
|
{0x33, 0x3B},
|
|
{0x33, 0x3B},
|
|
{0x34, 0x3E},
|
|
{0x34, 0x2F},
|
|
{0x39, 0x3E},
|
|
{0x2E, 0x38},
|
|
{0x2E, 0x36},
|
|
{0x2E, 0x38},
|
|
{0x3A, 0x41},
|
|
{0x32, 0x3E},
|
|
{0x33, 0x3F}
|
|
};
|
|
|
|
#define MJXQ_PAN_TILT_COUNT 16 // for H26D - match stock tx timing
|
|
#define MJXQ_PAN_DOWN 0x08
|
|
#define MJXQ_PAN_UP 0x04
|
|
#define MJXQ_TILT_DOWN 0x20
|
|
#define MJXQ_TILT_UP 0x10
|
|
|
|
static uint8_t __attribute__((unused)) MJXQ_pan_tilt_value()
|
|
{
|
|
// CH12_SW PAN // H26D
|
|
// CH13_SW TILT
|
|
uint8_t pan = 0;
|
|
packet_count++;
|
|
if(packet_count & MJXQ_PAN_TILT_COUNT)
|
|
{
|
|
if(CH12_SW)
|
|
pan=MJXQ_PAN_UP;
|
|
if(Channel_data[CH12]<CHANNEL_MIN_COMMAND)
|
|
pan=MJXQ_PAN_DOWN;
|
|
if(CH13_SW)
|
|
pan+=MJXQ_TILT_UP;
|
|
if(Channel_data[CH13]<CHANNEL_MIN_COMMAND)
|
|
pan+=MJXQ_TILT_DOWN;
|
|
}
|
|
return pan;
|
|
}
|
|
|
|
#define MJXQ_CHAN2TRIM(X) (((X) & 0x80 ? (X) : 0x7f - (X)) >> 1)
|
|
static void __attribute__((unused)) MJXQ_send_packet(uint8_t bind)
|
|
{
|
|
//RF freq
|
|
hopping_frequency_no++;
|
|
XN297_Hopping(hopping_frequency_no / 2);
|
|
hopping_frequency_no %= 2 * MJXQ_RF_NUM_CHANNELS; // channels repeated
|
|
|
|
//Build packet
|
|
packet[0] = convert_channel_8b(THROTTLE);
|
|
packet[1] = convert_channel_s8b(RUDDER);
|
|
packet[4] = 0x40; // rudder does not work well with dyntrim
|
|
packet[2] = 0x80 ^ convert_channel_s8b(ELEVATOR);
|
|
packet[5] = (CH9_SW || CH14_SW) ? 0x40 : MJXQ_CHAN2TRIM(packet[2]); // trim elevator
|
|
packet[3] = convert_channel_s8b(AILERON);
|
|
packet[6] = (CH9_SW || CH14_SW) ? 0x40 : MJXQ_CHAN2TRIM(packet[3]); // trim aileron
|
|
packet[7] = rx_tx_addr[0];
|
|
packet[8] = rx_tx_addr[1];
|
|
packet[9] = rx_tx_addr[2];
|
|
|
|
packet[10] = 0x00; // overwritten below for feature bits
|
|
packet[11] = 0x00; // overwritten below for X600
|
|
packet[12] = 0x00;
|
|
packet[13] = 0x00;
|
|
|
|
packet[14] = 0xC0; // bind value
|
|
|
|
// CH5_SW FLIP
|
|
// CH6_SW LED / ARM // H26WH - TDR Phoenix mini
|
|
// CH7_SW PICTURE
|
|
// CH8_SW VIDEO
|
|
// CH9_SW HEADLESS
|
|
// CH10_SW RTH
|
|
// CH11_SW AUTOFLIP // X800, X600
|
|
// CH12_SW PAN
|
|
// CH13_SW TILT
|
|
// CH14_SW XTRM // Dyntrim, don't use if high.
|
|
switch(sub_protocol)
|
|
{
|
|
case H26WH:
|
|
case H26D:
|
|
packet[10]=MJXQ_pan_tilt_value();
|
|
// fall through on purpose - no break
|
|
case WLH08:
|
|
case E010:
|
|
case PHOENIX:
|
|
packet[10] += GET_FLAG(CH10_SW, 0x02) //RTH
|
|
| GET_FLAG(CH9_SW, 0x01); //HEADLESS
|
|
if (!bind)
|
|
{
|
|
packet[14] = 0x04
|
|
| GET_FLAG(CH5_SW, 0x01) //FLIP
|
|
| GET_FLAG(CH7_SW, 0x08) //PICTURE
|
|
| GET_FLAG(CH8_SW, 0x10) //VIDEO
|
|
| GET_FLAG(!CH6_SW, 0x20); // LED or air/ground mode
|
|
if(sub_protocol==PHOENIX)
|
|
{
|
|
packet[10] |=0x20 //High rate
|
|
| GET_FLAG(CH6_SW, 0x80); // arm
|
|
packet[14] &= ~0x24; // unset air/ground & arm flags
|
|
}
|
|
if(sub_protocol==H26WH)
|
|
{
|
|
packet[10] |=0x40; //High rate
|
|
packet[14] &= ~0x24; // unset air/ground & arm flags
|
|
packet[14] |= GET_FLAG(CH6_SW, 0x02); // arm
|
|
}
|
|
}
|
|
break;
|
|
case X600:
|
|
packet[10] = GET_FLAG(!CH6_SW, 0x02); //LED
|
|
packet[11] = GET_FLAG(CH10_SW, 0x01); //RTH
|
|
if (!bind)
|
|
{
|
|
packet[14] = 0x02 // always high rates by bit2 = 1
|
|
| GET_FLAG(CH5_SW, 0x04) //FLIP
|
|
| GET_FLAG(CH11_SW, 0x10) //AUTOFLIP
|
|
| GET_FLAG(CH9_SW, 0x20); //HEADLESS
|
|
}
|
|
break;
|
|
case X800:
|
|
default:
|
|
packet[10] = 0x10
|
|
| GET_FLAG(!CH6_SW, 0x02) //LED
|
|
| GET_FLAG(CH11_SW, 0x01); //AUTOFLIP
|
|
if (!bind)
|
|
{
|
|
packet[14] = 0x02 // always high rates by bit2 = 1
|
|
| GET_FLAG(CH5_SW, 0x04) //FLIP
|
|
| GET_FLAG(CH7_SW, 0x08) //PICTURE
|
|
| GET_FLAG(CH8_SW, 0x10); //VIDEO
|
|
}
|
|
break;
|
|
}
|
|
|
|
uint8_t sum = packet[0];
|
|
for (uint8_t i=1; i < MJXQ_PACKET_SIZE-1; i++) sum += packet[i];
|
|
packet[15] = sum;
|
|
|
|
// Send
|
|
XN297_SetTxRxMode(TX_EN);
|
|
XN297_SetPower();
|
|
#ifdef NRF24L01_INSTALLED
|
|
if (sub_protocol == H26D || sub_protocol == H26WH)
|
|
{
|
|
//NRF24L01_WriteReg(NRF24L01_05_RF_CH, hopping_frequency[hopping_frequency_no / 2]);
|
|
//NRF24L01_WriteReg(NRF24L01_07_STATUS, 0x70);
|
|
//NRF24L01_FlushTx();
|
|
//NRF24L01_SetTxRxMode(TX_EN);
|
|
//NRF24L01_SetPower();
|
|
NRF24L01_WritePayload(packet, MJXQ_PACKET_SIZE);
|
|
}
|
|
else
|
|
#endif
|
|
{//E010, PHOENIX, WLH08, X600, X800
|
|
XN297_SetFreqOffset();
|
|
XN297_WritePayload(packet, MJXQ_PACKET_SIZE);
|
|
}
|
|
}
|
|
|
|
static void __attribute__((unused)) MJXQ_RF_init()
|
|
{
|
|
uint8_t addr[MJXQ_ADDRESS_LENGTH];
|
|
memcpy(addr, "\x6d\x6a\x77\x77\x77", MJXQ_ADDRESS_LENGTH);
|
|
if (sub_protocol == WLH08)
|
|
memcpy(hopping_frequency, "\x12\x22\x32\x42", MJXQ_RF_NUM_CHANNELS);
|
|
else
|
|
if (sub_protocol == H26D || sub_protocol == H26WH || sub_protocol == E010 || sub_protocol == PHOENIX)
|
|
memcpy(hopping_frequency, "\x2e\x36\x3e\x46", MJXQ_RF_NUM_CHANNELS);
|
|
else
|
|
{
|
|
memcpy(hopping_frequency, "\x0a\x35\x42\x3d", MJXQ_RF_NUM_CHANNELS);
|
|
memcpy(addr, "\x6d\x6a\x73\x73\x73", MJXQ_ADDRESS_LENGTH);
|
|
}
|
|
if (sub_protocol == E010 || sub_protocol == PHOENIX)
|
|
{
|
|
XN297_Configure(XN297_CRCEN, XN297_SCRAMBLED, XN297_250K);
|
|
XN297_SetTXAddr(addr, MJXQ_ADDRESS_LENGTH);
|
|
XN297_HoppingCalib(MJXQ_RF_NUM_CHANNELS);
|
|
}
|
|
else
|
|
{
|
|
XN297_Configure(XN297_CRCEN, XN297_SCRAMBLED, XN297_1M); // this will select the nrf and initialize it, therefore both H26 sub protocols can use common instructions
|
|
#ifdef NRF24L01_INSTALLED
|
|
if (sub_protocol == H26D || sub_protocol == H26WH)
|
|
NRF24L01_WriteRegisterMulti(NRF24L01_10_TX_ADDR, addr, MJXQ_ADDRESS_LENGTH);
|
|
else
|
|
#endif
|
|
XN297_SetTXAddr(addr, MJXQ_ADDRESS_LENGTH);
|
|
//NRF24L01_WriteReg(NRF24L01_11_RX_PW_P0, MJXQ_PACKET_SIZE); // no RX???
|
|
}
|
|
}
|
|
|
|
static void __attribute__((unused)) MJXQ_init2()
|
|
{
|
|
switch(sub_protocol)
|
|
{
|
|
case H26D:
|
|
memcpy(hopping_frequency, "\x32\x3e\x42\x4e", MJXQ_RF_NUM_CHANNELS);
|
|
break;
|
|
case H26WH:
|
|
memcpy(hopping_frequency, "\x37\x32\x47\x42", MJXQ_RF_NUM_CHANNELS);
|
|
break;
|
|
case E010:
|
|
case PHOENIX:
|
|
for(uint8_t i=0;i<2;i++)
|
|
{
|
|
hopping_frequency[i]=pgm_read_byte_near( &E010_map_rfchan[rx_tx_addr[3]&0x0F][i] );
|
|
hopping_frequency[i+2]=hopping_frequency[i]+0x10;
|
|
}
|
|
XN297_HoppingCalib(MJXQ_RF_NUM_CHANNELS);
|
|
break;
|
|
case WLH08:
|
|
// do nothing
|
|
break;
|
|
default:
|
|
for(uint8_t i=0;i<MJXQ_RF_NUM_CHANNELS;i++)
|
|
hopping_frequency[i]=pgm_read_byte_near( &MJXQ_map_rfchan[rx_tx_addr[3]%3][i] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void __attribute__((unused)) MJXQ_initialize_txid()
|
|
{
|
|
switch(sub_protocol)
|
|
{
|
|
case H26WH:
|
|
memcpy(rx_tx_addr, "\xa4\x03\x00", 3);
|
|
break;
|
|
case E010:
|
|
case PHOENIX:
|
|
for(uint8_t i=0;i<2;i++)
|
|
rx_tx_addr[i]=pgm_read_byte_near( &E010_map_txid[rx_tx_addr[3]&0x0F][i] );
|
|
if((rx_tx_addr[3]&0x0E) == 0x0E)
|
|
rx_tx_addr[2]=(rx_tx_addr[3]&0x01)+1;
|
|
else
|
|
rx_tx_addr[2]=0;
|
|
break;
|
|
case WLH08:
|
|
rx_tx_addr[0]&=0xF8;
|
|
rx_tx_addr[2]=rx_tx_addr[3]; // Make use of RX_Num
|
|
break;
|
|
default:
|
|
for(uint8_t i=0;i<3;i++)
|
|
rx_tx_addr[i]=pgm_read_byte_near( &MJXQ_map_txid[rx_tx_addr[3]%3][i] );
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint16_t MJXQ_callback()
|
|
{
|
|
if(IS_BIND_DONE)
|
|
{
|
|
#ifdef MULTI_SYNC
|
|
telemetry_set_input_sync(MJXQ_PACKET_PERIOD);
|
|
#endif
|
|
MJXQ_send_packet(0);
|
|
}
|
|
else
|
|
{
|
|
if (bind_counter == 0)
|
|
{
|
|
MJXQ_init2();
|
|
BIND_DONE;
|
|
}
|
|
else
|
|
{
|
|
bind_counter--;
|
|
MJXQ_send_packet(1);
|
|
}
|
|
}
|
|
|
|
return MJXQ_PACKET_PERIOD;
|
|
}
|
|
|
|
void MJXQ_init(void)
|
|
{
|
|
BIND_IN_PROGRESS; // autobind protocol
|
|
bind_counter = MJXQ_BIND_COUNT;
|
|
MJXQ_initialize_txid();
|
|
MJXQ_RF_init();
|
|
packet_count=0;
|
|
}
|
|
|
|
#endif
|