Skip to content

Instantly share code, notes, and snippets.

@goebish
Last active September 25, 2022 20:59
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save goebish/f8982353c34f2b71ffe8 to your computer and use it in GitHub Desktop.
Save goebish/f8982353c34f2b71ffe8 to your computer and use it in GitHub Desktop.
Cheerson CX-10 Tx Arduino Code (blue PCB)
// 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++;
}
}
@edulum
Copy link

edulum commented Sep 4, 2016

How should i change this code to make it RX?

@padhun
Copy link

padhun commented Apr 5, 2018

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