Skip to content

Instantly share code, notes, and snippets.

@paulrpotts
Last active April 6, 2018 11:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulrpotts/3cea24b7e003fe00c63ff467a6f567ff to your computer and use it in GitHub Desktop.
Save paulrpotts/3cea24b7e003fe00c63ff467a6f567ff to your computer and use it in GitHub Desktop.
/*
I am using Oleg's algorithm for reading an encoder using a lookup table, here:
https://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino
But instead of this table of 16 bytes:
static int8_t enc_states[] = { 0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0 };
I am using two tables of 8 bits each. The bits in these tables indicate sign bits and one bits
of the generated results. This takes advantage of the fact that I can easily encode 3 values
into two bits, without resorting to run-length encoding or fractional bits, _and_ the fact
that the table is palindromic. This allows my implementation to compile to simple 8-bit RISC
instructions for the Atmel ATtiny series without requiring special handling of 16-bit or
larger values.
Because my implementation will use arithmetic shift for right shifts on signed values, I can
make the return value out of bits taken from the table values:
return ( ( int8_t )( ( ( ( ( ENCODER_DIR_SIGNBITS & mask ) >> shift ) << 7 ) ) |
( ( ( ( ENCODER_DIR_ONEBITS & mask ) >> shift ) << 6 ) ) ) ) >> 6;
It may seem like it should be possible to simplify this code, for example by shifting left by
( 7 - shift ) and ( 6 - shift ) instead of using successive right and left shifts, but this
requires a branch to avoid the possibility of doing a left shift by a negative value, which
has undefined results in C.
This is not portable since it depends on right shift doing an _arithmetic_ (sign-preserving)
shift. The behavior of right shift is implementation-defined. If your compiler generates
logical shifts instead of arithmetic shifts, stick with the more portable and readable if/else
implementation to generate the return value:
if ( 0 != ( ENCODER_DIR_SIGNBITS & mask ) )
{
return -1;
}
else
{
return ( int8_t )( ( ENCODER_DIR_ONEBITS & mask ) >> shift );
}
Because I am using #defines for the table values, no SRAM is allocated for them, saving sixteen
bytes, which is very helpful on these small chips (the ATtiny 104 has only 32 bytes of SRAM!) In
fact, on this platform even the return value and non-static local variables are put in registers.
This means that with this implementation, the algorithm uses only _one_ byte of SRAM (holding the
encoder history).
*/
#define ENCODER_DIR_SIGNBITS ( 0b10000010U ) /* If you don't have support for binary constants, 0x82 */
#define ENCODER_DIR_ONEBITS ( 0b10010110U ) /* 0x96 */
inline static int8_t read_encoder(void)
{
static uint8_t AB_history = 0;
uint8_t shift;
uint8_t mask;
AB_history <<= 2;
AB_history |= ( ( ENCODER_READ_REG & ENCODER_A_B_MASK ) >> ENCODER_A_B_SHIFT );
shift = ( AB_history & 0xF );
/*
Take advantage of the fact that the lookup table is palindromic; bits 8..15 should be treated
the same as bits 7..0, so the shift value is always in the range 0..7.
*/
if ( shift > 7 )
{
shift = 15 - shift;
}
mask = 0x1 << shift;
if ( 0 != ( ENCODER_DIR_SIGNBITS & mask ) )
{
return -1;
}
else
{
return ( int8_t )( ( ENCODER_DIR_ONEBITS & mask ) >> shift );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment