Which TIMers are used by the Photon firmware/libraries?

I am about to start working on an application for a Photon where I need to use some timers and interrupts.
Because repeatable response times are part of the accuracy, I need direct access to the stm32 TIMers.

I am aware that the firmware uses some of these timers as well. To prevent me from using firmware timers, which timers cannot be used by an application? (or to be more precise: which timers are used for what firmware/library function). I have browsed through the firmware sources from github, but it’s hard to find your way around if your not familiar with it.

I’d rather have the OTA firmware flash functionality and the RGB led indications stay as they are provided, and the firmware’s sleep function should remain as well.

@raytje, what kind of timing do you need - microseconds, milliseconds? How much overhead to you anticipate in your ISR? Can your application tolerate some jitter on the timer interrupts?

For reference to, here is the Photon pin allocation also showing which timers are associated with pin functions:

https://docs.particle.io/datasheets/photon-datasheet/#pin-out-diagrams

Its a cyclic task, and within this task I need to capture precisely (100ns precision) the time of an external signal. At this event I also need to start a reproducable delay of 12 us before starting an ADC conversion. I was thinking of using a timer of 12 us generating an interrupt to start the ADC.

The timers mentioned in the documentation are not used by the photon firmware?

100ns (10MHz) is rather short. That’d be the time of 12 clock cycles on a 120MHz µC and with the current interrupt latency introduced by the HAL you won’t get anywhere near that precision.
12µs on the other hand should be doable (but with a uncertainty of several 100ns).

So you’d definetly need to get below the HAL for that.

@raytje, besides what @ScruffR said, the pin-out shows GPIO pins associated with TIM2, 3, 4 and 5 channels. So if you use these timers for interrupts, you will affect the associated pins’ functions if they are used. Hardware timers 6-14 on the STM32F205 are not use or associated with pins.

I am presently (among other things), testing code to bypass the HAL interrupt layer to avoid the associated latencies (5-6us).

1 Like

@peekay123, I am planning to use two counters in a master/slave configuration: an event of the master timer triggers the input of the next timer. In this configuration, there is only one input pin required for the master timer (input capture mode). Both counters are connected through their master/slave configuration. From the slave counter output event (realizing a simple fixed delay), an ADC conversion is triggered. The ADC requires another (analog) input pin.
By this configuration, an input pulse triggers an input capture of the master timer and simulatenously starts the delay of the slave timer (in one-pulse mode). When the delay of the slave timer is finished, the ADC conversion is triggered.
So, at the input pulse, the timing is captured and after a fixed delay, the ADC is started.

Configuration of master/slave is only possible for timers 1, 2, 3, 4, 5 and 8 (9 and 12 have limited master/slave capabilities which I cannot use).

Because of only 2 pins required for this configuration, the available pins of the Photon are not really the limitation. The real limitation lies within the timers that are used by the system software to provide basic functionality like OTA update, RGB led PWMs, sleep function etc. I need to avoid the use of the timers that are used by the system software.

So I would like to know which timers are used in the system software, seen from the system timing related functions (and not seen from the pins).

@raytje, again referring to the pinout diagram for the Photon, TIM2 is used for the RGB LEDs. You can use TIM1, 3, 4 and 5 understanding that their associated pin functions will be affected but only if they are being used (eg PWM, Tone). Have you looked at TIM6 and TIM7 for the delay & trigger of the ADC (I ask because I haven’t looked)?

@peekay123, unfortunately TIM6/7 are not equiped with extensive master/slave circuits that enable timer cascading, as are timers 1,2,3,4,5 and 8.

So I think for now, that I’ll try to implement it with TIM3_CH1 as input capture. The (internal) TRGO output of TIM3 connects to TRGI input of TIM4, starting a delay in one-pulse mode. The ADC is then to start a conversion through the TIM4_TRGO event.

Thanks for your help!

1 Like

Dear Raytje,

