/********************************************************* 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, Hexfet, Goebish, Victzh and all protocol developers 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 "Multiprotocol.h" //#define DEBUG_TX //Multiprotocol module configuration file #include "_Config.h" //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]; uint8_t Servo_AUX; // Protocol variables uint8_t rx_tx_addr[5]; uint8_t phase; uint16_t bind_counter; uint8_t bind_phase; uint8_t binding_idx; 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; uint16_t crc; // uint16_t state; uint8_t len; uint8_t RX_num; #if defined(FRSKYX_CC2500_INO) || defined(SFHSS_CC2500_INO) uint8_t calData[48][3]; #endif //Channel mapping for protocols const uint8_t CH_AETR[]={AILERON, ELEVATOR, THROTTLE, RUDDER, AUX1, AUX2, AUX3, AUX4, AUX5, AUX6, AUX7, AUX8}; //const uint8_t CH_TAER[]={THROTTLE, AILERON, ELEVATOR, RUDDER, AUX1, AUX2, AUX3, AUX4, AUX5, AUX6, AUX7, AUX8}; //const uint8_t CH_RETA[]={RUDDER, ELEVATOR, THROTTLE, AILERON, AUX1, AUX2, AUX3, AUX4, AUX5, AUX6, AUX7, AUX8}; // Mode_select variables uint8_t mode_select; uint8_t protocol_flags=0,protocol_flags2=0; // PPM variable volatile uint16_t PPM_data[NUM_CHN]; // Serial variables #define RXBUFFER_SIZE 25 #define TXBUFFER_SIZE 20 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 #define MAX_PKT 27 uint8_t pkt[MAX_PKT];//telemetry receiving packets #if defined(TELEMETRY) #if defined DSM2_CYRF6936_INO #define DSM_TELEMETRY #endif #if defined FRSKYX_CC2500_INO #define SPORT_TELEMETRY #endif #if defined FRSKY_CC2500_INO #define HUB_TELEMETRY #endif uint8_t pktt[MAX_PKT];//telemetry receiving packets volatile uint8_t tx_head=0; volatile uint8_t tx_tail=0; uint8_t v_lipo; int16_t RSSI_dBm; //const uint8_t RSSI_offset=72;//69 71.72 values db uint8_t telemetry_link=0; uint8_t telemetry_counter=0; #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; static void CheckTimer(uint16_t (*cb)(void)); // Init void setup() { #ifdef XMEGA PORTD.OUTSET = 0x17 ; PORTD.DIRSET = 0xB2 ; PORTD.DIRCLR = 0x4D ; PORTD.PIN0CTRL = 0x18 ; PORTD.PIN2CTRL = 0x18 ; PORTE.DIRSET = 0x01 ; PORTE.DIRCLR = 0x02 ; PORTE.OUTSET = 0x01 ; for ( uint8_t count = 0 ; count < 20 ; count += 1 ) asm("nop") ; PORTE.OUTCLR = 0x01 ; #else // General pinout DDRD = (1<B2,B3,B4,C0 mode_select = MODE_SERIAL ; #else mode_select=0x0F - ( ( (PINB>>2)&0x07 ) | ( (PINC<<3)&0x08) );//encoder dip switches 1,2,4,8=>B2,B3,B4,C0 #endif //********************************** //mode_select=1; // here to test PPM //********************************** // Update LED LED_OFF; LED_SET_OUTPUT; // Read or create protocol id MProtocol_id_master=random_id(10,false); //Init RF modules #ifdef CC2500_INSTALLED CC2500_Reset(); #endif #ifdef A7105_INSTALLED A7105_Reset(); #endif #ifdef CYRF6936_INSTALLED CYRF_Reset(); #endif #ifdef NFR24L01_INSTALLED NRF24L01_Reset(); #endif //Protocol and interrupts initialization if(mode_select != MODE_SERIAL) { // PPM mode_select--; cur_protocol[0] = PPM_prot[mode_select].protocol; sub_protocol = PPM_prot[mode_select].sub_proto; RX_num = PPM_prot[mode_select].rx_num; MProtocol_id = RX_num + MProtocol_id_master; option = PPM_prot[mode_select].option; if(PPM_prot[mode_select].power) POWER_FLAG_on; if(PPM_prot[mode_select].autobind) AUTOBIND_FLAG_on; mode_select++; protocol_init(); #ifndef XMEGA //Configure PPM interrupt EICRA |=(1<PPM_SWITCH) Servo_AUX|=1< led on else blink+=BLINK_BIND_TIME; //blink fastly during binding LED_TOGGLE; } } // Protocol scheduler static void CheckTimer(uint16_t (*cb)(void)) { uint16_t next_callback,diff; #ifdef XMEGA if( (TCC1.INTFLAGS & TC1_CCAIF_bm) != 0) { cli(); // disable global int TCC1.CCA = TCC1.CNT ; // Callback should already have been called... Use "now" as new sync point. sei(); // enable global int } else while((TCC1.INTFLAGS & TC1_CCAIF_bm) == 0); // wait before callback #else if( (TIFR1 & (1<4000) { // start to wait here as much as we can... next_callback=next_callback-2000; cli(); // disable global int #ifdef XMEGA TCC1.CCA +=2000*2; // set compare A for callback TCC1.INTFLAGS = TC1_CCAIF_bm ; // clear compare A=callback flag sei(); // enable global int while((TCC1.INTFLAGS & TC1_CCAIF_bm) == 0); // wait 2ms... #else OCR1A+=2000*2; // set compare A for callback 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 #ifdef XMEGA TCC1.CCA = TCC1.CNT + next_callback*2; // set compare A for callback sei(); // enable global int TCC1.INTFLAGS = TC1_CCAIF_bm ; // clear compare A flag #else 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 RX_num=rx_ok_buff[1]& 0x0F; MProtocol_id=MProtocol_id_master+RX_num; //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 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% } RX_FLAG_off; //data has been processed } static void module_reset() { if(remote_callback) { // previous protocol loaded remote_callback = 0; switch(prev_protocol) { case MODE_FLYSKY: case MODE_HUBSAN: A7105_Reset(); break; case MODE_FRSKY: case MODE_FRSKYX: case MODE_SFHSS: CC2500_Reset(); break; case MODE_DSM2: case MODE_DEVO: case MODE_J6PRO: CYRF_Reset(); break; default: // MODE_HISKY, MODE_V2X2, MODE_YD717, MODE_KN, MODE_SYMAX, MODE_SLT, MODE_CX10, MODE_CG023, MODE_BAYANG, MODE_ESKY, MODE_MT99XX, MODE_MJXQ, MODE_SHENQI, MODE_FY326, MODE_FQ777 NRF24L01_Reset(); break; } } } int16_t map( int16_t x, int16_t in_min, int16_t in_max, int16_t out_min, int16_t out_max) { // return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; long y ; x -= in_min ; y = out_max - out_min ; y *= x ; x = y / (in_max - in_min) ; return x + out_min ; } // 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]=TXBUFFER_SIZE) tx_head=0; tx_buff[tx_head]=data; #ifdef XMEGA USARTC0.CTRLA = (USARTC0.CTRLA & 0xFC) | 0x01 ; #else UCSR0B |= (1< UBRR0H = UBRRH_VALUE; UBRR0L = UBRRL_VALUE; UCSR0A = 0 ; // Clear X2 bit //Set frame format to 8 data bits, even parity, 2 stop bits UCSR0C = (1<> 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 } static uint32_t random_id(uint16_t adress, uint8_t create_new) { uint32_t id; uint8_t txid[4]; if (eeprom_read_byte((uint8_t*)(adress+10))==0xf0 && !create_new) { // TXID exists in EEPROM eeprom_read_block((void*)txid,(const void*)adress,4); id=(txid[0] | ((uint32_t)txid[1]<<8) | ((uint32_t)txid[2]<<16) | ((uint32_t)txid[3]<<24)); } else { // if not generate a random ID randomSeed((uint32_t)analogRead(A6)<<10|analogRead(A7));//seed // id = random(0xfefefefe) + ((uint32_t)random(0xfefefefe) << 16); txid[0]= (id &0xFF); txid[1] = ((id >> 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; } /********************/ /** SPI routines **/ /********************/ void SPI_Write(uint8_t command) { uint8_t n=8; SCK_off;//SCK start low SDI_off; do { if(command&0x80) SDI_on; else SDI_off; SCK_on; command = command << 1; SCK_off; } while(--n) ; SDI_on; } uint8_t SPI_Read(void) { uint8_t result; uint8_t i; for(i=0;i<8;i++) { result=result<<1; if(SDO_1) result |= 0x01; SCK_on; NOP(); SCK_off; } return result; } /************************************/ /** Arduino replacement routines **/ /************************************/ // replacement millis() and micros() // These work polled, no interrupts // micros() MUST be called at least once every 32 milliseconds uint16_t MillisPrecount ; uint16_t lastTimerValue ; uint32_t TotalMicros ; uint32_t TotalMillis ; uint8_t Correction ; uint32_t micros() { uint16_t elapsed ; uint8_t millisToAdd ; uint8_t oldSREG = SREG ; cli() ; uint16_t time = TCNT1 ; // Read timer 1 SREG = oldSREG ; elapsed = time - lastTimerValue ; elapsed += Correction ; Correction = elapsed & 0x01 ; elapsed >>= 1 ; uint32_t ltime = TotalMicros ; ltime += elapsed ; cli() ; TotalMicros = ltime ; // Done this way for RPM to work correctly lastTimerValue = time ; SREG = oldSREG ; // Still valid from above elapsed += MillisPrecount; millisToAdd = 0 ; if ( elapsed > 15999 ) { millisToAdd = 16 ; elapsed -= 16000 ; } if ( elapsed > 7999 ) { millisToAdd += 8 ; elapsed -= 8000 ; } if ( elapsed > 3999 ) { millisToAdd += 4 ; elapsed -= 4000 ; } if ( elapsed > 1999 ) { millisToAdd += 2 ; elapsed -= 2000 ; } if ( elapsed > 999 ) { millisToAdd += 1 ; elapsed -= 1000 ; } TotalMillis += millisToAdd ; MillisPrecount = elapsed ; return TotalMicros ; } uint32_t millis() { micros() ; return TotalMillis ; } void delay(unsigned long ms) { uint16_t start = (uint16_t)micros(); uint16_t lms = ms ; while (lms > 0) { if (((uint16_t)micros() - start) >= 1000) { lms--; start += 1000; } } } /* Delay for the given number of microseconds. Assumes a 8 or 16 MHz clock. */ void delayMicroseconds(unsigned int us) { // calling avrlib's delay_us() function with low values (e.g. 1 or // 2 microseconds) gives delays longer than desired. //delay_us(us); // for the 16 MHz clock on most Arduino boards // for a one-microsecond delay, simply return. the overhead // of the function call yields a delay of approximately 1 1/8 us. if (--us == 0) return; // the following loop takes a quarter of a microsecond (4 cycles) // per iteration, so execute it four times for each microsecond of // delay requested. us <<= 2; // account for the time taken in the preceeding commands. us -= 2; // busy wait __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); } void init() { // this needs to be called before setup() or some functions won't // work there sei(); } /**************************/ /**************************/ /** Interrupt routines **/ /**************************/ /**************************/ //PPM #ifdef XMEGA ISR(PORTD_INT0_vect) #else ISR(INT1_vect) #endif { // Interrupt on PPM pin static int8_t chan=-1; static uint16_t Prev_TCNT1=0; uint16_t Cur_TCNT1; #ifdef XMEGA Cur_TCNT1 = TCC1.CNT - Prev_TCNT1 ; // Capture current Timer1 value #else Cur_TCNT1=TCNT1-Prev_TCNT1; // Capture current Timer1 value #endif 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 uint16_t a = Cur_TCNT1>>1; if(aPPM_MAX) a=PPM_MAX; PPM_data[chan]=a; if(chan++>=NUM_CHN) chan=-1; // don't accept any new channels } Prev_TCNT1+=Cur_TCNT1; } //Serial RX #ifdef XMEGA ISR(USARTC0_RXC_vect) #else ISR(USART_RX_vect) #endif { // RX interrupt #ifdef XMEGA if((USARTC0.STATUS & 0x1C)==0) // Check frame error, data overrun and parity error #else if((UCSR0A&0x1C)==0) // Check frame error, data overrun and parity error #endif { // received byte is ok to process if(idx==0) { // Let's try to sync at this point #ifdef XMEGA if(USARTC0.DATA==0x55) // If 1st byte is 0x55 it looks ok #else if(UDR0==0x55) // If 1st byte is 0x55 it looks ok #endif { idx++; #ifdef XMEGA TCC1.CCB = TCC1.CNT+(6500L) ; // Full message should be received within timer of 3250us TCC1.INTFLAGS = TC1_CCBIF_bm ; // clear OCR1B match flag TCC1.INTCTRLB = (TCC1.INTCTRLB & 0xF3) | 0x04 ; // enable interrupt on compare B match #else OCR1B=TCNT1+6500L; // Full message should be received within timer of 3250us TIFR1=(1<RXBUFFER_SIZE) { // A full frame has been received #ifdef XMEGA TCC1.INTCTRLB &=0xF3; // disable interrupt on compare B match #else TIMSK1 &=~(1<=TXBUFFER_SIZE)//head tx_tail=0; #ifdef XMEGA USARTC0.DATA = tx_buff[tx_tail] ; #else UDR0=tx_buff[tx_tail]; #endif } if (tx_tail == tx_head) #ifdef XMEGA USARTC0.CTRLA &= ~0x03 ; #else UCSR0B &= ~(1<