/**
 ******************************************************************************
 *
 * @file       ppm.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      Sends or Receives the ppm values to/from the remote unit
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <string.h>	// memmove

#include "main.h"
#include "rfm22b.h"
#include "saved_settings.h"
#include "ppm.h"

#if defined(PIOS_COM_DEBUG)
	#define PPM_DEBUG
#endif

// *************************************************************

#define PPM_OUT_FRAME_PERIOD_US          20000                      // microseconds
#define PPM_OUT_HIGH_PULSE_US            480                        // microseconds
#define PPM_OUT_MIN_CHANNEL_PULSE_US     850                        // microseconds
#define PPM_OUT_MAX_CHANNEL_PULSE_US     2200                       // microseconds

#define PPM_IN_MIN_SYNC_PULSE_US         7000                       // microseconds .. Pip's 6-chan TX goes down to 8.8ms
#define PPM_IN_MIN_CHANNEL_PULSE_US      750                        // microseconds
#define PPM_IN_MAX_CHANNEL_PULSE_US      2400                       // microseconds

// *************************************************************

uint8_t ppm_mode;

volatile bool ppm_initialising = true;

volatile uint32_t ppm_In_PrevFrames = 0;
volatile uint32_t ppm_In_LastValidFrameTimer = 0;
volatile uint32_t ppm_In_Frames = 0;
volatile uint32_t ppm_In_ErrorFrames = 0;
volatile uint8_t ppm_In_NoisyChannelCounter = 0;
volatile int8_t ppm_In_ChannelsDetected = 0;
volatile int8_t ppm_In_ChannelPulseIndex = -1;
volatile int32_t ppm_In_PreviousValue = -1;
volatile uint32_t ppm_In_PulseWidth = 0;
volatile uint32_t ppm_In_ChannelPulseWidthNew[PIOS_PPM_MAX_CHANNELS];
volatile uint32_t ppm_In_ChannelPulseWidth[PIOS_PPM_MAX_CHANNELS];

volatile uint16_t ppm_Out_ChannelPulseWidth[PIOS_PPM_MAX_CHANNELS];
volatile uint32_t ppm_Out_SyncPulseWidth = PPM_OUT_FRAME_PERIOD_US;
volatile int8_t ppm_Out_ChannelPulseIndex = -1;
volatile uint8_t ppm_Out_ChannelsUsed = 0;

// *************************************************************

// Initialise the PPM INPUT
void ppm_In_Init(void)
{
	TIM_ICInitTypeDef TIM_ICInitStructure;

	// disable the timer
	TIM_Cmd(PIOS_PPM_TIM, DISABLE);

	ppm_In_PrevFrames = 0;
	ppm_In_NoisyChannelCounter = 0;
	ppm_In_LastValidFrameTimer = 0;
	ppm_In_Frames = 0;
	ppm_In_ErrorFrames = 0;
	ppm_In_ChannelsDetected = 0;
	ppm_In_ChannelPulseIndex = -1;
	ppm_In_PreviousValue = -1;
	ppm_In_PulseWidth = 0;

	for (int i = 0; i < PIOS_PPM_MAX_CHANNELS; i++)
	{
		ppm_In_ChannelPulseWidthNew[i] = 0;
		ppm_In_ChannelPulseWidth[i] = 0;
	}

	// Enable timer clock
	PIOS_PPM_TIMER_EN_RCC_FUNC;

	// Enable timer interrupts
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PIOS_IRQ_PRIO_MID;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannel = PIOS_PPM_TIM_IRQ;
	NVIC_Init(&NVIC_InitStructure);

	// Init PPM IN pin
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_StructInit(&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = PPM_IN_PIN;
	GPIO_InitStructure.GPIO_Mode = PPM_IN_MODE;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(PPM_IN_PORT, &GPIO_InitStructure);

	// remap the pin to switch it to timer mode
	if (PIOS_PPM_TIM == TIM2)
	{
//		GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
		GPIO_PinRemapConfig(GPIO_PartialRemap2_TIM2, ENABLE);
//		GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE);
	}

	// Configure timer for input capture
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICFilter = 15;     // 0 to 15
	TIM_ICInitStructure.TIM_Channel = PIOS_PPM_IN_TIM_CHANNEL;
	TIM_ICInit(PIOS_PPM_TIM_PORT, &TIM_ICInitStructure);

	// Configure timer clocks
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = 25000 - 1;   // 25ms - can be anything you like now really - up to 65536us
	TIM_TimeBaseStructure.TIM_Prescaler = (PIOS_MASTER_CLOCK / 1000000) - 1;
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_InternalClockConfig(PIOS_PPM_TIM_PORT);
	TIM_TimeBaseInit(PIOS_PPM_TIM_PORT, &TIM_TimeBaseStructure);

	// Enable the Capture Compare and Update Interrupts
	TIM_ITConfig(PIOS_PPM_TIM_PORT, PIOS_PPM_IN_TIM_CCR | TIM_IT_Update, ENABLE);

	// Clear TIMER Capture compare and update interrupt pending bits
	TIM_ClearITPendingBit(PIOS_PPM_TIM_PORT, PIOS_PPM_IN_TIM_CCR | TIM_IT_Update);

	// Enable timer
	TIM_Cmd(PIOS_PPM_TIM, ENABLE);

	// Setup local variable which stays in this scope
	// Doing this here and using a local variable saves doing it in the ISR
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICFilter = 0x0;

#ifdef PPM_DEBUG
	DEBUG_PRINTF("ppm_in: initialised\r\n");
#endif
}

// TIMER capture/compare/update interrupt
void PIOS_PPM_IN_CC_IRQ(void)
{
	uint16_t new_value = 0;
	uint32_t period = (uint32_t)PIOS_PPM_TIM->ARR + 1;

	if (booting || ppm_initialising)
	{   // clear the interrupts
		TIM_ClearITPendingBit(PIOS_PPM_TIM_PORT, PIOS_PPM_IN_TIM_CCR | TIM_IT_Update);
		return;
	}

	// determine the interrupt source(s)
	bool update_int = TIM_GetITStatus(PIOS_PPM_TIM_PORT, TIM_IT_Update) == SET;          // timer/counter overflow occured
	bool capture_int = TIM_GetITStatus(PIOS_PPM_TIM_PORT, PIOS_PPM_IN_TIM_CCR) == SET;   // PPM input capture

	if (capture_int)
		new_value = PIOS_PPM_IN_TIM_GETCAP_FUNC(PIOS_PPM_TIM_PORT);

	// clear the interrupts
	TIM_ClearITPendingBit(PIOS_PPM_TIM_PORT, PIOS_PPM_IN_TIM_CCR | TIM_IT_Update);

	// ********

	uint32_t ticks = 0;
	if (update_int)
	{   // timer/counter overflowed

		if (ppm_In_PreviousValue >= 0)
			ticks = (period - ppm_In_PreviousValue) + new_value;
		else
		{
			ticks = period;
			if (capture_int) ticks += new_value;
		}
		ppm_In_PreviousValue = -1;
	}
	else
	if (capture_int)
	{
		if (ppm_In_PreviousValue >= 0)
			ticks = new_value - ppm_In_PreviousValue;
		else
			ticks += new_value;
	}

	ppm_In_PulseWidth += ticks;
	if (ppm_In_PulseWidth > 0x7fffffff)
		ppm_In_PulseWidth = 0x7fffffff;                    // prevent overflows

	ppm_In_LastValidFrameTimer += ticks;
	if (ppm_In_LastValidFrameTimer > 0x7fffffff)
		ppm_In_LastValidFrameTimer = 0x7fffffff;           // prevent overflows

	if (capture_int)
		ppm_In_PreviousValue = new_value;

	// ********

#ifdef PPM_DEBUG
//	DEBUG_PRINTF("ppm_in:");
//	if (update_int) DEBUG_PRINTF(" update");
//	if (capture_int) DEBUG_PRINTF(" capture");
//	DEBUG_PRINTF(" %u %u\r\n", ppm_In_LastValidFrameTimer, ppm_In_PulseWidth);
#endif

	if (ppm_In_LastValidFrameTimer >= 200000 && ppm_In_Frames > 0)
	{	// we haven't seen a valid PPM frame for at least 200ms
		for (int i = 0; i < PIOS_PPM_MAX_CHANNELS; i++)
			ppm_In_ChannelPulseWidth[i] = 0;
		ppm_In_Frames = 0;
		ppm_In_ErrorFrames = 0;
	}

	if (ppm_In_ChannelPulseIndex < 0 || ppm_In_PulseWidth > PPM_IN_MAX_CHANNEL_PULSE_US)
	{	// we are looking for a SYNC pulse, or we are receiving one

			if (ppm_In_ChannelPulseIndex >= 0)
			{	// it's either the start of a sync pulse or a noisy channel .. assume it's the end of a PPM frame

				if (ppm_In_ChannelPulseIndex > 0)
				{
					if (ppm_In_Frames < 0xffffffff)
						ppm_In_Frames++;                            // update frame counter

					#ifdef PPM_DEBUG
//						DEBUG_PRINTF("ppm_in: %u %u\r\n", ppm_In_ChannelsDetected, ppm_In_ChannelPulseIndex);
					#endif

					if (ppm_In_ChannelsDetected > 0 &&
							ppm_In_ChannelsDetected == ppm_In_ChannelPulseIndex &&
							ppm_In_NoisyChannelCounter <= 2)
					{	// detected same number of channels as in previous PPM frame .. save the new channel PWM values
						#ifdef PPM_DEBUG
//							DEBUG_PRINTF("ppm_in: %u channels detected\r\n", ppm_In_ChannelPulseIndex);
						#endif

						for (int i = 0; i < PIOS_PPM_MAX_CHANNELS; i++)
							ppm_In_ChannelPulseWidth[i] = ppm_In_ChannelPulseWidthNew[i];

						ppm_In_LastValidFrameTimer = 0;                 // reset timer
					}
					else
					{
						if ((ppm_In_ChannelsDetected > 0 && ppm_In_ChannelsDetected != ppm_In_ChannelPulseIndex) ||
								ppm_In_NoisyChannelCounter >= 2)
						{
							if (ppm_In_ErrorFrames < 0xffffffff)
								ppm_In_ErrorFrames++;
						}
					}
					ppm_In_ChannelsDetected = ppm_In_ChannelPulseIndex;     // the number of channels we found in this frame
				}

				ppm_In_ChannelPulseIndex = -1;                              // back to looking for a SYNC pulse
			}

		if (ppm_In_PulseWidth >= PPM_IN_MIN_SYNC_PULSE_US)
		{	// SYNC pulse found
			ppm_In_NoisyChannelCounter = 0;                             // reset noisy channel detector
			ppm_In_ChannelPulseIndex = 0;                               // start of PPM frame
		}
	}
	else
	if (capture_int)
	{	// CHANNEL pulse

		if (ppm_In_PulseWidth < PPM_IN_MIN_CHANNEL_PULSE_US)
		{	// bad/noisy channel pulse .. reset state to wait for next SYNC pulse
			ppm_In_ChannelPulseIndex = -1;

			if (ppm_In_ErrorFrames < 0xffffffff)
				ppm_In_ErrorFrames++;
		}
		else
		{	// pulse width is within the accepted tolerance range for a channel
			if (ppm_In_ChannelPulseIndex < PIOS_PPM_MAX_CHANNELS)
			{
				if (ppm_In_ChannelPulseWidthNew[ppm_In_ChannelPulseIndex] > 0)
				{
					int32_t difference = (int32_t)ppm_In_PulseWidth - ppm_In_ChannelPulseWidthNew[ppm_In_ChannelPulseIndex];
					if (abs(difference) >= 600)
						ppm_In_NoisyChannelCounter++;                       // possibly a noisy channel - or an RC switch was moved
				}

				ppm_In_ChannelPulseWidthNew[ppm_In_ChannelPulseIndex] = ppm_In_PulseWidth;    // save it
			}

			if (ppm_In_ChannelPulseIndex < 127)
				ppm_In_ChannelPulseIndex++;                             // next channel
		}
	}

	if (capture_int)
		ppm_In_PulseWidth = 0;

	// ********
}

uint32_t ppm_In_NewFrame(void)
{
	if (booting || ppm_initialising)
		return 0;

	if (ppm_In_Frames >= 2 && ppm_In_Frames != ppm_In_PrevFrames)
	{	// we have a new PPM frame
		ppm_In_PrevFrames = ppm_In_Frames;
		return ppm_In_PrevFrames;
	}

	return 0;
}

int32_t ppm_In_GetChannelPulseWidth(uint8_t channel)
{
	if (booting || ppm_initialising)
		return -1;

	// Return error if channel not available
	if (channel >= PIOS_PPM_MAX_CHANNELS || channel >= ppm_In_ChannelsDetected)
		return -2;

	return ppm_In_ChannelPulseWidth[channel];    // return channel pulse width
}

// *************************************************************

// Initialise the PPM INPUT
void ppm_Out_Init(void)
{
	// disable the timer
	TIM_Cmd(PIOS_PPM_TIM, DISABLE);

	ppm_Out_SyncPulseWidth = PPM_OUT_FRAME_PERIOD_US;
	ppm_Out_ChannelPulseIndex = -1;
	ppm_Out_ChannelsUsed = 0;
	for (int i = 0; i < PIOS_PPM_MAX_CHANNELS; i++)
		ppm_Out_ChannelPulseWidth[i] = 1000;
//		ppm_Out_ChannelPulseWidth[i] = 1000 + i * 100;	// TEST ONLY

//	ppm_Out_ChannelsUsed = 5;	// TEST ONLY

	// Enable timer clock
	PIOS_PPM_TIMER_EN_RCC_FUNC;

	// Init PPM OUT pin
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_StructInit(&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = PPM_OUT_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(PPM_OUT_PORT, &GPIO_InitStructure);

	// remap the pin to switch it to timer mode
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
//	GPIO_PinRemapConfig(GPIO_PartialRemap2_TIM2, ENABLE);
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE);

	// Enable timer interrupt
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PIOS_IRQ_PRIO_MID;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannel = PIOS_PPM_TIM_IRQ;
	NVIC_Init(&NVIC_InitStructure);

	// Time base configuration
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = ppm_Out_SyncPulseWidth - 1;
	TIM_TimeBaseStructure.TIM_Prescaler = (PIOS_MASTER_CLOCK / 1000000) - 1;
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_InternalClockConfig(PIOS_PPM_TIM_PORT);
	TIM_TimeBaseInit(PIOS_PPM_TIM_PORT, &TIM_TimeBaseStructure);

	// Set up for output compare function
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
	TIM_OCInitStructure.TIM_Pulse = PPM_OUT_HIGH_PULSE_US;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
	TIM_OC3Init(PIOS_PPM_TIM, &TIM_OCInitStructure);
	TIM_OC3PreloadConfig(PIOS_PPM_TIM, TIM_OCPreload_Enable);

	TIM_ARRPreloadConfig(PIOS_PPM_TIM, ENABLE);

	// TIMER Main Output Enable
	TIM_CtrlPWMOutputs(PIOS_PPM_TIM, ENABLE);

	// TIM IT enable
	TIM_ITConfig(PIOS_PPM_TIM, PIOS_PPM_OUT_TIM_CCR, ENABLE);

	// Clear TIMER Capture compare interrupt pending bit
	TIM_ClearITPendingBit(PIOS_PPM_TIM_PORT, PIOS_PPM_IN_TIM_CCR);

	// Enable timer
	TIM_Cmd(PIOS_PPM_TIM, ENABLE);

#ifdef PPM_DEBUG
	DEBUG_PRINTF("ppm_out: initialised\r\n");
#endif
}

// TIMER capture/compare interrupt
void PIOS_PPM_OUT_CC_IRQ(void)
{
	// clear the interrupt
	TIM_ClearITPendingBit(PIOS_PPM_TIM_PORT, PIOS_PPM_OUT_TIM_CCR);

	if (booting || ppm_initialising)
		return;

	// *************************
	// update the TIMER period (channel pulse width)

	if (ppm_Out_ChannelPulseIndex < 0)
	{	// SYNC PULSE
		TIM_SetAutoreload(PIOS_PPM_TIM, ppm_Out_SyncPulseWidth - 1);    // sync pulse length
		ppm_Out_SyncPulseWidth = PPM_OUT_FRAME_PERIOD_US;               // reset sync period

		if (ppm_Out_ChannelsUsed > 0)
			ppm_Out_ChannelPulseIndex = 0;                              // onto channel-1
	}
	else
	{	// CHANNEL PULSE
		uint16_t pulse_width = ppm_Out_ChannelPulseWidth[ppm_Out_ChannelPulseIndex];
		if (pulse_width < PPM_OUT_MIN_CHANNEL_PULSE_US) pulse_width = PPM_OUT_MIN_CHANNEL_PULSE_US;
		else
		if (pulse_width > PPM_OUT_MAX_CHANNEL_PULSE_US) pulse_width = PPM_OUT_MAX_CHANNEL_PULSE_US;

		TIM_SetAutoreload(PIOS_PPM_TIM, pulse_width - 1);              // channel pulse width
		ppm_Out_SyncPulseWidth -= pulse_width;                         // maintain constant PPM frame period

		// TEST ONLY
//		pulse_width += 4;
//		if (pulse_width > 2000) pulse_width = 1000;
//		ppm_Out_ChannelPulseWidth[ppm_Out_ChannelPulseIndex] = pulse_width;

		ppm_Out_ChannelPulseIndex++;
		if (ppm_Out_ChannelPulseIndex >= ppm_Out_ChannelsUsed || ppm_Out_ChannelPulseIndex >= PIOS_PPM_MAX_CHANNELS)
			ppm_Out_ChannelPulseIndex = -1;                            // back to SYNC pulse
	}

	// *************************
}

// *************************************************************
// TIMER capture/compare interrupt

void PIOS_PPM_CC_IRQ_FUNC(void)
{
	if (ppm_mode == MODE_PPM_TX)
		PIOS_PPM_IN_CC_IRQ();
	else
	if (ppm_mode == MODE_PPM_RX)
		PIOS_PPM_OUT_CC_IRQ();
	else
		TIM_ClearITPendingBit(PIOS_PPM_TIM_PORT, PIOS_PPM_IN_TIM_CCR | TIM_IT_Update); // clear the interrupts
}

// *************************************************************
// can be called from an interrupt if you wish
// call this once every ms

void ppm_1ms_tick(void)
{
	if (booting || ppm_initialising)
		return;


}

// *************************************************************
// return a byte for the tx packet transmission.
//
// return value < 0 if no more bytes available, otherwise return byte to be sent

int16_t ppm_TxDataByteCallback(void)
{
	return -1;
}

// *************************************************************
// we are being given a block of received bytes
//
// return TRUE to continue current packet receive, otherwise return FALSE to halt current packet reception

bool ppm_RxDataCallback(void *data, uint8_t len)
{
	return true;
}

// *************************************************************
// call this from the main loop (not interrupt) as often as possible

void ppm_process(void)
{
	if (booting || ppm_initialising)
		return;

	if (ppm_mode == MODE_PPM_TX)
	{
		if (ppm_In_NewFrame() > 0)
		{	// we have a new PPM frame to send

			#ifdef PPM_DEBUG
				DEBUG_PRINTF("ppm_in: %5u %5u ..", ppm_In_Frames, ppm_In_ErrorFrames);
			#endif

			for (int i = 0; i <	PIOS_PPM_MAX_CHANNELS && i < ppm_In_ChannelsDetected; i++)
			{
//				int32_t pwm = ppm_In_GetChannelPulseWidth(i);

				#ifdef PPM_DEBUG
					DEBUG_PRINTF(" %4u", ppm_In_GetChannelPulseWidth(i));
				#endif

				// TODO:
			}

			#ifdef PPM_DEBUG
				DEBUG_PRINTF("\r\n");
			#endif
		}
	}
	else
	if (ppm_mode == MODE_PPM_RX)
	{
		// TODO:
	}
}

// *************************************************************

void ppm_deinit(void)
{
	ppm_initialising = true;

	ppm_mode = 0;

	// disable timer
	TIM_Cmd(PIOS_PPM_TIM, DISABLE);

	// Disable timer clock
	PIOS_PPM_TIMER_DIS_RCC_FUNC;

	// TIM IT disable
	TIM_ITConfig(PIOS_PPM_TIM, PIOS_PPM_IN_TIM_CCR | PIOS_PPM_OUT_TIM_CCR, DISABLE);

	// TIMER Main Output Disable
	TIM_CtrlPWMOutputs(PIOS_PPM_TIM, DISABLE);

	// un-remap the PPM pins
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, DISABLE);

	// Disable timer interrupt
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PIOS_IRQ_PRIO_MID;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE;
	NVIC_InitStructure.NVIC_IRQChannel = PIOS_PPM_TIM_IRQ;
	NVIC_Init(&NVIC_InitStructure);

	ppm_initialising = false;
}

void ppm_init(uint32_t our_sn)
{
	ppm_deinit();

	ppm_initialising = true;

	ppm_mode = saved_settings.mode;

	#if defined(PPM_DEBUG)
		DEBUG_PRINTF("\r\nPPM init\r\n");
	#endif

	if (ppm_mode == MODE_PPM_TX)
	{
		ppm_In_Init();
		rfm22_init_tx_stream(saved_settings.min_frequency_Hz, saved_settings.max_frequency_Hz);
	}
	else
	if (ppm_mode == MODE_PPM_RX)
	{
		ppm_Out_Init();
		rfm22_init_rx_stream(saved_settings.min_frequency_Hz, saved_settings.max_frequency_Hz);
	}

	rfm22_TxDataByte_SetCallback(ppm_TxDataByteCallback);
	rfm22_RxData_SetCallback(ppm_RxDataCallback);

    rfm22_setFreqCalibration(saved_settings.rf_xtal_cap);
	rfm22_setNominalCarrierFrequency(saved_settings.frequency_Hz);
	rfm22_setDatarate(saved_settings.max_rf_bandwidth, FALSE);
	rfm22_setTxPower(saved_settings.max_tx_power);

	if (ppm_mode == MODE_PPM_TX)
		rfm22_setTxStream();

	ppm_initialising = false;
}

// *************************************************************