Last active
August 29, 2015 14:19
-
-
Save goebish/6095bb2d95037297641d to your computer and use it in GitHub Desktop.
Eachine X4 3D TX.
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
// ************************************************************** | |
// ******************* 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