Last active
April 6, 2018 11:06
-
-
Save paulrpotts/3cea24b7e003fe00c63ff467a6f567ff to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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