I’d love to get a copy of your code to learn how you did it. Feel free to remove any proprietary code. I just want to see how you worked with the timers and how you got the ISR to work properly. I’m assuming you read the ADC outside of the ISR so I’l like to learn how to do that too.

Regards, Jeff

Hi Jeff,

Please find the code below. It is the complete software (.ino file) as is. I think the comments are pretty self-explanatory.
I used the STM32 library functions which are used by the Photon formware as well.
You can find a library description at http://www.han-ese.nl/STM32/stm32f2stdlibrary/html/index.html
and the STM32 documents UM1061Description of STM32F2xx Standard Peripheral Library, RM0033 Reference manual, AN4013 Application note, and numerous others come in very handy. Just google for STM32F2X and timer, adc and so on
I had some lines removed, due to max size of posts…

Have fun!

Ray

// ======================================================================

#include "Particle.h"

#define __NO_DEBUG_IRQ5
#define __NO_DEBUG_IRQ3
#define __NO_DEBUG_IRQ4
#define __NO_DEBUG_IRQ_ADC


// Definition of pins
#define ADR0 D4
#define ADR1 D5
#define ADR2 D6
#define ADR3 D7



// Clock frequency of the timers, needed for timing calculations
#define TIMCLK 30000000UL


#define NROFAISAMPLES 60


#define BASECYCLE             1.25E-3
#define HALF40KHZPERIOD      12.50E-6
#define NROFHALFPULSES       16
#define ENDPEAKDETECTPERIOD   1.10E-3
#define ENABLERECEIVER        0.30E-3

// Zero Crossing to peak is one quarter of the 40kHz period time = 6.25 us
// For some reason, this value can not be very small: the STM locks
// too short time between interrupts?
// When using IRQ for debugging, this value must be 10 times bigger
#define ZCD2PEAKDELAY 6.25E-6

// Pulse width of ADC Start conversion. Not critical, so 1 us
#define ADCSTARTPULSEWIDTH 1E-6

// Constant to reduce TIM5 ISR processing time
const uint16_t g_Half40kHzCnt = HALF40KHZPERIOD * TIMCLK;
const uint16_t g_End40kHzCnt = 10 * g_Half40kHzCnt;
const uint16_t g_EndPDPeriod = ENDPEAKDETECTPERIOD * TIMCLK;
const uint16_t g_EnableReceiver = ENABLERECEIVER * TIMCLK;

const uint16_t g_TIM4_Period = (ZCD2PEAKDELAY + ADCSTARTPULSEWIDTH) * TIMCLK;
const uint16_t g_TIM4_Pulse = ZCD2PEAKDELAY * TIMCLK;


// Temporary: for testing only
uint16_t g_TIM3CNT = 0;
uint g_NewTIM3CNT = 0;
uint16_t g_TIM4CNT = 0;
uint g_NewTIM4CNT = 0;
uint16_t g_TIM8CNT = 0;
uint g_NewTIM8CNT = 0;
uint g_ADCValue = 0;
uint16_t g_ADCCNT = 0;
uint g_ADC = 0;
// ======================================================================

void setup() {
    for (int l_Cnt = 0; l_Cnt<NROFAISAMPLES; l_Cnt++)
    {
        g_CCZeroDValue[l_Cnt] = 2;
        g_RawPeakValue[l_Cnt] = 1;
    }    

    GPIO_InitTypeDef GPIO_InitStructure;
	DMA_InitTypeDef DMA_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_OCInitTypeDef TIM_OCInitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	ADC_CommonInitTypeDef ADC_CommonInitStruct;
	ADC_InitTypeDef ADC_InitStruct;

    // ---------------------------------------------
    // TIM_TimeBaseInit writes registers:
    // - TIMx->CR1   : DIR,CMS,CounterMode
    // - TIMx->ARR   : Period
    // - TIMx->PSC   : Prescaler
    // - TIMx->EGR   : PreloadMode Immediate
    
    // TIM_ICInit writes registers:
    // - TIMx->CCMR1 : CC1S,IC1F,ICSelection,ICFIlter
    // - TIMx->CCER  : CC1P,CC1NP,ICPolarity,CC1E

    // TIM_OC1Init writes registers:
    // - TIMx->CCMR1 : OCMode,
    // - TIMx->CCR1  : Pulse
    // - TIMx->CCER  : CC1E, OCPolarity,OutputState
    
    // TIM_OC1PreloadConfig writes registers:
    // - TIMx->CCMR1 : OC1PE,OCPreload
    
    // TIM_ARRPreloadConfig writes registers:
    // - TIMx->CR1   : ARPE

    // TIM_SelectOnePulseMode writes registers:
    // - TIMx->CR1   : OPMode
    
    // TIM_GenerateEvent writes registers:
    // - TIMx->EGR   : (argument)
    
    // TIM_SelectOutputTrigger writes registers:
    // - TIMx->CR2   : MMS
    
    // TIM_SelectMasterSlaveMode writes registers:
    // - TIMx->SMCR  : MSM
    
    // TIM_SelectInputTrigger writes registers:
    // - TIMx->SMCR  : TS 
    
    // TIM_SelectSlaveMode writes registers:
    // - TIMx->SMCR  : SMS
    
    // ---------------------------------------------
	// ADC_CommonInit writes registers:
    // - ADC->CCR    : MULTI, DELAY, DMA, ADCPRE

	// ADC_Init writes registers:
    // - ADC->CR1    : RES, SCAN
    // - ADC->CR2    : CONT, ALIGN, EXTEN, EXTSEL
    // - ADC->SQR1   : L

	// ADC_RegularChannelConfig writes registers:
    // - ADC->SMPR1/2    :
    // - ADC->SQR1/2/3   : 

    // ADC_DMARequestAfterLastTransferCmd writes registers:
    // - ADC->CR2    : ADC_CR2_DDS

    // ADC_DMACmd writes registers:
    // - ADC->CR2    : ADC_CR2_DMA

	// ADC_ClearITPendingBit writes registers:
    // - ADC->SR     : IT mask

	// ADC_ITConfig writes registers:
    // - ADC->CR1    : IT mask

	// ADC_Cmd writes registers:
    // - ADC->CR2    : ADON
	



    
    // Timer 5: - pulse train of 40 kHz
    //          - cyclic trigger 10 ms
    // Timer 3: - input capture for zero detector
    // Timer 4: - delay between input capture and ADC conversion
    // ADC1:    - sample analog peak of signal
    // DMA1:    - input capture counter value to memory
    //          - ADC conversion result to memory

    // Setup of timers according:
    // UM1601 Description of STM32F2xx Standard Peripheral Library
    // - Input capture, section 25.2.3, page 525

    // Enable periherals
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

    // Set pins IO mode
    HAL_Pin_Mode(ADR0,OUTPUT);
    HAL_Pin_Mode(ADR1,OUTPUT);
    HAL_Pin_Mode(ADR2,OUTPUT);
    HAL_Pin_Mode(ADR3,OUTPUT);



    //Assign pins to their alternate functions
    // Pin WKP, TIM5_Ch1: 40 kHz pulse
    HAL_Pin_Mode(WKP,AF_OUTPUT_PUSHPULL);
    // Alternate Function AF2 on pin PA0-WKUP
    GPIO_PinAFConfig(PIN_MAP[WKP].gpio_peripheral,PIN_MAP[WKP].gpio_pin_source,GPIO_AF_TIM5);
    
    // Pin D3, TIM3_Ch1: Zero Cross Detector
    HAL_Pin_Mode(D3,INPUT);
    // Set the input pin D3 to GPIO_Mode_AF
    // D3 (B4) PINMAP: GPIOB, GPIO_Pin_4, GPIO_PinSource4, NONE, NONE, TIM3, TIM_Channel_1, PIN_MODE_NONE, 0, 0 },
    GPIO_InitStructure.GPIO_Pin = PIN_MAP[D3].gpio_pin;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // Alternate Function AF on pin PB4-D3
    GPIO_PinAFConfig(PIN_MAP[D3].gpio_peripheral,PIN_MAP[D3].gpio_pin_source,GPIO_AF_TIM3);

    // Pin D2, TIM3_Ch2: Breather LED
    HAL_Pin_Mode(D2,AF_OUTPUT_PUSHPULL);
    // Set the input pin D2 to GPIO_Mode_AF
    // D2 (B5) PINMAP: GPIOB, GPIO_Pin_5, GPIO_PinSource5, NONE, NONE, TIM3, TIM_Channel_2, PIN_MODE_NONE, 0, 0 },
//    GPIO_InitStructure.GPIO_Pin = PIN_MAP[D2].gpio_pin;
//    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
//    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // Alternate Function AF on pin PB4-D3
    GPIO_PinAFConfig(PIN_MAP[D2].gpio_peripheral,PIN_MAP[D2].gpio_pin_source,GPIO_AF_TIM3);

    // Pin A0, ADC1: Peak signal
    HAL_Pin_Mode(A0,AN_INPUT);
    // A0 (C5) PINMAP: GPIOC, GPIO_Pin_5, GPIO_PinSource5, ADC_Channel_15, NONE, NULL, NONE, PIN_MODE_NONE, 0, 0 },

    // ======================================================================
    // == Configuration of DMA 1, channel 0 (connected to the TIM3.CC1)    ==
    // ======================================================================
    // - used for transfering Capture Counter of TIM3 to a buffer in memory
    // - each zero detect leads to TIM3.CC1
    // - on this event, the DMA transfers the contents of the dataregister TIM3.CC1
    // - to the g_CCZeroDValue buffer in memory, incrementing the pointer
    // - no burst (=> single)
    // - no FIFO, since data (half word, 16 bits) is generated every 25 us only
    // - enable of TIM3.CC1 after 40 kHz pulses are generated (trigger by TIM5.CC2)
    // - disable after a period of i.e. 1 ms, pulse are dimished by then (trigger by TIM5.CC3)
	DMA_InitStruct.DMA_Channel = DMA_Channel_5;
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStruct.DMA_PeripheralBaseAddr = 0x40000434; // TIM3->CCR1; // 0x40000434; // TIM3 CCR1 address
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)g_CCZeroDValue;
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStruct.DMA_BufferSize = NROFAISAMPLES;
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStruct.DMA_Priority = DMA_Priority_High;
	DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
	DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_Init(DMA1_Stream4, &DMA_InitStruct);

    // ======================================================================
    // == Configuration of DMA 2, stream 0, channel 0 (connected to ADC)   ==
    // ======================================================================
    // - used for transfering ADC conversion data to a buffer in memory
    // - each peak is sampled by the ADC
    // - once EoC, the DMA transfers the contents of the dataregister ADC->DR
    // - to the g_RawPeakValue buffer in memory, incrementing the pointer
    // - no burst (=> single)
    // - no FIFO, since data (half word, 16 bits) is generated every 25 us only
    // - enable of ADC/DMA after 40 kHz pulses are generated (trigger by TIM5.CC2)
    // - disable after a period of i.e. 1 ms, pulse are dimished by then (trigger by TIM5.CC3)
	DMA_InitStruct.DMA_Channel = DMA_Channel_0;
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStruct.DMA_PeripheralBaseAddr = 0x4001204C; // ADC1->DR; // 0x4001204C; // DR ADDRESS; ADC1
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)g_RawPeakValue;
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStruct.DMA_BufferSize = NROFAISAMPLES;
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStruct.DMA_Priority = DMA_Priority_High;
	DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
	DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_Init(DMA2_Stream0, &DMA_InitStruct);

	// DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE);

    // Do not enable DMA now, but in ISR
    // DMA_Cmd(DMA2_Stream0, ENABLE);


    // ======================================================================
    // == Configuration of Timer 5                                         ==
    // ======================================================================
    // - used for generating 40kHz pulse and cyclic timing of measurement 
    //   and calculations
    // - resets the counter of TIM3 at an exact moment in the timing
    // - IRQ to handle:
    //   - 40 kHz pulse generation
    //   - wait for measurements
    //   - start calculations
    //   - handle overall measurement sequence and calculation  
    // - Timer in Output Compare mode, with a period (eg 1 ms) a multitude of
    //   one 40kHz period.
    // - OC1 toggles every 12.5 us and is reprogrammed for the next period
    //   After a fixed number of times (eg 8 pulses), OC1 is reprogrammed for
    //   the next period of 1 ms
    // - OC2 is programmed to signal TIM3 to reset the counter of the input
    //   capture
    // - OC3 can be used to interrupt and signal a calculation task after the
    //   measuring period. It can also be used to disable the input capturing
    // - OC4 can be used to pulse a external pin, as to trigger a scope.

	// - First enable interrupt for TIM5
	NVIC_InitStruct.NVIC_IRQChannel = TIM5_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);

    // - max counter resolution, clock = 30 Mhz
	// - 30000000UL = 30 MHz Timer Clock = HCLK / 4
    // - counting up, measure time from zero count 
    // - Period length 1.25 ms: 1.25E-3 * 30000000 = 37500
    // - OC1 every 12.5 us interrupt: 1.25E-5 * 30000000 = 375
    //   16 times an interrupt at n * 375 an interrupt delivers 8 pulses
    // - OC2 signals TIM3 at the end of the OC1 pulse train.
	TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
	TIM_TimeBaseStruct.TIM_Period = (BASECYCLE * TIMCLK);
	TIM_TimeBaseStruct.TIM_Prescaler = 1;
	TIM_TimeBaseStruct.TIM_ClockDivision = 0;
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStruct);

	// - OC1: Output Compare mode with toggle when creating a pulse.
	// - The first interrupt takes place at half the 40 kHz pulse
	// - Polarity low: pulses start high and go low, this enables use of 74ls139 (not)G input  
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_Toggle;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = NextOC1Capture(g_HalfPulseCnt);
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
	TIM_OC1Init(TIM5, &TIM_OCInitStruct);

	// - OC2: Output Compare mode with PWM2 (first low, then high when CC2 is reached).
    // - Force OC output low, also to be done at the start of each 1.2 ms cycle
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = g_End40kHzCnt;
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC2Init(TIM5, &TIM_OCInitStruct);

	// - OC3: Output Compare mode with PWM2 (first low, then high when CC3 is reached).
    // - Force OC output low, also to be done at the start of each 1.2 ms cycle
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = g_EndPDPeriod;
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC3Init(TIM5, &TIM_OCInitStruct);

	// - OC4: Output Compare mode with PWM2 (first low, then high when CC3 is reached).
    // - Force OC output low, also to be done at the start of each 1.2 ms cycle
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = g_EnableReceiver;
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC4Init(TIM5, &TIM_OCInitStruct);

	// - Take over new capture value immediately (it is written in the interrupt handler)
    TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Disable);

    // - Output TRGO signal to next timer when a capture takes place
    // - OC2 (end of 40kHz pulses) triggers TRGO
    // - enable master/slave operation between timer and slave timer
	TIM_SelectOutputTrigger(TIM5, TIM_TRGOSource_OC2Ref);
	TIM_SelectMasterSlaveMode(TIM5, TIM_MasterSlaveMode_Enable);

    // - clear and release Update and CC1 interrupts
    TIM_ClearITPendingBit(TIM5,TIM_IT_Update | TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4);
    TIM_ITConfig(TIM5,TIM_IT_Update | TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4,ENABLE);

    // TIM enable counter
    TIM_Cmd(TIM5, ENABLE);

    // ======================================================================
    // == Configuration of Timer 3                                         ==
    // ======================================================================
    // - input capture on Zero Cross Detector input TIM_Ch1, pin D3
    // - max counter resolution, clock = 30 Mhz
	// - 30000000UL = 30 MHz Timer Clock = HCLK / 4
	//   With maximum time of 1.25 ms, the 16-bit counter will reach 37500
	//   Timer period is set to maximum
    // - counting up, measure time from zero count 
	TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
	TIM_TimeBaseStruct.TIM_Period = 65535;
	TIM_TimeBaseStruct.TIM_Prescaler = 1;
	TIM_TimeBaseStruct.TIM_ClockDivision = 0;
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);

    // - capture rising edge on TIM3_Ch1, pin D3
    // - no input capture filters or prescalers
    // - direct connection: Channel1 to TI1
    TIM_ICStructInit(&TIM_ICInitStruct);
    TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStruct.TIM_ICFilter = 0x0;
    TIM_ICInit(TIM3, &TIM_ICInitStruct);
    
	// - OC2: Output Compare mode with PWM2 (first low, then high when CC2 is reached).
    // - Force OC output low, also to be done at the start of each 1.2 ms cycle
    // - max count = 37500 - TIM5->TIM3 delay.
    // - At 1.25 ms cycle start, output = low
    // - Only after TIM3 start, setting high is possible.
    // - CC2 value ranges from 0 (max LED) to 37500-TIM5->TIM3 delay (LED off)
    // - Initially LED is set off
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = 37500; 
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC2Init(TIM3, &TIM_OCInitStruct);

	// - Take over new capture value immediately (it is written in the interrupt handler)
//    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable);

	// - counter enabled when gated from TIM5 (OC2Ref)
	// - (counter is reset at start of TIM5 1.25 ms period by TIM5 ISR)
	// - counter is running when TIM5_OC2 reaches its CC value,
	//   so TIM3 capture counter is always referenced to TIM5 1.25 ms period
    TIM_SelectInputTrigger(TIM3, TIM_TS_ITR2);
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Gated);
    
    // - Output TRGO signal to next timer when a capture takes place
    // - enable master/slave operation between timer and slave timer
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_OC1);
	TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);

    // - Enable DMA trigger on CC1 
    TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);

#ifdef __DEBUG_IRQ3
    // - clear and release CC1 interrupts
    NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
    TIM_ClearITPendingBit(TIM3,TIM_IT_CC1);
    TIM_ITConfig(TIM3,TIM_IT_CC1,ENABLE);
#endif
    // TIM enable counter
    TIM_Cmd(TIM3, ENABLE);


    // ======================================================================
    // == Configuration of Timer 4                                         ==
    // ======================================================================
    // - to be triggered by an rising edge on TIM3 TRGO output
    // - after 6.25 us, TIM4 OC4Ref must be set
    // - 1us later, the OC4Ref output can drop
    // - input capture on Zero Cross Detector input TIM_Ch1, pin D3

    // - max counter resolution, clock = 30 Mhz
	// - 30000000UL = 30 MHz Timer Clock = HCLK / 4
    // - counting up, measure time from zero count 
    // - Pulse length: CCR register (TIM_OCInitStructure.TIM_Pulse): 6.25E-6 * 30000000 = 187
    // - Pulse+Delay length: The ARR register (TIM_TimeBaseStructure.TIM_Period): 7.25E-6 * 30000000 = 217
	TIM_TimeBaseStruct.TIM_Period = g_TIM4_Period;
	TIM_TimeBaseStruct.TIM_Prescaler = 1;
	TIM_TimeBaseStruct.TIM_ClockDivision = 0;
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStruct);
	
	// - One Pulse is a special case of PWM mode
	// - Set CC4 output enable (CC4E), to trigger ADC
	// - Set CC4 output polarity active high (CC4P), to trigger ADC
	// - PWM2: first low, then high
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = g_TIM4_Pulse;
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC4Init(TIM4, &TIM_OCInitStruct);

	// - One single pulse when triggered
	TIM_SelectOnePulseMode(TIM4, TIM_OPMode_Single);

	// - counter starts when triggered from TIM3 TRGO
	TIM_SelectInputTrigger(TIM4, TIM_TS_ITR2);
    TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Trigger);

