Skip to content

Instantly share code, notes, and snippets.

@goebish
Last active August 29, 2015 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goebish/2c2a7502b211fbda2d8f to your computer and use it in GitHub Desktop.
Save goebish/2c2a7502b211fbda2d8f to your computer and use it in GitHub Desktop.
/*
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.
Deviation 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 Deviation. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef MODULAR
//Allows the linker to properly relocate
#define CG023_Cmds PROTO_Cmds
#pragma long_calls
#endif
#include "common.h"
#include "interface.h"
#include "mixer.h"
#include "config/model.h"
#include "config/tx.h" // for Transmitter
#include "music.h"
#ifdef MODULAR
//Some versions of gcc apply this to definitions, others to calls
//So just use long_calls everywhere
//#pragma long_calls_off
extern unsigned _data_loadaddr;
const unsigned long protocol_type = (unsigned long)&_data_loadaddr;
#endif
#ifdef PROTO_HAS_NRF24L01
#include "iface_nrf24l01.h"
#ifdef EMULATOR
#define USE_FIXED_MFGID
#define BIND_COUNT 20
#define dbgprintf printf
#else
#define BIND_COUNT 800 // 6 seconds
//printf inside an interrupt handler is really dangerous
//this shouldn't be enabled even in debug builds without explicitly
//turning it on
#define dbgprintf if(0) printf
#endif
#define CG023_PACKET_PERIOD 8200 // Timeout for callback in uSec
#define YD829_PACKET_PERIOD 4100 // stock tx is 4100, but seems to work better with shorter period
#define INITIAL_WAIT 500
#define PACKET_SIZE 15 // packets have 15-byte payload
#define RF_BIND_CHANNEL 0x2D
static const char * const cg023_opts[] = {
"Format", "CG023", "YD-829", NULL,
"Dyn Trims", _tr_noop("Off"), _tr_noop("On"), NULL,
NULL
};
enum {
PROTOOPTS_FORMAT = 0,
PROTOOPTS_DYNTRIM,
LAST_PROTO_OPT,
};
ctassert(LAST_PROTO_OPT <= NUM_PROTO_OPTS, too_many_protocol_opts);
#define FORMAT_CG023 0
#define FORMAT_YD829 1
#define DYNTRIM_OFF 0
#define DYNTRIM_ON 1
// For code readability
enum {
CHANNEL1 = 0, // Aileron
CHANNEL2, // Elevator
CHANNEL3, // Throttle
CHANNEL4, // Rudder
CHANNEL5, // Leds
CHANNEL6, // Flip
CHANNEL7, // Still camera
CHANNEL8, // Video camera
CHANNEL9, // Headless
CHANNEL10 // Rate (3 pos)
};
enum{
// flags going to packet[13] (CG023)
FLAG_FLIP = 0x01,
FLAG_EASY = 0x02,
FLAG_VIDEO = 0x04,
FLAG_STILL = 0x08,
FLAG_LED_OFF = 0x10,
MASK_RATE = 0x60,
FLAG_RATE_MID = 0x20,
FLAG_RATE_HIGH= 0x40,
};
enum{
// flags going to packet[13] (YD-829)
YD_FLAG_FLIP = 0x01,
YD_MASK_RATE = 0x0C,
YD_FLAG_RATE_MID = 0x04,
YD_FLAG_RATE_HIGH= 0x08,
YD_FLAG_HEADLESS = 0x20,
YD_FLAG_VIDEO = 0x40,
YD_FLAG_STILL = 0x80,
};
enum {
CG023_INIT1 = 0,
CG023_BIND2,
CG023_DATA
};
static u8 packet[PACKET_SIZE];
static u16 counter;
static u8 tx_power;
static u8 txid[2];
static u8 rf_chan;
static const u8 rx_tx_addr[] = {0x26, 0xA8, 0x67, 0x35, 0xCC};
static u8 phase;
static u32 packet_period;
// Bit vector from bit position
#define BV(bit) (1 << bit)
static s16 scale_channel(u8 ch, s32 destMin, s32 destMax)
{
s32 a = (destMax - destMin) * ((s32)Channels[ch] - CHAN_MIN_VALUE);
s32 b = CHAN_MAX_VALUE - CHAN_MIN_VALUE;
return ((a / b) - (destMax - destMin)) + destMax;
}
static void send_packet(u8 bind)
{
if (bind) {
packet[0]= 0xaa;
} else {
packet[0]= 0x55;
}
// transmitter id
packet[1] = txid[0];
packet[2] = txid[1];
// unknown
packet[3] = 0x00;
packet[4] = 0x00;
// throttle : 0x00 - 0xFF
packet[5] = scale_channel(CHANNEL3, 0x00, 0xFF);
if( Channels[CHANNEL4] > 0) {
// yaw right : 0x80 (neutral) - 0xBC (right)
packet[6] = scale_channel(CHANNEL4, 0x44 , 0xBC);
} else {
// yaw left : 0x00 (neutral) - 0x3C (left)
packet[6] = scale_channel(CHANNEL4, 0x3C, -0x3C);
}
// elevator : 0xBB - 0x7F - 0x43
packet[7] = scale_channel(CHANNEL2, 0xBB, 0x43);
// aileron : 0x43 - 0x7F - 0xBB
packet[8] = scale_channel(CHANNEL1, 0x43, 0xBB);
// throttle trim : 0x30 - 0x20 - 0x10
packet[9] = 0x20; // neutral
if(Model.proto_opts[PROTOOPTS_DYNTRIM] == DYNTRIM_ON) { // experimental dynamic protocol trims, has no effect on 3D X4
// rudder trim : 0x10 - 0x20 - 0x30
packet[10] = scale_channel(CHANNEL4, 0x10, 0x30);
// elevator trim : 0x60 - 0x40 - 0x20
packet[11] = scale_channel(CHANNEL2, 0x60, 0x20);
// aileron trim : 0x60 - 0x40 - 0x20
packet[12] = scale_channel(CHANNEL1, 0x60, 0x20);
} else {
// neutral trims
packet[10] = 0x20;
packet[11] = 0x40;
packet[12] = 0x40;
}
// flags
packet[13] = 0x00;
switch( Model.proto_opts[PROTOOPTS_FORMAT]) {
case FORMAT_CG023:
if(Channels[CHANNEL10] > 0) {
if(Channels[CHANNEL10] < CHAN_MAX_VALUE / 2)
packet[13] |= FLAG_RATE_MID;
} else
packet[13] |= FLAG_RATE_HIGH;
if(Channels[CHANNEL5] > 0)
packet[13] |= FLAG_LED_OFF;
if(Channels[CHANNEL6] > 0)
packet[13] |= FLAG_FLIP;
if(Channels[CHANNEL7] > 0)
packet[13] |= FLAG_STILL;
if(Channels[CHANNEL8] > 0)
packet[13] |= FLAG_VIDEO;
if(Channels[CHANNEL9] > 0)
packet[13] |= FLAG_EASY;
break;
case FORMAT_YD829:
// reverse aileron direction
packet[8] = 0xFE - packet[8];
if(Channels[CHANNEL10] > 0) {
if(Channels[CHANNEL10] < CHAN_MAX_VALUE / 2)
packet[13] |= YD_FLAG_RATE_MID;
} else
packet[13] |= YD_FLAG_RATE_HIGH;
if(Channels[CHANNEL6] > 0)
packet[13] |= YD_FLAG_FLIP;
if(Channels[CHANNEL7] > 0)
packet[13] |= YD_FLAG_STILL;
if(Channels[CHANNEL8] > 0)
packet[13] |= YD_FLAG_VIDEO;
if(Channels[CHANNEL9] > 0)
packet[13] |= YD_FLAG_HEADLESS;
break;
}
packet[14] = 0;
// Power on, TX mode, 2byte CRC
// Why CRC0? xn297 does not interpret it - either 16-bit CRC or nothing
XN297_Configure(BV(NRF24L01_00_EN_CRC) | BV(NRF24L01_00_CRCO) | BV(NRF24L01_00_PWR_UP));
if (bind) {
NRF24L01_WriteReg(NRF24L01_05_RF_CH, RF_BIND_CHANNEL);
} else {
NRF24L01_WriteReg(NRF24L01_05_RF_CH, rf_chan);
}
// clear packet status bits and TX FIFO
NRF24L01_WriteReg(NRF24L01_07_STATUS, 0x70);
NRF24L01_FlushTx();
XN297_WritePayload(packet, PACKET_SIZE);
// Check and adjust transmission power. We do this after
// transmission to not bother with timeout after power
// settings change - we have plenty of time until next
// packet.
if (tx_power != Model.tx_power) {
//Keep transmit power updated
tx_power = Model.tx_power;
NRF24L01_SetPower(tx_power);
}
}
static void cg023_init()
{
NRF24L01_Initialize();
NRF24L01_SetTxRxMode(TX_EN);
// SPI trace of stock TX has these writes to registers that don't appear in
// nRF24L01 or Beken 2421 datasheets. Uncomment if you have an XN297 chip?
// NRF24L01_WriteRegisterMulti(0x3f, "\x4c\x84\x67,\x9c,\x20", 5);
// NRF24L01_WriteRegisterMulti(0x3e, "\xc9\x9a\xb0,\x61,\xbb,\xab,\x9c", 7);
// NRF24L01_WriteRegisterMulti(0x39, "\x0b\xdf\xc4,\xa7,\x03,\xab,\x9c", 7);
XN297_SetTXAddr(rx_tx_addr, 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); // Enable data pipe 0 only
NRF24L01_WriteReg(NRF24L01_06_RF_SETUP, 0x07);
NRF24L01_SetBitrate(NRF24L01_BR_1M); // 1Mbps
NRF24L01_SetPower(Model.tx_power);
// this sequence necessary for module from stock tx
NRF24L01_ReadReg(NRF24L01_1D_FEATURE);
NRF24L01_Activate(0x73); // Activate feature register
NRF24L01_ReadReg(NRF24L01_1D_FEATURE);
NRF24L01_WriteReg(NRF24L01_1C_DYNPD, 0x00); // Disable dynamic payload length on all pipes
NRF24L01_WriteReg(NRF24L01_1D_FEATURE, 0x00); // Set feature bits on
// Check for Beken BK2421/BK2423 chip
// It is done by using Beken specific activate code, 0x53
// and checking that status register changed appropriately
// There is no harm to run it on nRF24L01 because following
// closing activate command changes state back even if it
// does something on nRF24L01
NRF24L01_Activate(0x53); // magic for BK2421 bank switch
dbgprintf("Trying to switch banks\n");
if (NRF24L01_ReadReg(NRF24L01_07_STATUS) & 0x80) {
dbgprintf("BK2421 detected\n");
// Beken registers don't have such nice names, so we just mention
// them by their numbers
// It's all magic, eavesdropped from real transfer and not even from the
// data sheet - it has slightly different values
NRF24L01_WriteRegisterMulti(0x00, (u8 *) "\x40\x4B\x01\xE2", 4);
NRF24L01_WriteRegisterMulti(0x01, (u8 *) "\xC0\x4B\x00\x00", 4);
NRF24L01_WriteRegisterMulti(0x02, (u8 *) "\xD0\xFC\x8C\x02", 4);
NRF24L01_WriteRegisterMulti(0x03, (u8 *) "\x99\x00\x39\x21", 4);
NRF24L01_WriteRegisterMulti(0x04, (u8 *) "\xD9\x96\x82\x1B", 4);
NRF24L01_WriteRegisterMulti(0x05, (u8 *) "\x24\x06\x7F\xA6", 4);
NRF24L01_WriteRegisterMulti(0x0C, (u8 *) "\x00\x12\x73\x00", 4);
NRF24L01_WriteRegisterMulti(0x0D, (u8 *) "\x46\xB4\x80\x00", 4);
NRF24L01_WriteRegisterMulti(0x04, (u8 *) "\xDF\x96\x82\x1B", 4);
NRF24L01_WriteRegisterMulti(0x04, (u8 *) "\xD9\x96\x82\x1B", 4);
} else {
dbgprintf("nRF24L01 detected\n");
}
NRF24L01_Activate(0x53); // switch bank back
}
MODULE_CALLTYPE
static u16 cg023_callback()
{
switch (phase) {
case CG023_INIT1:
MUSIC_Play(MUSIC_TELEMALARM1);
phase = CG023_BIND2;
break;
case CG023_BIND2:
if (counter == 0) {
phase = CG023_DATA;
PROTOCOL_SetBindState(0);
MUSIC_Play(MUSIC_DONE_BINDING);
} else {
send_packet(1);
counter -= 1;
}
break;
case CG023_DATA:
send_packet(0);
break;
}
return packet_period;
}
static void initialize_txid()
{
/*u32 temp;
if (Model.fixed_id) {
temp = Crc(&Model.fixed_id, sizeof(Model.fixed_id));
} else {
temp = (Crc(&Model, sizeof(Model)) + Crc(&Transmitter, sizeof(Transmitter))) ;
}
txid[0] = 0x80 | (temp % 0x40);
if( txid[0] == 0xAA) // avoid using same freq for bind and data channel
txid[0] ++;
txid[1] = (temp >> 8) & 0xFF;
// rf channel for data packets
rf_chan = txid[0] - 0x7D;*/
txid[0] = 0xE3;
txid[1] = 0x11;
rf_chan = 0x26;
}
static void initialize()
{
CLOCK_StopTimer();
tx_power = Model.tx_power;
counter = BIND_COUNT;
initialize_txid();
cg023_init();
phase = CG023_INIT1;
switch( Model.proto_opts[PROTOOPTS_FORMAT]) {
case FORMAT_YD829:
packet_period = YD829_PACKET_PERIOD;
break;
case FORMAT_CG023:
packet_period = CG023_PACKET_PERIOD;
break;
}
PROTOCOL_SetBindState(BIND_COUNT * packet_period / 1000);
CLOCK_StartTimer(INITIAL_WAIT, cg023_callback);
}
const void *CG023_Cmds(enum ProtoCmds cmd)
{
switch(cmd) {
case PROTOCMD_INIT: initialize(); return 0;
case PROTOCMD_DEINIT:
case PROTOCMD_RESET:
CLOCK_StopTimer();
return (void *)(NRF24L01_Reset() ? 1L : -1L);
case PROTOCMD_CHECK_AUTOBIND: return (void *)1L; // always Autobind
case PROTOCMD_BIND: initialize(); return 0;
case PROTOCMD_NUMCHAN: return (void *) 10L; // A, E, T, R, light, flip, photo, video, headless, rate
case PROTOCMD_DEFAULT_NUMCHAN: return (void *)10L;
case PROTOCMD_CURRENT_ID: return Model.fixed_id ? (void *)((unsigned long)Model.fixed_id) : 0;
case PROTOCMD_GETOPTIONS: return cg023_opts;
case PROTOCMD_TELEMETRYSTATE: return (void *)(long)PROTO_TELEM_UNSUPPORTED;
default: break;
}
return 0;
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment