NRF52LL Quadrature decoder

Posted on
  • Hello,
    First I want to say that the espruino and the low-level api to nRF52 is super-cool. I could try some of the examples you have provided, and it is so quick to work with.

    I would like to request or figure out how to use the QDEC - Quadrature decoder feature that is described in the nRF52832 Product Specification v1.1. I have a quadrature encoder with A and B pins connected to the MDBT42Q breakout board already (working in software, but too slow) and if I could use the hardware feature connecting it to a counter it would be awesome. Any suggestion how it could be done?

    Best regards
    Jonas

  • Tue 2019.08.20

    '. . . the low-level api to nRF52 is super-cool . . . and it is so quick to work with.'

    Welcome to Espruino Jonas @Jofo and we are glad to see your enthusiasm getting started.

    'in the nRF52832 Product Specification v1.1'

    Would you mind posting a link to that doc please so that we all may follow along. Specifying the section headings/numbers will speed our ability to respond more timely.

    'quadrature encoder '

    Datasheet on encoder specifications here also.

    '(working in software, but too slow)'

    What access speed is expected? How many pulses must read and over what duration? How is this being done in code? A code snippet would assist us in verifying/observing what is currently being attempted. Would 'inlineC' help here? Has coding in this manner been attempted?

    https://www.espruino.com/InlineC
    https://www.espruino.com/Assembler

     

    What functions/methods are being used to read the values? setWatch() or just digitalRead()? Is the read section embedded inside a setInterval() perhaps? Is this where the impression 'software is too slow' is occurring? What duration of timing is being observed in that section?

     

    ' connecting it to a counter '

    Has a specific model and make been selected? What is the doc link for that also?

  • Thanks Robin,

    The specification in question is http://infocenter.nordicsemi.com/pdf/nRF­52832_PS_v1.1.pdf section 36 starting on page 346, also linked from the low-level library page http://www.espruino.com/NRF52LL
    It is actually from there I found out that the chip indeed had hardware support for quadrature decoding. The encoder I use is RLS RM08 https://www.rls.si/en/fileuploader/downl­oad/download/?d=1&file=custom%2Fupload%2­FRM08D01_12.pdf, but I think it would work just as well with more common optical encoders like HEDL5540 etc.

    connecting it to a counter

    Has a specific model and make been selected? What is the doc link for that also?

    I meant counter in the nRF52, like how it is done for counting button clicks in the example of low level library. Maybe not necessary, I am a bit unused to the low-level specification documentation (linked above).

    What access speed is expected? How many pulses must read and over what duration?

    We have 1024 counts per revolution and in our case we use it to detect angle of a manual axis rotation so it is perhaps max 10.000 counts per second. My software solution was just intended as a proof of concept but it works if I rotate slowly (~4s per turn), but skips counts if i turn faster.

    Here is the js code. Thanks for the inlineC suggestion, I will try and see if I can use it too.

    // D25 Green A channel
    // D26 Grey  B channel
    const stable = [0,-1,1,0,1,0,0,-1,-1,0, 0,1, 0,1, -1, 0];
    var encoder_raw = [false, false];
    var counter = 0;
    var prev_state = -1;
    
    var  on = false;
    setInterval(function() {
      on = !on;
      LED1.write(on);
      console.log("Counter " + counter);
    }, 100);
    
    const encoder_callback = function(AB, value)
    {
        encoder_raw[AB]=value;
        var cur_state = encoder_raw[0] << 1 | encoder_raw[1];
        if(prev_state < 0) prev_state = cur_state;
        counter += stable[prev_state << 2 | cur_state];
        prev_state=cur_state;
    };
    
    setWatch(function(e) {encoder_callback(0,true); }, D25, { repeat: true, edge: 'rising' });
    setWatch(function(e) {encoder_callback(0,false);}, D25, { repeat: true, edge: 'falling' });
    setWatch(function(e) {encoder_callback(1,true); }, D26, { repeat: true, edge: 'rising' });
    setWatch(function(e) {encoder_callback(1,false);}, D26, { repeat: true, edge: 'falling' });
    
  • To follow up, here is the inlineC version, which actually is fast enough for my purpose! I am still interested in hardware support, maybe more power-efficient or leaving cpu for other tasks, but this could be good enough anyway. Super-cool that we can combine js and c this way, so thanks for that.

    var c = E.compiledC(`
    // void encA(bool)
    // void encB(bool)
    // int getEnc()
    const int stable[] = {0,-1,1,0,1,0,0,-1,-1,0, 0,1, 0,1, -1, 0};
    volatile bool encoder_raw[] = {false, false};
    volatile int counter = 0;
    volatile int prev_state = -1;
    void encoder_callback(int AB, bool value){
        encoder_raw[AB]=value;
        int cur_state = encoder_raw[0] << 1 | encoder_raw[1];
        if(prev_state < 0) prev_state = cur_state;
        counter += stable[prev_state << 2 | cur_state];
        prev_state=cur_state;
    };
    void encA(bool state){ encoder_callback(0,state); }
    void encB(bool state){ encoder_callback(1,state); }
    int getEnc() {
     int r = counter;
     return r;
    }
    `);
    setWatch(c.encA, D25, {repeat:true, edge:"both", irq:true});
    setWatch(c.encB, D26, {repeat:true, edge:"both", irq:true});
    
    // Every .1 seconds, report back the value
    setInterval(function() {
      print(c.getEnc());
    }, 100);
    
  • Tue 2019.08.20

    Thank you @Jofo for the additional detail, we now may follow along more easily.

    Surprised, and glad you were able to get the inline 'C' working quickly. My first attempts took me longer. Nothing appears out of the ordinary for the Javascript snippet. Proper use of setWatch() and not embedded with the setInterval() function.

    This is most likely understood, so forgive me if this is too obvious. I use a technique to allow a function call in the WebIDE Left-Hand console window to stop processing as at times Ctrl+C isn't always as responsive when updating at 100msec. L9 could be modified to:

    var intervalID = {};
    
    function ci() { clearInterval( intervalID ); }
    
    L9:  intervalID = setInterval(function() {
    


    ' it works if I rotate slowly (~4s per turn), but skips counts if i turn faster'

    Your post reminded me of another with an encoder issue, several days ago, and a link there shows a solution Gordon proposed that should be fast enough with Javascript and setWatch() data: options attribute.

    See the link 'Proper technique needed to nest a setWatch()' in #27 there

    post #22 and #27 and with encoder image #30
    http://forum.espruino.com/comments/14857­261/

     

    'I am still interested in hardware support, maybe more power-efficient'

    I'll continue to read up from your links, in the mean time I'll defer to others. . . .

  • Nice inline-C solution.... and it is portable between Espruino bards. Doing it with counters may not be much faster but much more complicated and keeps the processor still busy. Using the built-in QDEC spares the processor - depending how you setup the interrupts and interface with JS layer.

    To use the QDEC in poling mode - almost what you do in current solution - you can achieve with poking and and peeking. poke32() -http://www.espruino.com/Reference#l__glo­bal_poke32 - and peek32() -http://www.espruino.com/Reference#l__glo­bal_peek32 - in JavaScript give you the access to the registers... so go for it! Since it is a dedicated piece of hardware, you will not interfere with Espruiono's use of timers and interrupts.

    Looking at the power use, it is for sure much much lower than get thru the interrupt FIFO the JavaScript execution space going. It also does not / much much less compete with the interrupts other IO generate.

    Interesting to the QDEC implementation is that it already does great error detection and counting and has built in 'debounce' filtering (which btw uses timers when using debounce in setWatch().

  • Thanks for posting up the C code - hopefully that'll really help some others as well.

    Robin's got a good point with the 'setWatch' link - by using the data pin argument you could use a single setWatch with JS. It'll only have half the accuracy but it should be capable of going much faster than the plain JS version (not your C version).

    Totally untested but:

    count=0;
    setWatch(function(e) {
      count+=(e.state^e.data)-0.5;
    }, PINA, {data:PINB,repeat:true});
    

    As far as I can tell the QDEC hardware seems pretty easy to use - I'd just use the http://www.espruino.com/modules/NRF52LL.­js file as a basis and fill in the registers from the datasheet.

    As far as I can tell for the simplest setup you need to:

    • ENABLE=1
    • set PSEL.A/B to the two pin numbers
    • TASKS_START=1

    Then when you want to read:

    • TASKS_RDCLRACC=1
    • Read ACCREAD

    You could also be fancy and use a new feature I added in 2v04 to access the registers as if they were variables: http://www.espruino.com/Reference#l_E_me­moryMap

    The gotcha here is you'll have to poll the reader (unless you're willing to waste 2 external pins so you can setWatch one and then use NRF52LL to toggle the pin state when an 'event' is generated from the QDEC).

    Also your current solution looks like it's actually the most power efficient. I could be wrong here but it looks like QDEC doesn't use edge detection - it repeatedly polls to check the values. That means that it'll require the high speed oscillator to be left on when it runs, which will cause at least 1mA (IIRC) power drain. Your method will draw basically nothing when idle.

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

NRF52LL Quadrature decoder

Posted by Avatar for Jofo @Jofo

Actions