#ifdef __DEBUG_IRQ4
    // clear and release CC4 interrupt
    NVIC_InitStruct.NVIC_IRQChannel = TIM4_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
	TIM_ClearITPendingBit(TIM4,TIM_IT_CC4);
    TIM_ITConfig(TIM4,TIM_IT_CC4,ENABLE);
#endif
    // TIM enable counter
    TIM_Cmd(TIM4, ENABLE);

    // ======================================================================
    // == Configuration of ADC                                             ==
    // ======================================================================
    // - to be triggered by a rising edge of TIM4_Ch4 output
    // - sample A0 analog input
    // - at EOC, DMA stores the result in memory

    // - Single ADC mode
    // - Sample at 30MHz clock (prescaler = 2)
    // - For now, no DMA
    // - No delay between ADC1 and ADC2 required
    
    ADC_DeInit();
    
	ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;
	ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div2;
	ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
	ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
	ADC_CommonInit(&ADC_CommonInitStruct);

    // - 12 bits resolution (=> 15 clock cycles = 0.5 us)
    // - Single (one channel) scan mode
    // - Single scan, not continuous
    // - Trigger converion on a rising edge
    // - Trigger from TIM4_CC4
    // - Right aligned data, no scaling
    // - 1 conversion
	ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
	ADC_InitStruct.ADC_ScanConvMode = ENABLE;
	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStruct.ADC_NbrOfConversion = 1;
	ADC_Init(ADC1, &ADC_InitStruct);

    // - Setup A0 input channel as single channel, with highest speed sampling
    // PIN_MAP[A0] - 10  { GPIOC, GPIO_Pin_5, GPIO_PinSource5, ADC_Channel_15, NONE, NULL, NONE, PIN_MODE_NONE, 0, 0 },
	ADC_RegularChannelConfig(ADC1, PIN_MAP[A0].adc_channel, 1, ADC_SampleTime_15Cycles);

#ifdef __DEBUG_IRQ_ADC
    // clear and release EoC interrupt
    NVIC_InitStruct.NVIC_IRQChannel = ADC_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
	ADC_ClearITPendingBit(ADC1,ADC_IT_EOC);
	ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE);
#else
    // Enable DMA
    // only possible when no interrupts are generated
	ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);
#endif
    // Do not enable ADC now, but in the TIM5.CC2 interrupt routine
    // ADC_Cmd(ADC1, ENABLE);

    pinMode(D7, OUTPUT);
    attachSystemInterrupt(SysInterrupt_TIM5_IRQ, TIM5_IRQHandler);
#ifdef __DEBUG_IRQ3
    attachSystemInterrupt(SysInterrupt_TIM3_IRQ, TIM3_IRQHandler);
#endif
#ifdef __DEBUG_IRQ4
    attachSystemInterrupt(SysInterrupt_TIM4_IRQ, TIM4_IRQHandler);
#endif
#ifdef __DEBUG_IRQ_ADC
    attachSystemInterrupt(SysInterrupt_ADC_IRQ, ADC_IRQHandler);
#endif

    // Debugging
    Serial.begin(9600);         // Open serial over USB.
    delay(5000);
    Serial.println("Spark Core or Photon says Hello over USB! ");
}
// ======================================================================

