/********************************************************* Multiprotocol Tx code by Midelic and Pascal Langer(hpnuts) http://www.rcgroups.com/forums/showthread.php?t=2165676 https://github.com/pascallanger/DIY-Multiprotocol-TX-Module/edit/master/README.md Thanks to PhracturedBlue Ported from deviation firmware 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 . */ #include #include #include #include "multiprotocol.h" //****************************************************** // Multiprotocol module configuration starts here //Uncomment the TX type #define ER9X //#define DEVO7 //Uncomment to enable 8 channels serial protocol, 16 otherwise //#define NUM_SERIAL_CH_8 //Uncomment to enable telemetry #define TELEMETRY //Protocols to include in compilation, comment to exclude #define BAYANG_NRF24L01_INO #define CG023_NRF24L01_INO #define CX10_NRF24L01_INO #define DEVO_CYRF6936_INO #define DSM2_CYRF6936_INO #define FLYSKY_A7105_INO #define FRSKY_CC2500_INO #define HISKY_NRF24L01_INO #define HUBSAN_A7105_INO #define KN_NRF24L01_INO #define SLT_NRF24L01_INO #define SYMAX_NRF24L01_INO #define V2X2_NRF24L01_INO #define YD717_NRF24L01_INO //#define FRSKYX_CC2500_INO //Update this table to set which protocol/sub_protocol is called for the corresponding dial number static const uint8_t PPM_prot[15][2]= { {MODE_FLYSKY , Flysky }, //Dial=1 {MODE_HUBSAN , 0 }, //Dial=2 {MODE_FRSKY , 0 }, //Dial=3 {MODE_HISKY , Hisky }, //Dial=4 {MODE_V2X2 , 0 }, //Dial=5 {MODE_DSM2 , DSM2 }, //Dial=6 {MODE_DEVO , 0 }, //Dial=7 {MODE_YD717 , YD717 }, //Dial=8 {MODE_KN , 0 }, //Dial=9 {MODE_SYMAX , SYMAX }, //Dial=10 {MODE_SLT , 0 }, //Dial=11 {MODE_CX10 , CX10_BLUE }, //Dial=12 {MODE_CG023 , CG023 }, //Dial=13 {MODE_BAYANG , 0 }, //Dial=14 {MODE_SYMAX , SYMAX5C } //Dial=15 }; // //TX definitions with timing endpoints and channels order // // Turnigy PPM and channels #if defined(ER9X) #define PPM_MAX 2140 #define PPM_MIN 860 #define PPM_MAX_100 2012 #define PPM_MIN_100 988 enum chan_order{ AILERON =0, ELEVATOR, THROTTLE, RUDDER, AUX1, AUX2, AUX3, AUX4, AUX5, AUX6, AUX7, AUX8 }; #endif // Devo PPM and channels #if defined(DEVO7) #define PPM_MAX 2100 #define PPM_MIN 900 #define PPM_MAX_100 1920 #define PPM_MIN_100 1120 enum chan_order{ ELEVATOR=0, AILERON, THROTTLE, RUDDER, AUX1, AUX2, AUX3, AUX4, AUX5, AUX6, AUX7, AUX8 }; #endif //CC2500 RF module frequency adjustment, use in case you cannot bind with Frsky RX //Note: this is set via Option when serial protocol is used //values from 0-127 offset increase frequency, values from 255 to 127 decrease base frequency //uint8_t fine = 0x00; uint8_t fine = 0xd7; //* 215=-41 * // Multiprotocol module configuration ends here //****************************************************** //Global constants/variables uint32_t MProtocol_id;//tx id, uint32_t MProtocol_id_master; uint32_t Model_fixed_id=0; uint32_t fixed_id; uint8_t cyrfmfg_id[6];//for dsm2 and devo uint32_t blink=0; // uint16_t counter; uint8_t channel; uint8_t packet[40]; #define NUM_CHN 16 // Servo data uint16_t Servo_data[NUM_CHN]; // PPM variable volatile uint16_t PPM_data[NUM_CHN]; // NRF variables uint8_t rx_tx_addr[5]; uint8_t phase; uint16_t bind_counter; uint8_t bind_phase; uint8_t binding_idx; uint32_t packet_counter; uint16_t packet_period; uint8_t packet_count; uint8_t packet_sent; uint8_t packet_length; uint8_t hopping_frequency[23]; uint8_t *hopping_frequency_ptr; uint8_t hopping_frequency_no=0; uint8_t rf_ch_num; uint8_t throttle, rudder, elevator, aileron; uint8_t flags; // Mode_select variables uint8_t mode_select; uint8_t protocol_flags; // Serial variables #if defined(NUM_SERIAL_CH_8) //8 channels serial protocol #define RXBUFFER_SIZE 14 #else //16 channels serial protocol #define RXBUFFER_SIZE 25 #endif #define TXBUFFER_SIZE 12 volatile uint8_t rx_buff[RXBUFFER_SIZE]; volatile uint8_t rx_ok_buff[RXBUFFER_SIZE]; volatile uint8_t tx_buff[TXBUFFER_SIZE]; volatile uint8_t idx = 0; //Serial protocol uint8_t sub_protocol; uint8_t option; uint8_t cur_protocol[2]; uint8_t prev_protocol=0; // Telemetry #if defined(TELEMETRY) uint8_t pkt[27];//telemetry receiving packets uint8_t pktt[27];//telemetry receiving packets volatile uint8_t tx_head; volatile uint8_t tx_tail; uint8_t v_lipo; int16_t RSSI_dBm; //const uint8_t RSSI_offset=72;//69 71.72 values db uint8_t telemetry_link=0; #include "telemetry.h" #endif // Callback typedef uint16_t (*void_function_t) (void);//pointer to a function with no parameters which return an uint16_t integer void_function_t remote_callback = 0; void CheckTimer(uint16_t (*cb)(void)); // Init void setup() { // General pinout DDRD = (1<>2)&0x07 ) | ( (PINC<<3)&0x08) );//encoder dip switches 1,2,4,8=>B2,B3,B4,C0 //********************************** //mode_select=14; // here to test PPM //********************************** // Update LED LED_OFF; LED_SET_OUTPUT; // Read or create protocol id MProtocol_id=random_id(10,false); MProtocol_id_master=MProtocol_id; //Set power transmission flags POWER_FLAG_on; //By default high power for everything //Protocol and interrupts initialization if(mode_select != MODE_SERIAL) { // PPM cur_protocol[0]= PPM_prot[mode_select-1][0]; sub_protocol = PPM_prot[mode_select-1][1]; protocol_init(cur_protocol[0]); //Configure PPM interrupt EICRA |=(1< led on else if(blink micros()) { // Callback did not took more than requested time for next callback if(next_callback>32000) { // next_callback should not be more than 32767 so we will wait here... delayMicroseconds(next_callback-2000); cli(); // disable global int OCR1A=TCNT1+4000; sei(); // enable global int } else { cli(); // disable global int OCR1A+=next_callback*2; // set compare A for callback sei(); // enable global int } TIFR1=(1<32000) { // next_callback should not be more than 32767 so we will wait here... delayMicroseconds(next_callback-2000); next_callback=2000; } cli(); // disable global int OCR1A=TCNT1+next_callback*2; // set compare A for callback sei(); // enable global int TIFR1=(1<>4)& 0x07; //subprotocol no (0-7) bits 4-6 MProtocol_id=MProtocol_id_master+(rx_ok_buff[1]& 0x0F); //personalized RX bind + rx num // rx_num bits 0---3 } else if( ((rx_ok_buff[0]&0x80)!=0) && ((cur_protocol[0]&0x80)==0) ) // Bind flag has been set CHANGE_PROTOCOL_FLAG_on; //restart protocol with bind cur_protocol[0] = rx_ok_buff[0]; //store current protocol // decode channel values #if defined(NUM_SERIAL_CH_8) //8 channels serial protocol Servo_data[0]=rx_ok_buff[3]+((rx_ok_buff[11]&0xC0)<<2); Servo_data[1]=rx_ok_buff[4]+((rx_ok_buff[11]&0x30)<<4); Servo_data[2]=rx_ok_buff[5]+((rx_ok_buff[11]&0x0C)<<6); Servo_data[3]=rx_ok_buff[6]+((rx_ok_buff[11]&0x03)<<8); Servo_data[4]=rx_ok_buff[7]+((rx_ok_buff[12]&0xC0)<<2); Servo_data[5]=rx_ok_buff[8]+((rx_ok_buff[12]&0x30)<<4); Servo_data[6]=rx_ok_buff[9]+((rx_ok_buff[12]&0x0C)<<6); Servo_data[7]=rx_ok_buff[10]+((rx_ok_buff[12]&0x03)<<8); for(uint8_t i=0;i<8;i++) Servo_data[i]=((Servo_data[i]*5)>>2)+860; //range 860-2140; #else //16 channels serial protocol volatile uint8_t *p=rx_ok_buff+2; uint8_t dec=-3; for(uint8_t i=0;i=8) { dec-=8; p++; } p++; Servo_data[i]=((((*((uint32_t *)p))>>dec)&0x7FF)*5)/8+860; //value range 860<->2140 -125%<->+125% } #endif RX_FLAG_off; //data has been processed } void module_reset() { remote_callback = 0; switch(prev_protocol) { case MODE_FLYSKY: case MODE_HUBSAN: A7105_Reset(); break; case MODE_FRSKY: case MODE_FRSKYX: CC2500_Reset(); break; case MODE_HISKY: case MODE_V2X2: case MODE_YD717: case MODE_KN: case MODE_SYMAX: case MODE_SLT: case MODE_CX10: NRF24L01_Reset(); break; case MODE_DSM2: case MODE_DEVO: CYRF_Reset(); break; } } // Channel value is converted to 8bit values full scale uint8_t convert_channel_8b(uint8_t num) { return (uint8_t) (map(limit_channel_100(num),PPM_MIN_100,PPM_MAX_100,0,255)); } // Channel value is converted to 8bit values to provided values scale uint8_t convert_channel_8b_scale(uint8_t num,uint8_t min,uint8_t max) { return (uint8_t) (map(limit_channel_100(num),PPM_MIN_100,PPM_MAX_100,min,max)); } // Channel value is converted sign + magnitude 8bit values uint8_t convert_channel_s8b(uint8_t num) { uint8_t ch; ch = convert_channel_8b(num); return (ch < 128 ? 127-ch : ch); } // Channel value is converted to 10bit values uint16_t convert_channel_10b(uint8_t num) { return (uint16_t) (map(limit_channel_100(num),PPM_MIN_100,PPM_MAX_100,1,1023)); } // Channel value is multiplied by 1.5 uint16_t convert_channel_frsky(uint8_t num) { return Servo_data[num] + Servo_data[num]/2; } // Channel value is converted for HK310 void convert_channel_HK310(uint8_t num, uint8_t *low, uint8_t *high) { uint16_t temp=0xFFFF-(4*Servo_data[num])/3; *low=(uint8_t)(temp&0xFF); *high=(uint8_t)(temp>>8); } // Channel value is limited to PPM_100 uint16_t limit_channel_100(uint8_t ch) { if(Servo_data[ch]>PPM_MAX_100) return PPM_MAX_100; else if (Servo_data[ch]> 24) & 0xFF; rx_tx_addr[1] = (id >> 16) & 0xFF; rx_tx_addr[2] = (id >> 8) & 0xFF; rx_tx_addr[3] = (id >> 0) & 0xFF; rx_tx_addr[4] = 0xC1; // for YD717: always uses first data port } #if defined(TELEMETRY) void Serial_write(uint8_t data) { uint8_t t=tx_head; if(++t>=TXBUFFER_SIZE) t=0; tx_buff[t]=data; tx_head=t; UCSR0B |= (1< UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; //Set frame format to 8 data bits, no parity, 1 stop bit UCSR0C |= (1< UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; //Set frame format to 8 data bits, even parity, 2 stop bits UCSR0C |= (1<> 8) & 0xFF); txid[2] = ((id >> 16) & 0xFF); txid[3] = ((id >> 24) & 0xFF); eeprom_write_block((const void*)txid,(void*)adress,4); eeprom_write_byte((uint8_t*)(adress+10),0xf0);//write bind flag in eeprom. } return id; } /**************************/ /**************************/ /** Interrupt routines **/ /**************************/ /**************************/ ISR(INT1_vect) { // Interrupt on PPM pin static int8_t chan=-1; static uint16_t Prev_TCNT1=0; uint16_t Cur_TCNT1; Cur_TCNT1=TCNT1-Prev_TCNT1; // Capture current Timer1 value if(Cur_TCNT1<1000) chan=-1; // bad frame else if(Cur_TCNT1>4840) { chan=0; // start of frame PPM_FLAG_on; // full frame present (even at startup since PPM_data has been initialized) } else if(chan!=-1) // need to wait for start of frame { //servo values between 500us and 2420us will end up here PPM_data[chan] = Cur_TCNT1/2; if(PPM_data[chan]PPM_MAX) PPM_data[chan]=PPM_MAX; if(chan++>=NUM_CHN) chan=-1; // don't accept any new channels } Prev_TCNT1+=Cur_TCNT1; } #if defined(TELEMETRY) ISR(USART_UDRE_vect) { // Transmit interrupt uint8_t t = tx_tail; if(tx_head!=t) { if(++t>=TXBUFFER_SIZE)//head t=0; UDR0=tx_buff[t]; tx_tail=t; } if (t == tx_head) UCSR0B &= ~(1<>8) ^ rx_buff[idx++]) & 0xFF]); } else { // A frame has been received and needs to be checked before giving it to main TIMSK1 &=~(1<RXBUFFER_SIZE) { // A full frame has been received TIMSK1 &=~(1<