diff --git a/Multiprotocol/Common.ino b/Multiprotocol/Common.ino index 9c819b0..292c1f0 100644 --- a/Multiprotocol/Common.ino +++ b/Multiprotocol/Common.ino @@ -55,6 +55,12 @@ void convert_channel_HK310(uint8_t num, uint8_t *low, uint8_t *high) *high=(uint8_t)(temp>>8); } +// Channel value is converted to 10bit values +uint16_t convert_channel_16b(uint8_t num, int16_t out_min, int16_t out_max) +{ + return (uint16_t) (map(limit_channel_100(num),servo_min_100,servo_max_100,out_min,out_max)); +} + // Channel value is limited to PPM_100 uint16_t limit_channel_100(uint8_t ch) { diff --git a/Multiprotocol/Multi.txt b/Multiprotocol/Multi.txt index 7c5bd88..01361a7 100644 --- a/Multiprotocol/Multi.txt +++ b/Multiprotocol/Multi.txt @@ -28,3 +28,4 @@ 28,AFHD2SA,PWM_IBUS,PPM_IBUS,PWM_SBUS,PPM_SBUS 29,Q2X2,Q222,Q242,Q282 30,WK2x01,WK2801,WK2401,W6_5_1,W6_6_1,W6_HEL,W6_HEL_I +31,Q303,Q303,CX35,CX10D,CX10WD diff --git a/Multiprotocol/Multiprotocol.h b/Multiprotocol/Multiprotocol.h index f0c4bb7..e44c8e2 100644 --- a/Multiprotocol/Multiprotocol.h +++ b/Multiprotocol/Multiprotocol.h @@ -19,7 +19,7 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 1 #define VERSION_REVISION 6 -#define VERSION_PATCH_LEVEL 4 +#define VERSION_PATCH_LEVEL 5 //****************** // Protocols @@ -57,6 +57,7 @@ enum PROTOCOLS MODE_AFHDS2A = 28, // =>A7105 MODE_Q2X2 = 29, // =>NRF24L01, extension of CX-10 protocol MODE_WK2x01 = 30, // =>CYRF6936 + MODE_Q303 = 31, // =>NRF24L01 }; enum Flysky @@ -184,6 +185,13 @@ enum WK2x01 W6_HEL = 4, W6_HEL_I= 5, }; +enum Q303 +{ + Q303 = 0, + CX35 = 1, + CX10D = 2, + CX10WD = 3, +}; #define NONE 0 #define P_HIGH 1 @@ -459,6 +467,7 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p -- AFHDS2A 28 Q2X2 29 WK2x01 30 + Q303 31 BindBit=> 0x80 1=Bind/0=No AutoBindBit=> 0x40 1=Yes /0=No RangeCheck=> 0x20 1=Yes /0=No @@ -553,6 +562,11 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p -- W6_6_1 3 W6_HEL 4 W6_HEL_I 5 + sub_protocol==Q303 + Q303 0 + CX35 1 + CX10D 2 + CX10WD 3 Power value => 0x80 0=High/1=Low Stream[3] = option_protocol; diff --git a/Multiprotocol/Multiprotocol.ino b/Multiprotocol/Multiprotocol.ino index acfd307..8bed2d8 100644 --- a/Multiprotocol/Multiprotocol.ino +++ b/Multiprotocol/Multiprotocol.ino @@ -884,6 +884,12 @@ static void protocol_init() remote_callback = HONTAI_callback; break; #endif + #if defined(Q303_NRF24L01_INO) + case MODE_Q303: + next_callback=initQ303(); + remote_callback = Q303_callback; + break; + #endif #endif } diff --git a/Multiprotocol/Q303_nrf24l01.ino b/Multiprotocol/Q303_nrf24l01.ino new file mode 100644 index 0000000..cd53582 --- /dev/null +++ b/Multiprotocol/Q303_nrf24l01.ino @@ -0,0 +1,409 @@ +/* + 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 . + */ + +#if defined(Q303_NRF24L01_INO) + +#include "iface_nrf24l01.h" + +#define Q303_BIND_COUNT 1500 +#define Q303_INITIAL_WAIT 500 +#define Q303_RF_BIND_CHANNEL 0x02 + +#define Q303_BTN_TAKEOFF 1 +#define Q303_BTN_DESCEND 2 +#define Q303_BTN_SNAPSHOT 4 +#define Q303_BTN_VIDEO 8 +#define Q303_BTN_RTH 16 +#define Q303_BTN_VTX 32 + +static uint8_t __attribute__((unused)) cx10wd_getButtons() +{ + #define CX10WD_FLAG_LAND 0x20 + #define CX10D_FLAG_LAND 0x80 + #define CX10WD_FLAG_TAKEOFF 0x40 + + static uint8_t Q303_BTN_state; + static uint8_t command; + + // startup + if(packet_count < 50) + { + Q303_BTN_state = 0; + command = 0; + packet_count++; + } + // auto land + else if(GET_FLAG(!Servo_AUX1,1) && !(Q303_BTN_state & Q303_BTN_DESCEND)) + { + Q303_BTN_state |= Q303_BTN_DESCEND; + Q303_BTN_state &= ~Q303_BTN_TAKEOFF; + switch(sub_protocol) + { + case CX10WD: + command ^= CX10WD_FLAG_LAND; + break; + case CX10D: + command ^= CX10D_FLAG_LAND; + break; + } + } + // auto take off + else if(GET_FLAG(Servo_AUX1,1) && !(Q303_BTN_state & Q303_BTN_TAKEOFF)) + { + Q303_BTN_state |= Q303_BTN_TAKEOFF; + Q303_BTN_state &= ~Q303_BTN_DESCEND; + command ^= CX10WD_FLAG_TAKEOFF; + } + + return command; +} + +static uint8_t __attribute__((unused)) cx35_lastButton() +{ + #define CX35_CMD_RATE 0x09 + #define CX35_CMD_TAKEOFF 0x0e + #define CX35_CMD_DESCEND 0x0f + #define CX35_CMD_SNAPSHOT 0x0b + #define CX35_CMD_VIDEO 0x0c + #define CX35_CMD_RTH 0x11 + #define CX35_CMD_VTX 0x10 + + static uint8_t Q303_BTN_state; + static uint8_t command; + // simulate 2 keypress on rate button just after bind + if(packet_count < 50) + { + Q303_BTN_state = 0; + packet_count++; + command = 0x00; // startup + } + else if(packet_count < 150) + { + packet_count++; + command = CX35_CMD_RATE; // 1st keypress + } + else if(packet_count < 250) + { + packet_count++; + command |= 0x20; // 2nd keypress + } + // descend + else if(!(GET_FLAG(Servo_AUX1, 1)) && !(Q303_BTN_state & Q303_BTN_DESCEND)) + { + Q303_BTN_state |= Q303_BTN_DESCEND; + Q303_BTN_state &= ~Q303_BTN_TAKEOFF; + command = CX35_CMD_DESCEND; + } + // take off + else if(GET_FLAG(Servo_AUX1,1) && !(Q303_BTN_state & Q303_BTN_TAKEOFF)) + { + Q303_BTN_state |= Q303_BTN_TAKEOFF; + Q303_BTN_state &= ~Q303_BTN_DESCEND; + command = CX35_CMD_TAKEOFF; + } + // RTH + else if(GET_FLAG(Servo_AUX6,1) && !(Q303_BTN_state & Q303_BTN_RTH)) + { + Q303_BTN_state |= Q303_BTN_RTH; + if(command == CX35_CMD_RTH) + command |= 0x20; + else + command = CX35_CMD_RTH; + } + else if(!(GET_FLAG(Servo_AUX6,1)) && (Q303_BTN_state & Q303_BTN_RTH)) + { + Q303_BTN_state &= ~Q303_BTN_RTH; + if(command == CX35_CMD_RTH) + command |= 0x20; + else + command = CX35_CMD_RTH; + } + // video + else if(GET_FLAG(Servo_AUX4,1) && !(Q303_BTN_state & Q303_BTN_VIDEO)) + { + Q303_BTN_state |= Q303_BTN_VIDEO; + if(command == CX35_CMD_VIDEO) + command |= 0x20; + else + command = CX35_CMD_VIDEO; + } + else if(!(GET_FLAG(Servo_AUX4,1)) && (Q303_BTN_state & Q303_BTN_VIDEO)) + { + Q303_BTN_state &= ~Q303_BTN_VIDEO; + if(command == CX35_CMD_VIDEO) + command |= 0x20; + else + command = CX35_CMD_VIDEO; + } + // snapshot + else if(GET_FLAG(Servo_AUX3,1) && !(Q303_BTN_state & Q303_BTN_SNAPSHOT)) + { + Q303_BTN_state |= Q303_BTN_SNAPSHOT; + if(command == CX35_CMD_SNAPSHOT) + command |= 0x20; + else + command = CX35_CMD_SNAPSHOT; + } + // vtx channel + else if(GET_FLAG(Servo_AUX2,1) && !(Q303_BTN_state & Q303_BTN_VTX)) + { + Q303_BTN_state |= Q303_BTN_VTX; + if(command == CX35_CMD_VTX) + command |= 0x20; + else + command = CX35_CMD_VTX; + } + + if(!(GET_FLAG(Servo_AUX3,1))) + Q303_BTN_state &= ~Q303_BTN_SNAPSHOT; + if(!(GET_FLAG(Servo_AUX2,1))) + Q303_BTN_state &= ~Q303_BTN_VTX; + + return command; +} + +static void __attribute__((unused)) Q303_send_packet(uint8_t bind) +{ + uint16_t aileron, elevator, throttle, rudder, slider; + if(bind) + { + packet[0] = 0xaa; + memcpy(&packet[1], rx_tx_addr + 1, 4); + memset(&packet[5], 0, packet_length-5); + } + else + { + packet[0] = 0x55; + // sticks + switch(sub_protocol) + { + case Q303: + case CX35: + aileron = convert_channel_16b(AILERON, 1000, 0); + elevator = convert_channel_16b(ELEVATOR, 1000, 0); + throttle = convert_channel_16b(THROTTLE, 0, 1000); + rudder = convert_channel_16b(RUDDER, 0, 1000); + if(sub_protocol == CX35) + aileron = 1000 - aileron; + packet[1] = aileron >> 2; // 8 bits + packet[2] = (aileron & 0x03) << 6 // 2 bits + | (elevator >> 4); // 6 bits + packet[3] = (elevator & 0x0f) << 4 // 4 bits + | (throttle >> 6); // 4 bits + packet[4] = (throttle & 0x3f) << 2 // 6 bits + | (rudder >> 8); // 2 bits + packet[5] = rudder & 0xff; // 8 bits + break; + case CX10D: + case CX10WD: + aileron = convert_channel_16b(AILERON, 1000, 2000); + elevator = convert_channel_16b(ELEVATOR, 2000, 1000); + throttle = convert_channel_16b(THROTTLE, 1000, 2000); + rudder = convert_channel_16b(RUDDER, 2000, 1000); + packet[1] = aileron & 0xff; + packet[2] = aileron >> 8; + packet[3] = elevator & 0xff; + packet[4] = elevator >> 8; + packet[5] = throttle & 0xff; + packet[6] = throttle >> 8; + packet[7] = rudder & 0xff; + packet[8] = rudder >> 8; + break; + } + + // buttons + switch(sub_protocol) + { + case Q303: + packet[6] = 0x10; // trim(s) ? + packet[7] = 0x10; // trim(s) ? + packet[8] = 0x03 // high rate (0-3) + | GET_FLAG(Servo_AUX1, 0x40) + | GET_FLAG(Servo_AUX6, 0x80); + packet[9] = 0x40 // always set + | GET_FLAG(Servo_AUX5,0x08) + | GET_FLAG(Servo_AUX2, 0x80) + | GET_FLAG(Servo_AUX3,0x10) + | GET_FLAG(Servo_AUX4, 0x01); + if(Servo_data[AUX7] < PPM_MIN_COMMAND) + packet[9] |= 0x04; // gimbal down + else if(Servo_data[AUX7] > PPM_MAX_COMMAND) + packet[9] |= 0x20; // gimbal up + break; + + case CX35: + slider = convert_channel_16b(AUX7, 731, 342); + packet[6] = slider >> 2; + packet[7] = ((slider & 3) << 6) + | 0x3e; // ?? 6 bit left (always 111110 ?) + packet[8] = 0x80; // always set + packet[9] = cx35_lastButton(); + break; + + case CX10D: + packet[8] |= GET_FLAG(Servo_AUX2, 0x10); + packet[9] = 0x02; // rate (0-2) + packet[10]= cx10wd_getButtons(); // auto land / take off management + break; + + case CX10WD: + packet[8] |= GET_FLAG(Servo_AUX2, 0x10); + packet[9] = 0x02 // rate (0-2) + | cx10wd_getButtons(); // auto land / take off management + packet[10] = 0x00; + break; + } + } + + // Power on, TX mode, CRC enabled + XN297_Configure(_BV(NRF24L01_00_EN_CRC) | _BV(NRF24L01_00_CRCO) | _BV(NRF24L01_00_PWR_UP)); + + NRF24L01_WriteReg(NRF24L01_05_RF_CH, bind ? Q303_RF_BIND_CHANNEL : hopping_frequency[hopping_frequency_no++]); + hopping_frequency_no %= rf_ch_num; + + NRF24L01_WriteReg(NRF24L01_07_STATUS, 0x70); + NRF24L01_FlushTx(); + + XN297_WritePayload(packet, packet_length); + + NRF24L01_SetPower(); // Set tx_power +} + +static void __attribute__((unused)) Q303_init() +{ + const uint8_t bind_address[] = {0xcc,0xcc,0xcc,0xcc,0xcc}; + + NRF24L01_Initialize(); + NRF24L01_SetTxRxMode(TX_EN); + switch(sub_protocol) + { + case CX35: + case CX10D: + case CX10WD: + XN297_SetScrambledMode(XN297_SCRAMBLED); + NRF24L01_SetBitrate(NRF24L01_BR_1M); + break; + case Q303: + XN297_SetScrambledMode(XN297_UNSCRAMBLED); + NRF24L01_SetBitrate(NRF24L01_BR_250K); + break; + } + XN297_SetTXAddr(bind_address, 5); + NRF24L01_FlushTx(); + NRF24L01_FlushRx(); + NRF24L01_WriteReg(NRF24L01_07_STATUS, 0x70); // Clear data ready, data sent, and retransmit + NRF24L01_WriteReg(NRF24L01_01_EN_AA, 0x00); // No Auto Acknowldgement on all data pipes + NRF24L01_WriteReg(NRF24L01_02_EN_RXADDR, 0x01); + NRF24L01_WriteReg(NRF24L01_03_SETUP_AW, 0x03); + NRF24L01_WriteReg(NRF24L01_04_SETUP_RETR, 0x00); // no retransmits + NRF24L01_SetPower(); + NRF24L01_Activate(0x73); // Activate feature register + NRF24L01_WriteReg(NRF24L01_1C_DYNPD, 0x00); // Disable dynamic payload length on all pipes + NRF24L01_WriteReg(NRF24L01_1D_FEATURE, 0x01); // Set feature bits on + NRF24L01_Activate(0x73); +} + +static void __attribute__((unused)) Q303_initialize_txid() +{ + uint8_t i,offset; + + rx_tx_addr[0] = 0x55; + + switch(sub_protocol) + { + case Q303: + case CX10WD: + offset = rx_tx_addr[1] & 3; + for(i=0; i<4; i++) + hopping_frequency[i] = 0x46 + i*2 + offset; + break; + case CX35: + case CX10D: + // not thoroughly figured out rx_tx_addr/channels mapping yet + // for now 5 msb of rx_tx_addr[1] must be cleared + rx_tx_addr[1] &= 7; + offset = 6+((rx_tx_addr[1] & 7)*3); + hopping_frequency[0] = 0x14; // works only if rx_tx_addr[1] < 8 + for(i=1; i<16; i++) + { + hopping_frequency[i] = hopping_frequency[i-1] + offset; + if(hopping_frequency[i] > 0x41) + hopping_frequency[i] -= 0x33; + if(hopping_frequency[i] < 0x14) + hopping_frequency[i] += offset; + } + // CX35 tx uses only 4 of those channels (#0,3,6,9) + if(sub_protocol == CX35) + for(i=0; i<4; i++) + hopping_frequency[i] = hopping_frequency[i*3]; + break; + } +} + +uint16_t Q303_callback() +{ + if(IS_BIND_DONE_on) + Q303_send_packet(0); + else + { + if (bind_counter == 0) + { + XN297_SetTXAddr(rx_tx_addr, 5); + packet_count = 0; + BIND_DONE; + } + else + { + Q303_send_packet(1); + bind_counter--; + } + } + return packet_period; +} + +uint16_t initQ303() +{ + Q303_initialize_txid(); + Q303_init(); + bind_counter = Q303_BIND_COUNT; + switch(sub_protocol) + { + case Q303: + packet_period = 1500; + packet_length = 10; + rf_ch_num = 4; + break; + case CX35: + packet_period = 3000; + packet_length = 10; + rf_ch_num = 4; + break; + case CX10D: + packet_period = 3000; + packet_length = 11; + rf_ch_num = 16; + break; + case CX10WD: + packet_period = 3000; + packet_length = 11; + rf_ch_num = 4; + break; + } + hopping_frequency_no = 0; + BIND_IN_PROGRESS; // autobind protocol + return Q303_INITIAL_WAIT; +} + +#endif diff --git a/Multiprotocol/Validate.h b/Multiprotocol/Validate.h index 27bf510..5431bd5 100644 --- a/Multiprotocol/Validate.h +++ b/Multiprotocol/Validate.h @@ -65,6 +65,7 @@ #undef FQ777_NRF24L01_INO #undef ASSAN_NRF24L01_INO #undef HONTAI_NRF24L01_INO + #undef Q303_NRF24L01_INO #endif //Make sure telemetry is selected correctly diff --git a/Multiprotocol/_Config.h b/Multiprotocol/_Config.h index 8951480..3d8d0e3 100644 --- a/Multiprotocol/_Config.h +++ b/Multiprotocol/_Config.h @@ -102,7 +102,7 @@ #define FQ777_NRF24L01_INO #define ASSAN_NRF24L01_INO #define HONTAI_NRF24L01_INO - +#define Q303_NRF24L01_INO /**************************/ /*** TELEMETRY SETTINGS ***/ @@ -305,6 +305,11 @@ const PPM_Parameters PPM_prot[15]= { W6_6_1 W6_HEL W6_HEL_I + MODE_Q303 + Q303 + CX35 + CX10D + CX10WD */ // RX_Num is used for model match. Using RX_Num values different for each receiver will prevent starting a model with the false config loaded...