12 Replies Latest reply on Oct 2, 2015 12:31 PM by nemrod

    Measuring a PWM signal

    nemrod

      Hi,

      I'm trying to measure a pulse width (from an RC receiver for quadcopters) but am getting very unstable values with an accuracy of generally about ±0.3ms, though sometimes worse, i.e. at any point it can be anywhere between 0.7 and 1.3ms when it should be stable at 1ms. I've looked at the pulse with an oscilloscope (before and after level conversion from 3.3V to 1.8V) and it looks stable. It's a standard PWM signal with a frequency of 50Hz (20ms period) and pulsewidth ranging from 1 to 2ms.

       

      I assumed it was due to the Edison not being realtime and I've tried to mitigate it if only slightly by setting the nice value for the process to -20 and using the isolcpus kernel parameter and CPU affinity, but I can't tell any immediate difference in the stability of the measured signal.

       

      Is the GPIO I/O, interrupt handling, or the Edison in general simply not fast enough to measure this kind of signal, or (more likely) am I doing something silly or plain wrong? Is there a faster way to measure the signal than my implementation? I've heard about memory mapped I/O and that it might be faster than libmraa GPIO, but haven't seen any concrete examples and have no previous experience with it. For what it's worth I tried simply adding useMmap(true) to no effect. I might add that I'm not particularly interested in using the MCU for various reasons.

       

      The relevant code that I have (as bare as possible) looks like this:

      RCChannel::RCChannel(int pin) {
          this->gpio = new mraa::Gpio(pin);
          this->gpio->dir(mraa::DIR_IN);
          this->gpio->isr(mraa::EDGE_BOTH, &RCChannel::interruptHandler, (void*)this);
      }
      
      
      void RCChannel::startPulseTimer() {
          clock_gettime(CLOCK_MONOTONIC, &this->startTime);
      }
      
      
      void RCChannel::endPulseTimer() {
          clock_gettime(CLOCK_MONOTONIC, &this->endTime);
          this->pulseWidth = (this->endTime.tv_sec * 1000000000 + this->endTime.tv_nsec) - (this->startTime.tv_sec * 1000000000 + this->startTime.tv_nsec);
      }
      
      
      int RCChannel::readGpio() {
          return this->gpio->read();
      }
      
      
      void RCChannel::interruptHandler(void* args) {
          RCChannel* channel = (RCChannel*)args;
          if (channel->readGpio()) {
              channel->startPulseTimer();
          } else {
              channel->endPulseTimer();
          }
      }
      
      
      

       

       

      So, what do you people think? Can anyone help me getting this thing in the air?

        • 1. Re: Measuring a PWM signal
          PabloM_Intel

          Hi nemrod,

           

          Could you please share the code that you’re using and the specifications of the signal that the receiver is producing? So we can reproduce it and see if we get the same results. Given that the Edison has not hardware interrupts but software simulated interrupts, this will have an impact in the measure. And yes, one way to improve this would be to try fast memory mapped I/O, I’ll keep looking for examples and if I find something interesting I’ll post it here.

           

          Regards,

          PabloM_Intel

          • 2. Re: Measuring a PWM signal
            nemrod

            Hi Pablo,

            Thanks for the reply! The code I'm using is pretty much what's up there, but for testing I've now also isolated the relevant bits to a stand-alone C program that I'll paste below. It exhibits roughly the same behaviour, though subjectively slightly better. Still, if I define (as in the code below) anything that's ±10% outside the expected value I get a fail rate of 10-20%. Of those some are just outside (1.12ms), some are quite far removed (0.7ms), while some are way off base like 19ms (perhaps a missed edge so a whole period passes by) or 0.32ms.

             

            As stated above it's supposed to be a simple PWM signal with a period of 20ms and a pulse width of 1ms, and I've verified it with an oscilloscope and it looks to be correct. The signals are coming from the FlySky FS-R6B RC receiver, level converted via a TXS0108E chip.

             

            Any verification if this is just happening to me or if it's reproducible with a signal generator or anything at all will be helpful, as will any hints to optimise it to minimise the misses. Thanks!

             

            edit: using CPU isolation and affinity to dedicate a whole core to run exclusively this program I get a fail rate of roughly 4%; much better. This tells me it's possibly because the Edison simply can't keep up, presumably on the software end of things, despite the low frequency of the signal. Is there any way to use proper interrupts instead of the mraa software ones? While I might have been able to work with the 4% situation (most of the misses seem to be relatively minor) there'll be a lot of other things going on in a live situation (sensor fusion algorithms etcetera) inducing load and increasing the failure rate, so I still need to work around this problem somehow.

             

            #include "mraa.h"
            #include <time.h>
            
            
            void interruptHandler(void* args);
            
            
            int main() {
                mraa_init();
                mraa_gpio_context gpio = mraa_gpio_init(53); // throttle
                mraa_gpio_dir(gpio, MRAA_GPIO_IN);
                mraa_gpio_use_mmaped(gpio, 1);
                mraa_gpio_isr(gpio, MRAA_GPIO_EDGE_BOTH, &interruptHandler, gpio);
            
            
                while (1) {
                    sleep(60);
                }
            
            
                mraa_gpio_close(gpio);
                return MRAA_SUCCESS;
            }
            
            
            struct timespec startTime;
            struct timespec endTime;
            long pulseWidth;
            unsigned long total;
            unsigned long failed;
            float failRate;
            
            
            void interruptHandler(void* args) {
                mraa_gpio_context gpio = (mraa_gpio_context)args;
            
            
                if (mraa_gpio_read(gpio)) {
                    clock_gettime(CLOCK_MONOTONIC, &startTime);
                } else {
                    total++;
                    clock_gettime(CLOCK_MONOTONIC, &endTime);
                    pulseWidth = (endTime.tv_sec * 1000000000 + endTime.tv_nsec) - (startTime.tv_sec * 1000000000 + startTime.tv_nsec);
                    if (pulseWidth < 900000 || pulseWidth > 1100000) {
                        failed++;
                        failRate = (float)failed / total;
                        printf("%.8ld\t%f\n", pulseWidth, failRate);
                    }
                }
            }
            
            
            • 3. Re: Measuring a PWM signal
              nemrod

              I've tried a few different approaches now, including things like separate threads polling with mmap enabled, but with several pins to measure and the added load of sensor fusion etc. what I'm getting just isn't accurate enough. I fear that there's not much I can do in software in userspace.

               

              PabloM_Intel, any news, or tips about other approaches? Are there really no hardware interrupts on the Edison GPIO? That's quite crippling for a device such as this, isn't it?

              • 4. Re: Measuring a PWM signal
                PabloM_Intel

                Hi nemrod,

                 

                Have you checked this thread? https://communities.intel.com/message/281413.

                They use an example for memory mapped GPIO, you might find that code helpful.

                 

                Regards,

                PabloM_Intel

                • 5. Re: Measuring a PWM signal
                  nemrod

                  PabloM_Intel, thanks for the tip. From that thread I'm given to understand that reading the GPIOs mmaped isn't even implemented though. From the answer marked correct:

                  You're right we dont have any mmap'd access to read gpios, I'll look into what we can do for that, honestly we haven't had too much interest that way round, people typically use interupts when they want fast gpio responses rather than poll them.

                   

                  At this point I've pretty much given up on being able to read the signals on the Edison to be able to move on and will have to state as much in my thesis. Currently I'm simulating the signals in software but to be able to complete the project properly I might use a PIC or an AVR to time the signals and I²C to get the values to the Edison. Quite disappointing considering a main RQ was if it was possible to complete this project specifically without using an external MCU. I guess a negative result is still a result though, aggravating as it is.

                   

                  I'll still be on the lookout for other approaches to the problem or news regarding this since it leaves a bad taste not finishing what I set out to do originally, so please do keep this thread in mind if you see anything else

                  • 6. Re: Measuring a PWM signal
                    k4mcv

                    I have had success reading a PWM signal (readings around plus or minus 5us of being correct) by dedicating one of the two cores on the Edison to constantly polling the gpio pins rather than using an interrupt (on top of the isolcpus flag, SCHED_FIFO scheduling, cpu affinity, and memory mapped io). Something like:

                     

                    while(gpioPin.read() == 0) { }

                    long long int a = <current time>;

                    while(gpioPin.read() == 1) { }

                    long long int b = <current time>;

                    pwm_value = b-a;

                     

                    Not a great solution, but it does work. I would have liked to be able to have done this on the quark rather than one of the main cores, after all that is what the quark is meant for. But unfortunately the gpio read speed from the quark is abysmally slow (~25us per read, iirc) and I need to monitor 6 different pins simultaneously. If anyone from Intel is listening, please remove the OS from the quark, it defeats the whole point of having a microprocessor attached the main cpu if that microprocessor is 50x slower at reading gpio pins than the main cpu.

                    • 7. Re: Measuring a PWM signal
                      nemrod

                      k4mcv, thanks for the input. It does work, but with a period of 20ms and typical pulsewidths of 1-2ms, as in my case, a ±5ms error makes the data unusable. And that's only 50Hz, so I can't even imagine an application where such a large error would be acceptable. You're also in a busy wait on a single pin in your example code, so you can't measure more than one pin at a time.

                       

                      I've tried the approach of polling with isolcups etc. as you do (though in an infinite loop polling each relevant pin in sequence and checking the state), with still a far too large error. The best measurements I've achieved were actually with the Quark, though not as accurate as I'd like they were at least more stable within their error range of about ±0.1ms. The problem with that was getting those measurements to userspace Linux reliably... *sigh*

                      • 8. Re: Measuring a PWM signal
                        k4mcv

                        Whoops, I meant 5us. I edited my last post. I'm also using a 50hz singal coming from a rc receiver, so yeah, I know that 5ms is unacceptable.

                         

                        Its actually much better than 5us (within 1 us), but every once in a while i will get a 5us error in a few readings (probably due to some system interrupt triggering).

                         

                        As far as multiple pins, it turns out that on virtually all rc receivers the pulses come in some fixed order (ex. pin 1, then pin 2, ...), where the previous pulse finishes before the next begins, so if you determine this order, you can always busy wait for the correct pin, and thus monitor six channels at 50hz.

                        • 9. Re: Measuring a PWM signal
                          nemrod

                          k4mcv, that's great! I'll check the pulses with my oscilloscope to see if they're in the sort of cycle you describe. I was planning on dedicating a core to the whole flight control software and not just the RC receiving though (which would be possible with interrupts) so I'm not sure if I will be able to use this in practice, but still interesting to check out. Thanks for the info!

                          • 10. Re: Measuring a PWM signal
                            nemrod

                            k4mcv, you're right about the signals; on my RC receiver the next channel's pulse starts right as the last one ends. It's too bad I don't have a core to dedicate because the timings you achieved are certainly good enough. :/

                            • 11. Re: Measuring a PWM signal
                              g.gregory8

                              nemrod Did you get a better solution for this. I need to do exactly the same thing. My current application can handle 0.5ms error as I am only checking for ~1ms or ~2ms so you originally solution might work for me here. But I have other applications where I need an accurate measure to 0.1ms. k4mcv method will probably be too resource hungry for my application but I will investigate.

                               

                              I will let you know my results. But would be nice to know if there is a better approach. It seem silly that the onboard MCU cannot be used for this.

                              • 12. Re: Measuring a PWM signal
                                nemrod

                                g.gregory8, I'm afraid not, not without dedicating an entire core to that single task. You can actually use the MCU though if you're willing, as long as you're on 2.1 it seems (2.0 freezes with too much unread data, though I have to be on 2.0 because I²C is broken on 2.1 for me). Check my other thread about it: Edison becomes unresponsive - too much data from MCU?