|
| 1 | +/* |
| 2 | + * This file is part of the MicroPython project, http://micropython.org/ |
| 3 | + * |
| 4 | + * The MIT License (MIT) |
| 5 | + * |
| 6 | + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries |
| 7 | + * |
| 8 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 9 | + * of this software and associated documentation files (the "Software"), to deal |
| 10 | + * in the Software without restriction, including without limitation the rights |
| 11 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 12 | + * copies of the Software, and to permit persons to whom the Software is |
| 13 | + * furnished to do so, subject to the following conditions: |
| 14 | + * |
| 15 | + * The above copyright notice and this permission notice shall be included in |
| 16 | + * all copies or substantial portions of the Software. |
| 17 | + * |
| 18 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 19 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 20 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 21 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 22 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 23 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 24 | + * THE SOFTWARE. |
| 25 | + */ |
| 26 | + |
| 27 | +#include "py/runtime.h" |
| 28 | + |
| 29 | +#include <hardware/regs/pio.h> |
| 30 | +#include "common-hal/rotaryio/IncrementalEncoder.h" |
| 31 | +#include "bindings/rp2pio/__init__.h" |
| 32 | +#include "bindings/rp2pio/StateMachine.h" |
| 33 | + |
| 34 | +STATIC const uint16_t encoder[] = { |
| 35 | + // again: |
| 36 | + // in pins, 2 |
| 37 | + 0x4002, |
| 38 | + // mov x, isr |
| 39 | + 0xa026, |
| 40 | + // jmp x!=y, push_data |
| 41 | + 0x00a5, |
| 42 | + // mov isr, null |
| 43 | + 0xa0c3, |
| 44 | + // jmp again |
| 45 | + 0x0000, |
| 46 | + // push_data: |
| 47 | + // push |
| 48 | + 0x8020, |
| 49 | + // mov y, x |
| 50 | + 0xa041, |
| 51 | +}; |
| 52 | + |
| 53 | +STATIC const uint16_t encoder_init[] = { |
| 54 | + // set y, 31 |
| 55 | + 0xe05f, |
| 56 | +}; |
| 57 | + |
| 58 | +STATIC void incrementalencoder_interrupt_handler(void *self_in); |
| 59 | + |
| 60 | +void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencoder_obj_t* self, |
| 61 | + const mcu_pin_obj_t* pin_a, const mcu_pin_obj_t* pin_b) { |
| 62 | + mp_obj_t pins[] = {MP_OBJ_FROM_PTR(pin_a), MP_OBJ_FROM_PTR(pin_b)}; |
| 63 | + if (!common_hal_rp2pio_pins_are_sequential(2, pins)) { |
| 64 | + mp_raise_RuntimeError(translate("Pins must be sequential")); |
| 65 | + } |
| 66 | + |
| 67 | + self->position = 0; |
| 68 | + self->quarter_count = 0; |
| 69 | + |
| 70 | + common_hal_rp2pio_statemachine_construct(&self->state_machine, |
| 71 | + encoder, MP_ARRAY_SIZE(encoder), |
| 72 | + 1000000, |
| 73 | + encoder_init, MP_ARRAY_SIZE(encoder_init), // init |
| 74 | + NULL, 1, 0, 0xffffffff, // out pin |
| 75 | + pin_a, 2, // in pins |
| 76 | + 3, 0, // in pulls |
| 77 | + NULL, 0, 0, 0x1f, // set pins |
| 78 | + NULL, 0, 0, 0x1f, // sideset pins |
| 79 | + true, // exclusive pin use |
| 80 | + false, 32, false, // out settings |
| 81 | + false, // Wait for txstall |
| 82 | + false, 32, false); // in settings |
| 83 | + |
| 84 | + common_hal_rp2pio_statemachine_run(&self->state_machine, encoder_init, MP_ARRAY_SIZE(encoder_init)); |
| 85 | + |
| 86 | + // We're guaranteed by the init code that some output will be available promptly |
| 87 | + uint8_t state; |
| 88 | + common_hal_rp2pio_statemachine_readinto(&self->state_machine, &state, 1, 1); |
| 89 | + // Top two bits of self->last_state don't matter, because they'll be gone as soon as |
| 90 | + // interrupt handler is called. |
| 91 | + self->last_state = state & 3; |
| 92 | + |
| 93 | + common_hal_rp2pio_statemachine_set_interrupt_handler(&self->state_machine, incrementalencoder_interrupt_handler, self, PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS); |
| 94 | +} |
| 95 | + |
| 96 | +bool common_hal_rotaryio_incrementalencoder_deinited(rotaryio_incrementalencoder_obj_t* self) { |
| 97 | + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); |
| 98 | +} |
| 99 | + |
| 100 | +void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_obj_t* self) { |
| 101 | + if (common_hal_rotaryio_incrementalencoder_deinited(self)) { |
| 102 | + return; |
| 103 | + } |
| 104 | + common_hal_rp2pio_statemachine_deinit(&self->state_machine); |
| 105 | +} |
| 106 | + |
| 107 | +mp_int_t common_hal_rotaryio_incrementalencoder_get_position(rotaryio_incrementalencoder_obj_t* self) { |
| 108 | + return self->position; |
| 109 | +} |
| 110 | + |
| 111 | +void common_hal_rotaryio_incrementalencoder_set_position(rotaryio_incrementalencoder_obj_t* self, |
| 112 | + mp_int_t new_position) { |
| 113 | + self->position = new_position; |
| 114 | +} |
| 115 | + |
| 116 | +STATIC void incrementalencoder_interrupt_handler(void *self_in) { |
| 117 | + rotaryio_incrementalencoder_obj_t* self = self_in; |
| 118 | + // This table also works for detent both at 11 and 00 |
| 119 | + // For 11 at detent: |
| 120 | + // Turning cw: 11->01->00->10->11 |
| 121 | + // Turning ccw: 11->10->00->01->11 |
| 122 | + // For 00 at detent: |
| 123 | + // Turning cw: 00->10->11->10->00 |
| 124 | + // Turning ccw: 00->01->11->10->00 |
| 125 | + |
| 126 | + // index table by state <oldA><oldB><newA><newB> |
| 127 | + #define BAD 7 |
| 128 | + static const int8_t transitions[16] = { |
| 129 | + 0, // 00 -> 00 no movement |
| 130 | + -1, // 00 -> 01 3/4 ccw (11 detent) or 1/4 ccw (00 at detent) |
| 131 | + +1, // 00 -> 10 3/4 cw or 1/4 cw |
| 132 | + BAD, // 00 -> 11 non-Gray-code transition |
| 133 | + +1, // 01 -> 00 2/4 or 4/4 cw |
| 134 | + 0, // 01 -> 01 no movement |
| 135 | + BAD, // 01 -> 10 non-Gray-code transition |
| 136 | + -1, // 01 -> 11 4/4 or 2/4 ccw |
| 137 | + -1, // 10 -> 00 2/4 or 4/4 ccw |
| 138 | + BAD, // 10 -> 01 non-Gray-code transition |
| 139 | + 0, // 10 -> 10 no movement |
| 140 | + +1, // 10 -> 11 4/4 or 2/4 cw |
| 141 | + BAD, // 11 -> 00 non-Gray-code transition |
| 142 | + +1, // 11 -> 01 1/4 or 3/4 cw |
| 143 | + -1, // 11 -> 10 1/4 or 3/4 ccw |
| 144 | + 0, // 11 -> 11 no movement |
| 145 | + }; |
| 146 | + |
| 147 | + while (common_hal_rp2pio_statemachine_get_in_waiting(&self->state_machine)) { |
| 148 | + // Bypass all the logic of StateMachine.c:_transfer, we need something |
| 149 | + // very simple and fast for an interrupt! |
| 150 | + uint8_t new = self->state_machine.pio->rxf[self->state_machine.state_machine]; |
| 151 | + |
| 152 | + // Shift the old AB bits to the "old" position, and set the new AB bits. |
| 153 | + self->last_state = (self->last_state & 0x3) << 2 | (new & 0x3); |
| 154 | + |
| 155 | + int8_t quarter_incr = transitions[self->last_state]; |
| 156 | + if (quarter_incr == BAD) { |
| 157 | + // Missed a transition. We don't know which way we're going, so do nothing. |
| 158 | + return; |
| 159 | + } |
| 160 | + |
| 161 | + self->quarter_count += quarter_incr; |
| 162 | + if (self->quarter_count >= 4) { |
| 163 | + self->position += 1; |
| 164 | + self->quarter_count = 0; |
| 165 | + } else if (self->quarter_count <= -4) { |
| 166 | + self->position -= 1; |
| 167 | + self->quarter_count = 0; |
| 168 | + } |
| 169 | + } |
| 170 | +} |
0 commit comments