mirror of
				https://github.com/pascallanger/DIY-Multiprotocol-TX-Module.git
				synced 2025-10-31 03:14:16 +00:00 
			
		
		
		
	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
 |