• improved encoder reading

    Matthew Reeves11/12/2018 at 18:43 0 comments

    The software I'm modifying does an encoder read as follows:

      int rot = ((digitalRead(BTN_EN1) == LOW) << BLEN_A)
              | ((digitalRead(BTN_EN2) == LOW) << BLEN_B);
      // potentiometer uses grey code.  Pattern is 0 3 
      switch (rot) {
        /** logic to interpret the value goes here ** /
      }
    

    This routine is ok, but it could be made a little better.

    First I cut and paste the routine in a separate test project so that I can gather timings.

    To collect timing I'm collecting 1000 iterations of the encoder reading.

    void consume_input() {
        // grab the time prior to mucking with serial output
        unsigned long now = micros();
        unsigned long delta_ms = now - delta_time_start;
    
        Serial.print("Turn: ");
        Serial.print(lcd_turn);
        Serial.print(" uS: ");
        Serial.println(delta_ms);
        lcd_turn = 0;
        delta_time_start = micros();
    }
    
    static int g_loopctr = 0;
    void loop() {
      read_input_pot();
      ++g_loopctr;
      if(g_loopctr>1000)
      {
          g_loopctr = 0;
          consume_input();
      }
    }
    

    I'm running this code on an Arduino Mega 2650 and using Arduino 1.8.5 (which is using avr-gcc/5.4.0-atmel3.6.1)

    Starting with the existing implementation:

    Turn: 0 uS: 12028
    Turn: 0 uS: 12028
    Turn: 1 uS: 12108
    Turn: 2 uS: 12232
    Turn: 1 uS: 12040
    Turn: 1 uS: 12148
    Turn: 3 uS: 12224

    There's a fairly consistent time of 12028 uS per 1000 iteration and an additional cost of 50 - 100uS when the pin detection code runs

    Measuring 1000 loop iterations with the pin reading code disabled
     microseconds (uS): 3080
     
     12028 - 3080 = 8948
     

    Measuring 1000 loop iterations and calling read_input_pot() twice:

    Turn: 0 uS: 23108
    Turn: 0 uS: 23108
    Turn: 0 uS: 23108
    Turn: 0 uS: 23108

    23108 - 12028 = 11080

    That's a bit higher than I expected, but within reason. It seems like the timing numbers being collected have some validity.

    So, first easy win is to switch some variables over to 8 bit integers. There's basically 2 pins that are being used to read values, so there's no reason to use anything larger than 8 bits.

    Turn: 0 uS: 11648
    Turn: 1 uS: 11740
    Turn: 2 uS: 11876
    Turn: 1 uS: 11664

     That's a small but measurable improvement.

    Looking at the assembly code it's easy to see why this was a win

     //rot is int
      switch (rot) {
     4ba: 21 30        cpi r18, 0x01 ; 1
     4bc: 31 05        cpc r19, r1
     4be: 61 f1        breq .+88      ; 0x518 <__LOCK_REGION_LENGTH__+0x118>
     4c0: 24 f4        brge .+8      ; 0x4ca <__LOCK_REGION_LENGTH__+0xca>
     4c2: 21 15        cp r18, r1
     4c4: 31 05        cpc r19, r1
     4c6: 41 f0        breq .+16      ; 0x4d8 <__LOCK_REGION_LENGTH__+0xd8>
     4c8: 2c c0        rjmp .+88      ; 0x522 <__LOCK_REGION_LENGTH__+0x122>
     4ca: 22 30        cpi r18, 0x02 ; 2
     4cc: 31 05        cpc r19, r1
     4ce: c9 f0        breq .+50      ; 0x502 <__LOCK_REGION_LENGTH__+0x102>
     4d0: 23 30        cpi r18, 0x03 ; 3
     4d2: 31 05        cpc r19, r1
     4d4: d9 f0        breq .+54      ; 0x50c <__LOCK_REGION_LENGTH__+0x10c>
     4d6: 25 c0        rjmp .+74      ; 0x522 <__LOCK_REGION_LENGTH__+0x122>
    // rot is uint8_t
    {
    switch (rot) {
     4ae: 91 30        cpi r25, 0x01 ; 1
     4b0: 31 f1        breq .+76      ; 0x4fe <__LOCK_REGION_LENGTH__+0xfe>
     4b2: 28 f0        brcs .+10      ; 0x4be <__LOCK_REGION_LENGTH__+0xbe>
     4b4: 92 30        cpi r25, 0x02 ; 2
     4b6:...
    Read more »