opt_encoder_tiny.c (4001B)
1 /* Copyright 2023 Leorize <leorize+oss@disroot.org> 2 * Copyright 2011 Ben Buxton <bb@cactii.net> 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 #include "opt_encoder.h" 18 #include <stdbool.h> 19 #include <stdint.h> 20 21 /* An extremely simple implementation of the encoder: 22 * 23 * For read out, the mechanism mimics that of a Schmitt trigger, with 24 * statically defined high/low thresholds used instead of computing 25 * one at runtime. 26 * 27 * The advantage of this approach is computing less in the decoder 28 * implementation and allow the state to be measured before the wheel 29 * moved. 30 * 31 * Compared to opt_encoder_simple.c, the use of an intermediary state 32 * reduces sensitivity and de-sensitize against tiny movements caused 33 * when lifting finger off the wheel. 34 * 35 * For turning decoded values into rotation, an algorithm inspired by 36 * http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html 37 * is employed. 38 */ 39 40 #if !defined(ENCODER_LOW_THRES_A) || !defined(ENCODER_HIGH_THRES_A) 41 # error "The thresholds for phototransistors A is not defined in config.h" 42 #endif 43 #if !defined(ENCODER_LOW_THRES_B) || !defined(ENCODER_HIGH_THRES_B) 44 # error "The thresholds for phototransistors B is not defined in config.h" 45 #endif 46 /* 47 * Sample values, captured for a Ploopy Mini Trackball 48 * 49 * The following min-max values was captured by aggregating data recorded 50 * when debug_encoder is enabled: 51 * 52 * A: min: 0, max: 214 53 * B: min: 0, max: 204 54 * 55 * The threshold specified is then defined at the 1/4 and the 3/4 points. 56 * 57 * As these values might vary between units, you're encouraged to 58 * measure your own. 59 */ 60 #if 0 61 # define ENCODER_LOW_THRES_A 53 62 # define ENCODER_HIGH_THRES_A 161 63 # define ENCODER_LOW_THRES_B 52 64 # define ENCODER_HIGH_THRES_B 153 65 #endif 66 67 /* Utilities for composing the encoder state */ 68 #define MAKE_STATE(HI_A, HI_B) (((uint8_t)((HI_A) & 0x1) << 1) | ((uint8_t)((HI_B) & 0x1))) 69 #define STATE_A(st) ((st & 0x2) >> 1) 70 #define STATE_B(st) (st & 0x1) 71 72 typedef enum { 73 START, 74 DOWN_BEGIN, 75 UP_BEGIN, 76 START_MID, 77 DOWN_BEGIN_MID, 78 UP_BEGIN_MID, 79 STATE_MASK = 0xf, /* 0b1111 */ 80 EMIT_UP = 0x10, 81 EMIT_UP_MID = EMIT_UP & START_MID, 82 EMIT_DOWN = 0x80, 83 EMIT_DOWN_MID = EMIT_DOWN & START_MID, 84 EMIT_MASK = 0xf0 85 } encoder_state_t; 86 87 static encoder_state_t state; 88 static uint8_t encState; 89 90 static const uint8_t transitions[] = { 91 // clang-format off 92 // START -> 00, 01, 10, 11 93 START, DOWN_BEGIN, UP_BEGIN, START_MID, 94 // DOWN_BEGIN -> 00, 01, 10, 11 95 START, DOWN_BEGIN, START, EMIT_DOWN_MID, 96 // UP_BEGIN -> 00, 01, 10, 11 97 START, START, UP_BEGIN, EMIT_UP_MID, 98 // START_MID -> 00, 01, 10, 11 99 START, UP_BEGIN_MID, DOWN_BEGIN_MID, START_MID, 100 // DOWN_BEGIN_MID -> 00, 01, 10, 11 101 EMIT_DOWN, START_MID, DOWN_BEGIN_MID, START_MID, 102 // UP_BEGIN_MID -> 00, 01, 10, 11 103 EMIT_UP, UP_BEGIN_MID, START_MID, START_MID, 104 // clang-format on 105 }; 106 107 void opt_encoder_init(void) { 108 state = START; 109 encState = 0; 110 } 111 112 int8_t opt_encoder_handler(uint16_t encA, uint16_t encB) { 113 encState = MAKE_STATE((STATE_A(encState) & (encA > ENCODER_LOW_THRES_A)) | (encA > ENCODER_HIGH_THRES_A), (STATE_B(encState) & (encB > ENCODER_LOW_THRES_B)) | (encB > ENCODER_HIGH_THRES_B)); 114 state = transitions[((state & STATE_MASK) << 2) + encState]; 115 return state & EMIT_MASK; 116 }