void TIM5_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)
    {
#ifdef __DEBUG_IRQ5
        g_CCR[g_HalfPulseCnt] = TIM3->CNT;
        g_BaseCycleSema++;
#endif
        // This fires every 12.5 us (HALF40KHZPERIOD define), but only when OC1 is captured
        // Set new capture value
        g_HalfPulseCnt++;
        TIM_SetCompare1(TIM5,NextOC1Capture(g_HalfPulseCnt));

        // Clear interrupt
        TIM_ClearITPendingBit(TIM5, TIM_IT_CC1);
    }
    if (TIM_GetITStatus(TIM5, TIM_IT_CC2) != RESET)
    {
        TIM_ClearITPendingBit(TIM5, TIM_IT_CC2);
        
        // Start of Pulse Detect Period, reset and then enable DMA
        DMA_ClearFlag(DMA1_Stream4, DMA_IT_TCIF4);
        DMA_ClearFlag(DMA2_Stream0, DMA_IT_TCIF0);
        DMA_Cmd(DMA1_Stream4,ENABLE);
        DMA_Cmd(DMA2_Stream0,ENABLE);
    }
    if (TIM_GetITStatus(TIM5, TIM_IT_CC3) != RESET)
    {
#ifdef __DEBUG_IRQ5
        g_CC3 = TIM3->CNT;
        g_CC3Sema++;
#endif
        TIM_ClearITPendingBit(TIM5, TIM_IT_CC3);
        
        // End of the Pulse Detect Period, disable DMA
        DMA_Cmd(DMA1_Stream4,DISABLE);
        DMA_Cmd(DMA2_Stream0,DISABLE);
        DMA_ClearFlag(DMA1_Stream4, DMA_IT_TCIF4);
        DMA_ClearFlag(DMA2_Stream0, DMA_IT_TCIF0);
        
        // Now process raw captured data
        ProcessRawCapturedData();
        // and prepare new measurement run
        PrepareMeasurement();
    }
    if (TIM_GetITStatus(TIM5, TIM_IT_CC4) != RESET)
    {
        TIM_ClearITPendingBit(TIM5, TIM_IT_CC4);
        
        // Enable receiver
        digitalWriteFast(ADR2,g_RxChannel[g_ScanChannel] & 0x01);
        digitalWriteFast(ADR3,g_RxChannel[g_ScanChannel] & 0x02);
    }
    if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
    {
#ifdef __DEBUG_IRQ5
        g_CCR[0] = TIM3->CNT;
#endif
        // This fires every 1.25 ms (BASECYCLE define)
        // Set new capture value
        g_HalfPulseCnt=1;
        TIM_SetCompare1(TIM5,NextOC1Capture(g_HalfPulseCnt));

        // - Set TIM3 counter to zero
        TIM_SetCounter(TIM3,0);

        // Clear interrupt
        TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
        ProcessSystemTick();
        g_BaseCycleFlag=true;
    }
}
// ======================================================================
#ifdef __DEBUG_IRQ3
void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
    {
        // Captured!, read value
        g_TIM3CNT = TIM_GetCapture1(TIM3);

        TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
        g_NewTIM3CNT++;
    }
}
#endif
// ======================================================================
#ifdef __DEBUG_IRQ4
void TIM4_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM4, TIM_IT_CC4) != RESET)
    {
        // Captured!, read value
        g_TIM4CNT = TIM3->CNT;

        TIM_ClearITPendingBit(TIM4, TIM_IT_CC4);
        g_NewTIM4CNT++;
    }
}
#endif
// ======================================================================
#ifdef __DEBUG_IRQ_ADC
void ADC_IRQHandler(void)
{
    if (ADC_GetITStatus(ADC1, ADC_IT_EOC) != RESET)
    {
        // EoC!, read value
        g_ADCCNT = TIM3->CNT;
        g_ADCArr[g_ADCArrCnt] = g_ADCCNT;
        if (g_ADCArrCnt<14)
        {
            g_ADCArrCnt++;
        }
//        g_ADCValue = ADC_GetConversionValue(ADC1);

        ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
        g_ADC++;
    }
}
#endif
// ======================================================================