Last active
September 25, 2022 20:59
-
-
Save goebish/f8982353c34f2b71ffe8 to your computer and use it in GitHub Desktop.
Cheerson CX-10 Tx Arduino Code (blue PCB)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Update : a xn297 is not required anymore, it can be emulated with a nRF24l01 : | |
// https://gist.github.com/goebish/ab4bc5f2dfb1ac404d3e | |
// ************************************************************** | |
// ****************** CX-10 Tx Code (blue PCB) ****************** | |
// by goebish on RCgroups.com | |
// based on green pcb cx-10 TX code by closedsink on RCgroups.com | |
// based largely on flysky code by midelic on RCgroups.com | |
// Thanks to PhracturedBlue, hexfet, ThierryRC | |
// Hasi for his arduino PPM decoder | |
// ************************************************************** | |
// Hardware any M8/M168/M328 setup(arduino promini,duemilanove...as) | |
// !!! take care when flashing the AVR, the XN297 RF chip supports 3.6V max !!! | |
// !!! use a 3.3V programmer or disconnect the RF module when flashing. !!! | |
// !!! XN297 inputs are not 5 Volt tolerant, use a 3.3V MCU or logic level !!! | |
// !!! adapter or voltage divider on MOSI/SCK/CS/CE if using 5V MCU !!! | |
// DIY CX-10 RF module - XN297 and circuitry harvested from *BLUE* CX-10 controller | |
// pinout: http://imgur.com/a/unff4 | |
// XN297 datasheet: http://www.foxware-cn.com/UploadFile/20140808155134.pdf | |
//Spi Comm.pins with XN297/PPM, direct port access, do not change | |
#define PPM_pin 2 // PPM in | |
#define MOSI_pin 5 // MOSI-D5 | |
#define SCK_pin 4 // SCK-D4 | |
#define CS_pin 6 // CS-D6 | |
#define CE_pin 3 // CE-D3 | |
#define MISO_pin 7 // MISO-D7 | |
//--------------------------------- | |
// spi outputs | |
#define CS_on PORTD |= 0x40 // PORTD6 | |
#define CS_off PORTD &= 0xBF // PORTD6 | |
#define CE_on PORTD |= 0x08 // PORTD3 | |
#define CE_off PORTD &= 0xF7 // PORTD3 | |
// | |
#define SCK_on PORTD |= 0x10 // PORTD4 | |
#define SCK_off PORTD &= 0xEF // PORTD4 | |
#define MOSI_on PORTD |= 0x20 // PORTD5 | |
#define MOSI_off PORTD &= 0xDF // PORTD5 | |
// spi input | |
#define MISO_on (PIND & 0x80) // PORTD7 | |
// | |
#define NOP() __asm__ __volatile__("nop") | |
#define PACKET_LENGTH 19 | |
#define PACKET_INTERVAL 6 // interval of time between start of 2 packets, in ms | |
#define PPM_MIN 1000 | |
#define PPM_MAX 2000 | |
// PPM stream settings | |
#define CHANNELS 6 | |
enum chan_order{ // TAER -> Spektrum/FrSky chan order | |
THROTTLE, | |
AILERON, | |
ELEVATOR, | |
RUDDER, | |
AUX1, // mode | |
AUX2, // flip control | |
}; | |
//########## Variables ################# | |
static uint8_t aid[4]={0xFF,0xFF,0xFF,0xFF}; // aircraft ID | |
static uint8_t txid[4]; // transmitter ID | |
static uint8_t freq[4]; // frequency hopping table | |
static uint8_t packet[PACKET_LENGTH]; | |
volatile uint16_t Servo_data[CHANNELS] = {0,}; | |
int ledPin = 13; | |
void setup() { | |
randomSeed((analogRead(A0) & 0x1F) | (analogRead(A1) << 5)); | |
for(uint8_t i=0;i<4;i++) { | |
txid[i] = random(); | |
} | |
txid[1] %= 0x30; | |
freq[0] = (txid[0] & 0x0F) + 0x03; | |
freq[1] = (txid[0] >> 4) + 0x16; | |
freq[2] = (txid[1] & 0x0F) + 0x2D; | |
freq[3] = (txid[1] >> 4) + 0x40; | |
pinMode(ledPin, OUTPUT); | |
//PPM input from transmitter port | |
pinMode(PPM_pin, INPUT); | |
//RF module pins | |
pinMode(MOSI_pin, OUTPUT); | |
pinMode(SCK_pin, OUTPUT); | |
pinMode(CS_pin, OUTPUT); | |
pinMode(CE_pin, OUTPUT); | |
pinMode(MISO_pin, INPUT); | |
digitalWrite(ledPin, LOW);//start LED off | |
CS_on;//start CS high | |
CE_on;//start CE high | |
MOSI_on;//start MOSI high | |
SCK_on;//start sck high | |
delay(70);//wait 70ms | |
CS_off;//start CS low | |
CE_off;//start CE low | |
MOSI_off;//start MOSI low | |
SCK_off;//start sck low | |
delay(100); | |
CS_on;//start CS high | |
delay(10); | |
//############ INIT1 ############## | |
CS_off; | |
_spi_write(0x3f); // Set Baseband parameters (debug registers) - BB_CAL | |
_spi_write(0x4c); | |
_spi_write(0x84); | |
_spi_write(0x67); | |
_spi_write(0x9c); | |
_spi_write(0x20); | |
CS_on; | |
delayMicroseconds(5); | |
CS_off; | |
_spi_write(0x3e); // Set RF parameters (debug registers) - RF_CAL | |
_spi_write(0xc9); | |
_spi_write(0x9a); | |
_spi_write(0xb0); | |
_spi_write(0x61); | |
_spi_write(0xbb); | |
_spi_write(0xab); | |
_spi_write(0x9c); | |
CS_on; | |
delayMicroseconds(5); | |
CS_off; | |
_spi_write(0x39); // Set Demodulator parameters (debug registers) - DEMOD_CAL | |
_spi_write(0x0b); | |
_spi_write(0xdf); | |
_spi_write(0xc4); | |
_spi_write(0xa7); | |
_spi_write(0x03); | |
CS_on; | |
delayMicroseconds(5); | |
CS_off; | |
_spi_write(0x30); // Set TX address 0xCCCCCCCC | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
CS_on; | |
delayMicroseconds(5); | |
CS_off; | |
_spi_write(0x2a); // Set RX pipe 0 address 0xCCCCCCCC | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
_spi_write(0xcc); | |
CS_on; | |
delayMicroseconds(5); | |
_spi_write_address(0xe1, 0x00); // Clear TX buffer | |
_spi_write_address(0xe2, 0x00); // Clear RX buffer | |
_spi_write_address(0x27, 0x70); // Clear interrupts | |
_spi_write_address(0x21, 0x00); // No auto-acknowledge | |
_spi_write_address(0x22, 0x01); // Enable only data pipe 0 | |
_spi_write_address(0x23, 0x03); // Set 5 byte rx/tx address field width | |
_spi_write_address(0x25, 0x02); // Set channel frequency | |
_spi_write_address(0x24, 0x00); // No auto-retransmit | |
_spi_write_address(0x31, PACKET_LENGTH); // 19-byte payload | |
_spi_write_address(0x26, 0x07); // 1 Mbps air data rate, 5dbm RF power | |
_spi_write_address(0x50, 0x73); // Activate extra feature register | |
_spi_write_address(0x3c, 0x00); // Disable dynamic payload length | |
_spi_write_address(0x3d, 0x00); // Extra features all off | |
MOSI_off; | |
delay(100);//100ms delay | |
//############ INIT2 ############## | |
if(_spi_read_address(0x10) != 0xCC) // reads 1 byte of Transmit address | |
; // we have a problem .... | |
_spi_write_address(0x20, 0x0e); // Power on, TX mode, 2 byte CRC | |
MOSI_off; | |
delay(50);//50ms delay | |
//********************************************************************** | |
//PPM setup | |
attachInterrupt(PPM_pin - 2, read_ppm, CHANGE); | |
TCCR1A = 0; //reset timer1 | |
TCCR1B = 0; | |
TCCR1B |= (1 << CS11); //set timer1 to increment every 1 us @ 8MHz, 0.5 us @16MHz | |
// | |
delay(50); | |
//Bind to Receiver | |
bind_XN297(); | |
} | |
//############ MAIN LOOP ############## | |
void loop() { | |
for (int chan = 0; chan < 4; chan++) { | |
uint32_t nextPacket = millis()+PACKET_INTERVAL; | |
CE_off; | |
delayMicroseconds(5); | |
_spi_write_address(0x20, 0x0e); // TX mode | |
_spi_write_address(0x25, freq[chan]); // Set RF chan | |
_spi_write_address(0x27, 0x70); // Clear interrupts | |
_spi_write_address(0xe1, 0x00); // Flush TX | |
Write_Packet(0x55); // servo_data timing is updated in interrupt (ISR routine for decoding PPM signal) | |
while(millis() < nextPacket) {} // wait | |
} | |
} | |
//BIND_TX | |
void bind_XN297() { | |
byte counter=255; | |
bool bound=false; | |
while(!bound){ | |
CE_off; | |
delayMicroseconds(5); | |
_spi_write_address(0x20, 0x0e); // Power on, TX mode, 2 byte CRC | |
_spi_write_address(0x25, 0x02); // set RF channel 2 | |
_spi_write_address(0x27, 0x70); // Clear interrupts | |
_spi_write_address(0xe1, 0x00); // Flush TX | |
Write_Packet(0xaa); // send bind packet | |
delay(2); | |
_spi_write_address(0x27, 0x70); // Clear interrupts | |
_spi_write_address(0x25, 0x02); // Set RF channel | |
_spi_write_address(0x20, 0x0F); // Power on, RX mode, 2 byte CRC | |
CE_on; // RX mode | |
uint32_t timeout = millis()+4; | |
while(millis()<timeout) { | |
if(_spi_read_address(0x07) == 0x40) { // data received | |
CE_off; | |
Read_Packet(); | |
aid[0] = packet[5]; | |
aid[1] = packet[6]; | |
aid[2] = packet[7]; | |
aid[3] = packet[8]; | |
if(packet[9]==1) { | |
bound=true; | |
break; | |
} | |
} | |
} | |
CE_off; | |
digitalWrite(ledPin, bitRead(--counter,3)); //check for 0bxxxx1xxx to flash LED | |
} | |
digitalWrite(ledPin, HIGH);//LED on at end of bind | |
} | |
//------------------------------- | |
//------------------------------- | |
//XN297 SPI routines | |
//------------------------------- | |
//------------------------------- | |
void Write_Packet(uint8_t init){//24 bytes total per packet | |
uint8_t i; | |
CS_off; | |
_spi_write(0xa0); // Write TX payload | |
_spi_write(init); // packet type: 0xaa or 0x55 aka bind packet or data packet) | |
_spi_write(txid[0]); | |
_spi_write(txid[1]); | |
_spi_write(txid[2]); | |
_spi_write(txid[3]); | |
_spi_write(aid[0]); // Aircraft ID | |
_spi_write(aid[1]); | |
_spi_write(aid[2]); | |
_spi_write(aid[3]); | |
// channels data | |
if (Servo_data[5] > 1500) | |
bitSet(Servo_data[3], 12);// Set flip mode based on chan6 input | |
cli(); // disable interrupts | |
packet[0]=lowByte(Servo_data[AILERON]);//low byte of servo timing(1000-2000us) | |
packet[1]=highByte(Servo_data[AILERON]);//high byte of servo timing(1000-2000us) | |
packet[2]=lowByte(Servo_data[ELEVATOR]); | |
packet[3]=highByte(Servo_data[ELEVATOR]); | |
packet[4]=lowByte(Servo_data[THROTTLE]); | |
packet[5]=highByte(Servo_data[THROTTLE]); | |
packet[6]=lowByte(Servo_data[RUDDER]); | |
packet[7]=highByte(Servo_data[RUDDER]); | |
sei(); // enable interrupts | |
for(i=0;i<4;i++){ | |
_spi_write(packet[0+2*i]); | |
_spi_write(packet[1+2*i]); | |
} | |
// Set mode based on chan5 input | |
if (Servo_data[4] > 1800) | |
i = 0x02;// mode 3 | |
else if (Servo_data[4] > 1300) | |
i = 0x01;// mode 2 | |
else | |
i= 0x00;// mode 1 | |
_spi_write(i); | |
_spi_write(0x00); | |
MOSI_off; | |
CS_on; | |
CE_on; // transmit | |
} | |
void Read_Packet() { | |
uint8_t i; | |
CS_off; | |
_spi_write(0x61); // Read RX payload | |
for (i=0;i<PACKET_LENGTH;i++) { | |
packet[i]=_spi_read(); | |
} | |
CS_on; | |
} | |
void _spi_write(uint8_t command) { | |
uint8_t n=8; | |
SCK_off; | |
MOSI_off; | |
while(n--) { | |
if(command&0x80) | |
MOSI_on; | |
else | |
MOSI_off; | |
SCK_on; | |
NOP(); | |
SCK_off; | |
command = command << 1; | |
} | |
MOSI_on; | |
} | |
void _spi_write_address(uint8_t address, uint8_t data) { | |
CS_off; | |
_spi_write(address); | |
NOP(); | |
_spi_write(data); | |
CS_on; | |
} | |
// read one byte from MISO | |
uint8_t _spi_read() | |
{ | |
uint8_t result=0; | |
uint8_t i; | |
MOSI_off; | |
NOP(); | |
for(i=0;i<8;i++) { | |
if(MISO_on) // if MISO is HIGH | |
result = (result<<1)|0x01; | |
else | |
result = result<<1; | |
SCK_on; | |
NOP(); | |
SCK_off; | |
NOP(); | |
} | |
return result; | |
} | |
uint8_t _spi_read_address(uint8_t address) { | |
uint8_t result; | |
CS_off; | |
_spi_write(address); | |
result = _spi_read(); | |
CS_on; | |
return(result); | |
} | |
// ppm input interrupt | |
void read_ppm() | |
{ | |
#if F_CPU == 16000000 | |
#define PPM_SCALE 1L | |
#elif F_CPU == 8000000 | |
#define PPM_SCALE 0L | |
#else | |
#error // 8 or 16MHz only ! | |
#endif | |
static unsigned int pulse; | |
static unsigned long counterPPM; | |
static byte chan; | |
counterPPM = TCNT1; | |
TCNT1 = 0; | |
if(counterPPM < 510 << PPM_SCALE) { //must be a pulse if less than 510us | |
pulse = counterPPM; | |
} | |
else if(counterPPM > 1910 << PPM_SCALE) { //sync pulses over 1910us | |
chan = 0; | |
} | |
else{ //servo values between 510us and 2420us will end up here | |
if(chan < CHANNELS) { | |
Servo_data[chan]= constrain((counterPPM + pulse) >> PPM_SCALE, PPM_MIN, PPM_MAX); | |
} | |
chan++; | |
} | |
} |
hi,
I recently bought a cheerson cx10 with a blue pcb.
but it seems to have been changed in design since the models from last year.
on the board it reads cx-10r-6 instead of cx-10r-3 and this code doesnt bind it anymore.
did you by any chance have a code for the new one or could you point me in a direction as to how to reverse engineer the protocol used by the drone.
Any help would be appreciated.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How should i change this code to make it RX?