Inline Assembly: A Fast Quadrature Decoder…

Inline assembly is a great tool to have in your programming arsenal. It gets things done as fast as a kitten chasing a hummingbird, yet retains the usability and portability of C code.


This bit will decode a quadrature input from a rotary incremental or linear position sensor,  it polls two inputs (PIND4 & PIND5) to convert the incoming grey code into respective direction and count, tallying the position in a signed 16bit variable.  It can be run on pinchange interrupt, timer interrupt, or called as a function. It also contains a provision for missed transitions.

Globally we have three declared variables, the current position and a couple of previous variables. As they will be modified inside the inline statement, we have declared them as volatile:

volatile int CurrentPosition;
volatile unsigned char PreviousEncoder;
volatile unsigned char PreviousDirection;

We also have a locally declared 16 bit variable, used for temporary storage during calculations. In assembly this would normally be handled by a pair of the x, y, z doubles:

unsigned int Zbuffer = 0; // setup a 16bit temporary local buffer

In standard inline form, the inputs and outputs are listed as the last bit, separated by colons. As zbuffer is a temporary variable it’s only declared in the output, but must be constrained to upper registers (r16 to r31) as it will be used with immediates. The input  uses the integer constraint while everything else is relegated to any available register. This allows for the compiler to have the most flexibility in assigning resources:

// Output
:  "=r" (CurrentPosition),   
   "=d" (Zbuffer),
   "=r" (PreviousEncoder),
   "=r" (PreviousDirection)                    

// Input
:  "I" (_SFR_IO_ADDR(PIND)),
   "0" (CurrentPosition),
   "2" (PreviousEncoder),
   "3" (PreviousDirection)

The original assembly was written by Chan of ElmChan, it was converted to inline and republished with permission under GPL. This code has been tested on a DC Servo sporting a Mega8 @ 16Mhz, running inside a timer interrupt at 100 kHz. No registers were harmed in the making of this code. Finally without further NOPs, here is the complete code:

unsigned int Zbuffer = 0; // setup a 16 bit temporary local buffer

asm volatile(
// incoming stack maintance is handled by compiler

"mov    %A1, %2     nt"    // Store previous encoder in low buffer
"in     %2,  %4     nt"    // Input encoder state PORTD 0b00xx0000
"swap   %2          nt"    // Swap nibbles 0b76xx3210 -> 0b321076xx

"ldi    %B1, 1      nt"    // Load 1 into high buffer
"sbrc   %2,  1      nt"    // Skip next instruction if bit 1 is clear
"eor    %2,  %B1    nt"    // Exclusive OR, ENC = 1x, if 10 -> 11, if 11 -> 10

"sub    %A1, %2     nt"    // Subract previous from 11 or 10, store in low
"andi   %A1, 3      nt"    // AND with 11 - check if bits have changed
"breq   exitpoint   nt"    // If equal, no change, jump to exit

"cpi    %A1, 3      nt"    // Compare low buffer to 0b00000011
"breq   decrement   nt"    // If equal jump to decrement 

"cpi    %A1, 1      nt"    // Compare low buffer to 0b00000001            
"breq   increment   nt"    // If equal jump to increment

"mov    %A1, %3     nt"    // No branch = lost in transistion
"mov    %B1, %3     nt"    // Use previous direction to make up lost bit
"lsl    %A1         nt"    // Logical left shift = x2 - double up for lost bits
"asr    %B1         nt"    // Clear and save signed flag
"rjmp   summation   nt"    // Jump to total

"decrement:         nt"    
"ldi     %A1, -1    nt"    // Load low buffer with -1
"ser     %B1        nt"    // Set high buffer for negative number = 0xFF
"rjmp storeprevious nt"    // Jump over/skip increment

"increment:         nt"    
"ldi     %A1, 1     nt"    // Load low buffer with 1
"clr     %B1        nt"    // Clear high buffer

"storeprevious:     nt"      
"mov     %3,  %A1   nt"    // Store the previous direction in low buffer

"summation:         nt"    
"add     %A0, %A1   nt"    // Add low byte and carry to high
"adc     %B0, %B1   nt"    // This becomes the 16bit variable CurrentPosition

"exitpoint:         nt"    // final destination

// outoging stack maintance is handled by compiler....

// Output
:  "=r" (CurrentPosition),   
   "=d" (Zbuffer),
   "=r" (PreviousEncoder),
   "=r" (PreviousDirection)                    

// Input
:  "I" (_SFR_IO_ADDR(PIND)),
   "0" (CurrentPosition),
   "2" (PreviousEncoder),
   "3" (PreviousDirection) 

// Clobber
:  // nothing to clobber                                                


An example application and the current test platform is the DC Servo kit

4 Responses to “Inline Assembly: A Fast Quadrature Decoder…

  • Hi,
    Very nice and useful topic.
    Can you help me to this code to a atmega8 with connection pins diagrams.
    Nuri Erginer

  • Hi Nuri,

    You want to connect your encoder A & B to PIND4 & PIND5.
    If it’s a Servo and it takes off in one direction, reverse your A & B or reverse the motor wires. You may want to enable the internal pullups or use external pullups if your encoder is Open Drain.


  • Can you please tell me which part of the assembly code is “the pin declaration” or where can i change the pins from 4 and 5 to 4 and 11 (arduino uno) because my 5th pin is faulty.
    Thanks in advance!

  • The input is defined on line 7 and line 57.
    You will have to a bit of mangling to get going with pin11 (PB3).

    If your not familiar with assembly it’s probably easier to use pins 4 & 5 from another port.
    Port C (PC4 & PC5) map to Analog 4 & 5 which also work as digital pins, change line 57 to PINC.
    And Port B (PB4 & PB5) map to Arduino pins 12 & 13, change line 57 to PINB.

    Or you can remove line 8 (the nibble swap) and use the first two port pins of either port.
    Port D is out as it’s RXD & TXD but there is Port C & B (A0, A1 or 8, 9).

    The schematic for the UNO might help:
    The Arduino pin names are arbitrary and trace back to the register names for ports and pins.


Leave a Reply

Your email address will not be published. Required fields are marked *