Skip to content

Instantly share code, notes, and snippets.

@goebish
Last active August 29, 2015 14:19
Show Gist options
  • Save goebish/6095bb2d95037297641d to your computer and use it in GitHub Desktop.
Save goebish/6095bb2d95037297641d to your computer and use it in GitHub Desktop.
Eachine X4 3D TX.
// **************************************************************
// ******************* Eachine X4 3D Tx Code *******************
// by goebish on RCgroups.com
// 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 Eachine X4 3D RF module - XN297 and circuitry harvested from stock controller
// 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 PAYLOAD_SIZE 0x0F
#define PACKET_INTERVAL 8250 // interval of time between start of 2 packets, in us
#define PPM_MIN 1000
#define PPM_MID 1500
#define PPM_MAX 2000
#define PPM_DEADDBAND 10
// PPM stream settings
#define CHANNELS 6
enum chan_order{ // TAER -> Spektrum/FrSky chan order
THROTTLE,
AILERON,
ELEVATOR,
RUDDER,
AUX1, // mode
AUX2, // flip control
};
enum FLAGS{
// flags going to packet[13]
FLIP = 0x01, // right shoulder (3D flip switch), resets after aileron or elevator has moved and came back to neutral
EASY = 0x02, // left shoulder (headless mode, unsupported on X4 3D)
CAMERA = 0x04, // video camera (unsupported on X4 3D)
UNK_BTN = 0x08, // still camera (unsupported on X4 3D)
LED_OFF = 0x10,
RATE_60 = 0x20,
RATE_100= 0x40,
};
//########## Variables #################
static uint8_t packet[PAYLOAD_SIZE];
volatile uint16_t Servo_data[CHANNELS] = {0,};
static uint16_t ppm[CHANNELS] = {PPM_MIN,PPM_MID,PPM_MID,PPM_MID,PPM_MAX,PPM_MIN};
volatile bool ppm_ok = false;
int ledPin = 13;
void setup()
{
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(0x6F);
_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(0x80);
_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 0x26A86735CC
_spi_write(0x26);
_spi_write(0xA8);
_spi_write(0x67);
_spi_write(0x35);
_spi_write(0xcc);
CS_on;
delayMicroseconds(5);
CS_off;
_spi_write(0x2a); // Set RX pipe 0 address 0x26A86735CC
_spi_write(0x26);
_spi_write(0xA8);
_spi_write(0x67);
_spi_write(0x35);
_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, 0x2D); // Set channel frequency
_spi_write_address(0x24, 0x00); // No auto-retransmit
_spi_write_address(0x31, PAYLOAD_SIZE); // 15-byte payload
_spi_write_address(0x26, 0x07); // 1 Mbps air data rate, 5dbm RF power
_spi_write_address(0x3c, 0x00); // Disable dynamic payload length
_spi_write_address(0x3d, 0x00); // Extra features all off
if(_spi_read_address(0xA0) != 0x26) // reads 1 byte of Receive address ?
; // we have a problem ....
MOSI_off;
delay(400);
_spi_write_address(0x20, 0x0e); // Power on, TX mode, 2 byte CRC
MOSI_off;
delay(400);
//**********************************************************************
//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
//Bind to Receiver
bind_X43D();
}
//############ MAIN LOOP ##############
void loop()
{
uint32_t nextPacket = micros()+PACKET_INTERVAL; // assumes this program will run for less than 70 minutes.
CE_off;
delayMicroseconds(5);
_spi_write_address(0x27,0x70); // Clear interrupts
_spi_write_address(0xe1,0x00); // Clear TX buffer
_spi_write_address(0x25,0x07); // Set channel frequency
Write_Packet(0x55);
while(micros() < nextPacket) {
if(ppm_ok) {
for(uint8_t ch=0; ch<CHANNELS; ch++) {
ppm[ch] = Servo_data[ch];
}
ppm_ok = false;
}
}
}
//BIND_TX
void bind_X43D() {
byte counter=255;
for(;;) {
CE_off;
delayMicroseconds(5);
_spi_write_address(0x27,0x70); // Clear interrupts
_spi_write_address(0xe1,0x00); // Clear TX buffer
_spi_write_address(0x25,0x2D); // Set channel frequency
Write_Packet(0xAA); // send bind packet
delay(7);
if (bitRead(counter,3)==1) //check for 0bxxxx1xxx to flash LED
digitalWrite(ledPin, HIGH);//LED on
if(bitRead(counter,3)==0)
digitalWrite(ledPin, LOW);//LED off
counter--;
if(counter==0)
break;
}
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;
packet[0] = init;
packet[1] = 0x84; // unknown (txid ?)
packet[2] = 0x43; // unknown (txid ?)
packet[3] = 0; // unknown
packet[4] = 0; // unknown
packet[5] = map(ppm[THROTTLE], PPM_MIN, PPM_MAX, 0, 255) ; // throttle stick
if(Servo_data[RUDDER] < PPM_MID - PPM_DEADDBAND)
packet[6] = map(ppm[RUDDER], PPM_MID - PPM_DEADDBAND, PPM_MIN, 0x80 , 0xBC);
else if(Servo_data[RUDDER] > PPM_MID + PPM_DEADDBAND)
packet[6] = map(ppm[RUDDER], PPM_MID + PPM_DEADDBAND, PPM_MAX, 0x00 , 0x3C);
else
packet[6] = 0x00;
packet[7] = map(ppm[ELEVATOR], PPM_MIN, PPM_MAX, 0xBB, 0x43); // elevator stick 0xBB - 0x7F - 0x43
packet[8] = map(ppm[AILERON], PPM_MIN, PPM_MAX, 0xBB, 0x43); // aileron stick 0xBB - 0x7F - 0x43
packet[9] = 0x20; // throttle trim, neutral = 0x20
packet[10] = 0x20; // rudder trim, neutral = 0x20
packet[11] = 0x40; // elevator trim, neutral = 0x40
packet[12] = 0x40; // aileron trim, neutral = 0x40
packet[13] |= RATE_100; // 100% rate
packet[14] = 0x00; // unknown
CS_off;
_spi_write(0xa0); // Write TX payload
for(uint8_t i=0; i<PAYLOAD_SIZE; i++)
_spi_write(packet[i]);
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<PAYLOAD_SIZE;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;
ppm_ok=false;
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);
if(chan==CHANNELS-1)
ppm_ok = true;
}
chan++;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment