reading and writing PWM

Posted on
  • Hi Gordon!

    A few of the folks at work have been building a quadcopter flight controller with me using an Espruino. It's fantastic, we've had lots of fun. We've done some interesting things - we've written a gulp deploy tool, we run unit tests using the espruino process on our macs, we have written our own AMD loader, AMD optimiser and a promise library. All of this is very crude.

    Anyone wanting to see the code can do so here:

    https://github.com/camswords/quadcopter/tree/master/src/main

    We have wired up an input pin to receive a PWM signal from the RC receiver. We have registered a function similar to the following to 'watch' on a falling edge in a repeating way.

    function(event) {
          var dutyCycle = Math.floor((event.time - event.lastTime) * 1000000);
      
          ...
    }
    

    Strangely, when we send the (dutyCycle, event.time, event.lastTime) to my laptop via bluetooth we get output such as the following (forget the strange characters, that is related to my serial / bluetooth solution, not this issue):

    981.33087158203125, 4.910�20849609375, 4.90931987762451171875|
    981.33087158203125, 4.9816s751708984375, 4.98071384429931640625|
    980.377197265625, 5.05308�71484375, 5.052104949951171875|
    981.33087158203125, 5.124489784072265625, 5.123508453369140625|
    981.33087158203125, 5.1780481338506484375, 5.24847507476806640625|
    981.33087158203125, 5.3208665847740625, 5.39130115509033203125|
    53564.0716552734375, 5.445846557�1875, 5.3922824859619140625|
    

    We expect values roughly between 1000 and 2000, as it is recording the length of the duty cycle (1ms to 2ms). Every now and again we get very large values in the time difference. My naiive guess is that this time is calculated by the CPU, not a timer? And code being executed may cause a delay between "event.time" and "event.lastTime"? If that is the case, is there anything I can do to get a timer to populate a register with the duty cycle, and have my 'watch' event fire on change of this value instead?

    I felt the same when writing PWM outputs for each of the propeller motors. We are using digitalPulse with a setInterval every 20ms, but I fear that the PWM signal quality will degrade when other code is executing at the time the signal should be pulsed. Looking at the STM library source (disclaimer: I know very little about this, and microcontrollers in general) my understanding is that I could write to a register and have a timer automatically adjust the duty cycle. I believe there is a TIM_SetCompareX method that does this.

    Does this make any sense? I'd love to hear your feedback. I'm not expecting you to solve these problems for me, I'm happy to jump into the code and send you a patch if it makes sense.

    fyi, we've been reading PWM signals on pin A8 and and sending PWM signals on pins C6, C7, C8 and C9. All of this is done on Espruino running 1v61. I'm happy to run this on a more up-to-date version of Espruino if required.

    Cheers
    Cam

  • Sounds great! For PWM out, try using analogWrite - there's an example down the bottom of http://www.espruino.com/Servo+Motors

    And for input, yes, it's being done in software - if the pulses are running at 20ms it shouldn't miss any - but perhaps it is simply that you are printing too much data to send over serial?

    If you did want to use hardware that should be possible - you can use the peek and poke instructions and then look at the chip's data sheet... However getting a callback when the value updates would be much more difficult I'm afraid - you'd have to poll it.

    Hope that helps!

  • Probably sending data has limitation. Could be interesting to check on how long between each transmitted byte to calc the effective speed. While operating the HW from JS give flexibility, it seems that predefined "HW operating scheme" modules (made in C) could improve performance for people going the performance/speed path. If there would be a way to use JavaScript to implement things like this: http://www.wolinlabs.com/blog/stm32f4.adc.dsp.dac.html would be an achievement!

  • Cheers for the responses!

    Gordon,

    I initially tried analogWrite, though when we started getting sporadic increases of throttle at the propeller side I thought that maybe the timer might be initialising again, in which I mean starting the pulse again as opposed to just adjusting the duty cycle. Is this the case? If I call analogWrite many times per second would you expect a consistent pulse on the output?

    Regarding the input, yes it is possible that we're sending too much out on the serial. Generally though, this means that as we add more work to do for the Espruino, the event times we read while watching will get less and less reliable. I'll read the STM docs and see if I can find anyway I can do it with peek / poke.

    Cheers
    Cam

  • Update firmware - v61 had some issues with PWM on various pins fighting each other, these were fixed in v62.

    Also, each "bank" of PWM pins shares the timer (though not the duty cycle, of course) - if you weren't aware of this.

    What frequency is the PWM running at? If it mattered whether the timer reset when changing duty cycles, I imagine it would have to be a very low frequency? Or a very sensitive device you're driving.

  • Cheers, @DrAzzy, I'll update the firmware. I knew about the timer sharing, though I haven't really looked in to it / understood it yet.

    I did some testing with analogWrite on my digital logic analyser. The timer does have a consistent pulse even when updating the duty cycle more frequently than the pulse width milliseconds. For example, running the code:

    var value = 0.1;
    
    setInterval(function() {
           if (value === 0.1) {
               value = 0.05;
           } else {
               value = 0.1;
           }
           analogWrite(C6, value, { freq: 50 });
    }, 10);
    

    resulted in a consistent pulse width of 20ms, and a consistent duty cycle of 2ms. I assume this takes the most recent duty cycle value when it writes the pulse, therefore it always used the "0.1" value and the "0.05" was never used as the duty cycle. Happiness! At least I've isolated my problem now.

  • Ok, I think I understand my PWM input woes now.

    An external interrupt is registered on the rising and falling edge of a pin. The interrupt code records the pin, time, and pin state and pushes an io-event to an event queue. Later, in the main program loop (in jsidle) the io-events are consumed. Here, depending on whether the user specified rising, falling or both, and the state of the pin then one of two things will happen. If the callback shouldn't be triggered (eg. falling edge when the user watches for rising edges) then the lastTime will be set to the event time and the loop will continue. If it's callback time, then the previously defined lastTime will be used along with the event time and the callback will be executed.

    The issue I'm facing is that the (event.time - event.lastTime) I'm getting is far larger than the pulse coming on the pin I'm watching. I believe this is happening because when io-events are added to the event queue they are discarded if the queue is full. That makes sense from a memory conservation point of view, however it means that "lastTime" represents the time between the current io-event and last io-event that wasn't discarded due to a full queue. That makes me distrust "lastTime" completely. I still trust "time", because it's defined when the interrupt is called, not in the main program loop.

    I think I have a work around, which is to watch the pin on both edge rising and fall and calculate the time difference using only "time". When io-events are discarded from the event queue I will still get an incorrect value, but I can recognise this because I know the minimum and maximum value that duty cycle should be on for (1ms - 2ms). Anything outside this range means that io-events were discarded and therefore I didn't get a correct reading. This means that I will get less PWM input readings, but it does mean that they will be accurate (so I believe).

    Anyone have any thoughts?

  • This io event queue filling up would explain why I can't get DHT11/22's to give me their checksum byte! I'll bet the queue is 32 events, and I end up recording like 33-34, and the rest, containing the checksum bytes, are getting discarded..

  • Maybe! Although from what I can tell the queue size will be 64 events long on an Espruino (32 events long on a board with less flash). IO Events are any work that needs to be done regarding external line interrupts (setWatch), serial (usb and via pins), SPI and I2C. For example, each character received on a Serial read is one IO Event.

    In my use case, my input pin was receiving pulses every 20ms. I got a reading of time between events (event.time - event.lastTime) of 0.053564071 seconds, in which I expect 4 - 6 IO Events to occur due to my pin edge rising / falling. For me to fill the queue I think I would have had to have other events on it too, presumably incoming characters on a serial link, though I'm a bit sceptical now because I thought that my Espruino serial link was only sending characters out.

    One thing I might try is not making my setWatch repeat, just turning it on every now and again might mean less events in the queue for me.

  • 64 events would explain what I see - because each bit takes 2 events, rising and falling.

  • @DrAzzy: yes, that could be your issue - however I'd have thought that Espruino would have been able to consume some of the events before the queue got full?

    @CamSwords: The reason that the queue is getting full is almost certainly the serial output: If you're writing data out down Serial and Espruino's output buffer gets full, print/console.log waits for there to be enough space in the queue to put in what you're sending (rather than throwing it away).

    If you're getting signals every 20ms and the buffer is getting full, that's roughly 20*32 = 640ms since you last went around the idle loop. Regardless of whether you're getting the right values or not, a 640ms delay in response time for a quadcopter is going to be disasterous!

    What about:

    • modifying your print statement to print out only every 10th value
    • using myvalue.toFixed(2) to restrict the number of decimal places to 2.
    • increasing the serial port baud rate?
  • Cheers Gordon,

    You're right, 640ms waiting for a quad would be disastrous! Needless to say, our quad isn't flying just yet.

    I didn't realise that serial output ended up on the same IO Event queue, but that makes more sense as to why the queue was full. Is the waiting for space in the queue that occurs the following loop in jsdevices.c? If so does that work because there is an interrupt consuming characters from the queue and sending them over the wire?

    while (txHeadNext==txTail) {
      // wait for send to finish as buffer is about to overflow
    }
    

    By the way, the equation you gave was 20*32 = 640ms. Does that imply that the maximum io event queue size is 32 on an Espruino?

    I'll try all of the suggestions you made, thanks. I'm a little scared because we have two I2C devices to attach on top of what we have there now. I think we'll get it flying, though I expect it to be a little clunky and unresponsive.

    Cheers for your help!
    Cam

  • There are actually two different queues - a TX and an RX queue. RX handles watches and Serial/USB input, and TX handles Serial/USB output. It's best to think of it as the interface between interrupts - interrupts put data in the RX queue, and get data from the TX queue.

    And yes, you're right about the while loop - interrupts will be grabbing data and moving txTail up towards txHead in the background. Ideally the while loop would never get hit in most cases (the TX queue is 64 characters) but if you're sending data out faster than the serial port can transmit then it doesn't have much choice - it shouldn't drop characters, and saving them into memory would just fill up the available RAM after a few seconds.

    32 is because you've got rising + falling edges, at 20ms intervals. The buffer size is still (I believe) 64.

    What baud rate is your bluetooth dongle? still 9600? That's only ~1000 characters/sec, but you'd be trying to transmit: 64 /* chars per line in your example */ * 50 /* 20ms */ = 3200 characters/sec

    If you don't do that then Espruino should end up significantly faster. I think you'd be able to hit the 20ms update rate pretty easily - even with I2C devices :)

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

reading and writing PWM

Posted by Avatar for CamSwords @CamSwords

Actions