• Welcome to Andy's Workshop Forums. Please login or sign up.
 
April 23, 2024, 05:40:14 pm

News:

SMF - Just Installed!


Input Capture STM32F4Discovery

Started by mhel, May 27, 2016, 01:23:40 pm

Previous topic - Next topic

mhel

Hi All,

I'm trying to capture PWM signal from an RC servo receiver  but I'm having a hard time configuring the timer for input.
I change the example to use Timer3 CH1 on Port C6. At the moment I'm just trying to capture 1 channel but will be using 3 onced I get one running.

    typedef Timer3<
        Timer3InternalClockFeature,         // we'll need this for the frequency calculation
//        TimerInternalTriggerClockFeature<TIM_TS_TI1FP1>,
        TimerChannel1Feature<               // we're going to use channel 1
            TimerChannelICBothEdgesFeature,    // both edge trigger
            TimerChannelICDirectTiFeature,    // direct connection
            TimerChannelICPreScaler1Feature,  // prescaler of 1
            TimerChannelICFilterFeature<0>    // no filter
        >,
        Timer3InterruptFeature,           // we want to use interrupts
        Timer3GpioFeature<                // we want to read something from GPIO
          TIMER_REMAP_FULL,               // the GPIO input will be re-mapped
          TIM3_CH1_IN                     // we will read channel 1 from GPIO PC6
        >
      >MyInputTimer;


Some other examples I found suggests that I should set these:

  /* Select the TIM3 Input Trigger: TI1FP1 */
  TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);

  /* Select the slave Mode: Reset Mode */
  TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
  TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);

What am I missing with the timer configuration?
Better yet, if anyone has an example of capturing servo pwm from an RC receiver
it woulbe be greatly appreciated.

edit:
Forgot to mention I'm using the current lib from git.

mhel

I'm making progress...sort of. I can't seem to clear timer interrupt. It fires but it doesn't get cleared.
At-least that's what the gnu-arm register plugin says.
I'm calling this at the start of initialization before enabling the timer just to see if the flags are cleared, but it doesn't clear all.
CC1F,CC10F remains.

_myInputTimer->clearPendingInterruptsFlag(0xFF);



mhel

June 01, 2016, 08:44:52 am #2 Last Edit: June 01, 2016, 11:31:23 am by mhel
Hi All,

I made some more progress :-)
It's still has a glitch, it might be an overflow or likely a mistake.
Now how do I convert the captured pulse width to pwm duty cycle to drive my hbridge?

I'm posting it here for the google warriors like me.

class TimerInputCaptureTest {

  protected:

    /**
     * Declare a type for our input timer.
         */
        typedef TimerChannel1Feature<
                TimerChannelICBothEdgesFeature,
                TimerChannelICDirectTiFeature,
//            TimerChannelICPreScaler1Feature,
                TimerChannelICFilterFeature<0>
        > T1Features;

        typedef TimerChannel2Feature<
                TimerChannelICBothEdgesFeature,
                TimerChannelICDirectTiFeature,
//            TimerChannelICPreScaler1Feature,
                TimerChannelICFilterFeature<0>
        > T2Features;

       typedef Timer3<
               Timer3InternalClockFeature, // we'll need this for the frequency calculation
               T1Features,
               T2Features,
               Timer3InterruptFeature, // we want to use interrupts
               Timer3GpioFeature<    // we want to read something from GPIO
                  TIMER_REMAP_FULL, // the GPIO input will be re-mapped
                  TIM3_CH1_IN, // we will read channel 1 from GPIO PC6
                  TIM3_CH2_IN              // Channel 2 on GPIO PC7
                      >
        > MyInputTimer;

       /*
        * The timer needs to be a class member so we can see it from the callback
        */

       MyInputTimer *_myInputTimer;


    volatile uint8_t _indexCH1;
    volatile uint8_t _indexCH2;
    volatile uint32_t _captureCH1[2];
    volatile uint32_t _captureCH2[2];
    volatile uint32_t _ch1Out;
    volatile uint32_t _ch2Out;
    volatile uint32_t _duty1;
    volatile uint32_t _duty2;


    GpioE<DefaultDigitalOutputFeature<7> > LED;
  public:

    void run() {

      /*
       * Declare a USART1 object. Note that an alternative Usart1_Remap object is available
       * if your application demands that you use the alternate pins for USART1
       */

//      Usart1<> usart1(57600);
        Usart2_Remap1<> usart2(9600);

      /*
       * We'll use an output stream for sending to the port instead of using the
       * send(uint8_t) method on the usart object
       */

      UsartPollingOutputStream outputStream(usart2);

      /*
       * We'll use Timer 1 to generate a PWM signal on its channel 1.
       * The signal will be output on PA1
       */


      Timer1<
        Timer1InternalClockFeature,     // clocked from the internal clock
        TimerChannel1Feature<>,         // we're going to use channel 1
        Timer1GpioFeature<              // we want to output something to GPIO
          TIMER_REMAP_FULL,             // the GPIO output will not (cannot for this timer) be remapped
          TIM1_CH1_OUT                  // we will output channel 1 to GPIO (PA1)
        >
      > outputTimer;

      /*
       * Set the output timer to 1MHz with a reload frequency of 20Khz (1MHz/50).
       */


      outputTimer.setTimeBaseByFrequency(1000000,50-1);

      /* Timer runs at 168Mhz with reload frequency of 20Khz */
      // (((SystemCoreClock / 1000000) / 2) - 1)

      /*
       * Initialise the output channel for PWM output with a duty cycle of 50%. This will
       * give us a nice square wave for the input capture channel to sample.
       */

      outputTimer.initCompareForPwmOutput(50);

      /*
       * Declare a new instance of the input capture timer.
       */

      _myInputTimer=new MyInputTimer;


//      _myInputTimer->setTimeBaseByFrequency(4000000,4-1);

      /* Timer3 runs at 84Mhz on APB1
       * This sets up a free running timer that ticks at 1MHz
       */

//      (uint16_t) (((SystemCoreClock / 1000000) / 2) - 1); // Shooting for 1 MHz, (1us)
        _myInputTimer->initialiseTimeBase(0xFFFF,                 // Auto-reload
                                            84-1,                 // Prescaler
                                            0,                    // Division
                                            TIM_CounterMode_Up    // Counting up
                                           );

     /*
       * Insert our subscribtion of the capture interrupts generated by the input timer
       */

      _myInputTimer->TimerInterruptEventSender.insertSubscriber(
          TimerInterruptEventSourceSlot::bind(this,&TimerInputCaptureTest::onInterrupt)
        );


      /*
       * Reset the variables used to hold the state
       */

      _indexCH1=0;
      _indexCH2=0;
//      _capturingNextFrequency=true;


      _captureCH1[0] = 0;
      _captureCH2[1] = 0;
      _ch1Out = 0;
      _ch2Out = 0;
      _duty1 = 0;
      _duty2 = 0;

      /*
       * Enable channel 4 interrupts on Timer 3.
       */

            _myInputTimer->clearPendingInterruptsFlag(0xFF);
            _myInputTimer->enableInterrupts(TIM_IT_CC1 | TIM_IT_CC2);


      /*
       * Enable both timers to start the action
       */

      outputTimer.enablePeripheral();
      _myInputTimer->enablePeripheral();


            for (;;) {

                char buf1[15];

                LED[7].reset();

                if ( (_captureCH1[1] - _captureCH1[0]) > 700) {

                    if ( (_captureCH1[1] - _captureCH1[0]) < 2300)
                    _ch1Out = _captureCH1[1] - _captureCH1[0];

                }
                    _captureCH1[0] = _captureCH1[1];

                if ((_captureCH2[1] - _captureCH2[0]) > 700) {

                    if ( (_captureCH2[1] - _captureCH2[0]) < 2300)
                    _ch2Out = _captureCH2[1] - _captureCH2[0];

                }
                     _captureCH2[0] = _captureCH2[1];

                StringUtil::modp_uitoa10(_ch1Out, buf1);
                outputStream << buf1 << " Ch1Out ";
                StringUtil::modp_uitoa10(_ch2Out, buf1);
                outputStream << buf1 << " Ch2Out\r\n";

                MillisecondTimer::delay(500);
                LED[7].set();

            }
    }


    /*
     * Interrupt callback function. This is called when the input capture
     * event is fired
     */

        void onInterrupt(TimerEventType tet, uint8_t /* timerNumber */) {


            if (tet == TimerEventType::EVENT_COMPARE1) {

                _captureCH1[_indexCH1] = _myInputTimer->T1Features::getCapture();

                if (_indexCH1++ == 1) {

                    _indexCH1 = 0;
                }

            }

            if (tet == TimerEventType::EVENT_COMPARE2) {

                _captureCH2[_indexCH2] = _myInputTimer->T2Features::getCapture();

                if (_indexCH2++ == 1) {

                    _indexCH2 = 0;
                }
            }
//            if (tet == TimerEventType::EVENT_COMPARE3) {
//
//            }
        }
};


Edit1: Fixed the glitch by clamping the capture...courtesy of google ofcourse...well I'm a DuckDuckGo user...so credit where credit is due. Thanks DuckDuckGo :-)
(Still needs help converting the capture to PWM out.)

Andy Brown

Quote
Now how do I convert the captured pulse width to pwm duty cycle to drive my hbridge


If you know the frequency of the signal being sampled (the input signal) and you do know the frequency of the timer used for the sampling (you choose that in the code) then you also know the number of timer ticks between two rising edges.

Your interrupt triggers on rising and falling edges. The difference in capture values between those two interrupts is the duty cycle, measured in timer ticks as a fraction of the maximum possible, which you have pre-calculated.
It's worse than that, it's physics Jim!

mhel

Thanks for the reply.

So I already have the duty cycle captured between rising and falling edges.
What I can't figure out yet (with my limited understanding) is how can I turn
the pulse width to value between 0:100 so could pass it to setDutyCyle().
Knowing that my 100% is 2300 and my 0% is 700.
( I almost think that it should be a simple math, but I can't even pretend I know what I'm doing :-) )

Andy Brown

If it's as simple as converting a range of 700 to 2300 then your formula is:

(value - 700) * 100 / 1600

Use 32 bit integers and do the calculations in the order presented to avoid loss of precision. Also best to constrain your captured value to a range of 700-2300 to avoid under/overflow. Is it that easy do you think?
It's worse than that, it's physics Jim!

mhel

Thanks,
I may have busted my input pins, the board freezes everytime I turn the RC receiver on, I must have shorted something when I hook up the hbridge.
I'll post back if it still works if I remap the pins.
Thanks again.

mhel

So my discovery board is busted. I got me a couple of these stm32f103 since stm32f4 is overkill for what I want to do, and I don't want to wait long so I got it sort of locally.
I'm having timer trouble. I'll start a new thread for it.

Andy Brown

I've got one of those boards. You know they can be had for US$1.98 including shipping on Ali Express. I seriously don't know how these guys can make a profit.
It's worse than that, it's physics Jim!

mhel

Yeah, I paid a premium for mine if I compare it to those less than $5 board, but I got it in 3 days. The chip itself is about $9CAD at Digikey.
I was afraid if I order from far East it will take 2 weeks minimum and my vacation time will be over. And there's looming postal worker strike here.

mhel

Hi All,

So I managed to revive my discovery-board. I wanted to buy a new one but it gotten expensive.
I remember buying mine for about $20CAD, and all I ever did was blink a LED.I just bought the chip and winged it. It's not easy but a chipquick helps a lot, as I didn't want
to take a chance damaging the board like the last time I made a similar attempt that ended up me throwing the board out of disappointment  :-[ (It's kind of an expensive hobby).

I tried using the stm32f103 and did not realize I couldn't just port the codes from f4. And it would take me more time to figure out how I to utilize
the EXTI instead of the input capture. I probably would try to use a lower series like the F303 , because the F4 is way overkill for my purpose.
I've read some smarties did it on an AVR and PIC.

Anyhow, I'd like to thank Andy again for the libraries and the examples. I tried to do it in C because a lot of info I found on other forums uses it,
but I keep coming back to stm32plus,I even tried stm32duino. Even though I'm still learning c++ with very little knowledge of C (which is kind of obvious with what I've posted so far).


Below is what I've come up with, well it's a Frankentstein's version of what I've gathered. I've learnt about two types of controlling h-bridges.
One is called locked antiphase (google can help here) and the other is sign magnitude, where basically one needs a pin for PWM and another for Direction,
where lock antiphase uses a single PWM pin. I've tried both because my h-bridge supports it. A Cytron MD10C. I like  the locked antiphase because it drives smooth, but I'm afraid of the runaway motor in case of crash, I settled for the sign magnitude.
The downside is, changing direction causes jerks, and I've read some other complicated tricks to solve it and it's out of reach of my current understanding.
If anyone knows how I can ease in and ease out when changing direction that would be great. The code below works, my logic is messy but it's functional.
Some newbies like me would still probably appreciate it  ::)
The attached is based on the input capture example I didn't change all the comments. (code reach the maximum 2000 characters)