Control Surface  1.2.0
MIDI Control Surface library for Arduino
MCP23017Encoders.hpp
Go to the documentation of this file.
1 #pragma once
2 
5 
6 AH_DIAGNOSTIC_WERROR() // Enable errors on warnings
7 
9 #include <AH/Arduino-Wrapper.h>
11 
12 #include <AH/Hardware/ExtendedInputOutput/ExtendedInputOutput.hpp>
13 #include <AH/STL/type_traits> // std::conditional
14 
16 
17 constexpr static int8_t MCP23017Encoders_lut[16] = {
18  0, // 0 0 0 0
19  +1, // 0 0 0 1
20  -1, // 0 0 1 0
21  +2, // 0 0 1 1
22  -1, // 0 1 0 0
23  0, // 0 1 0 1
24  -2, // 0 1 1 0
25  +1, // 0 1 1 1
26  +1, // 1 0 0 0
27  -2, // 1 0 0 1
28  0, // 1 0 1 0
29  -1, // 1 0 1 1
30  +2, // 1 1 0 0
31  -1, // 1 1 0 1
32  +1, // 1 1 1 0
33  0, // 1 1 1 1
34 };
35 
56 template <class WireType, class EncoderPositionType = int32_t,
57  bool InterruptSafe = false>
59  private:
61  typename std::conditional<InterruptSafe, volatile EncoderPositionType,
62  EncoderPositionType>::type;
64  typename std::conditional<InterruptSafe, volatile uint16_t,
65  uint16_t>::type;
66 
67  constexpr static uint8_t I2C_BASE_ADDRESS = 0x20;
68 
69  WireType *wire;
70  uint8_t address;
72 
75 
76  protected:
78  template <size_t N>
79  void writeI2C(const uint8_t (&values)[N]) {
80  this->wire->beginTransmission(address);
81  this->wire->write(values, N);
82  this->wire->endTransmission();
83  }
84 
93  template <class... Args>
94  void writeI2C(uint8_t addr, Args... values) {
95  const uint8_t v[] = {addr, static_cast<uint8_t>(values)...};
96  writeI2C(v);
97  }
98 
100  uint16_t readGPIO() {
101  // No need to specify the register address, since this was done in the
102  // begin method, and the MCP23017 mode was set to Byte mode with
103  // IOCON.BANK = 0 (see §3.2.1 in the datasheet).
104  //
105  // TODO:
106  // For some reason, it sometimes seems to mess up though, and it'll read
107  // the wrong register, so we'll select the register again (for now).
108 
109  writeI2C(0x12); // GPIOA
110 
111  this->wire->requestFrom(address, uint8_t(2));
112  uint8_t a = this->wire->read();
113  uint16_t b = this->wire->read();
114  return a | (b << 8);
115  }
116 
117  public:
136  MCP23017Encoders(WireType &wire, uint8_t addr_offset = 0,
137  pin_t interrupt_pin = NO_PIN)
138  : wire(&wire), address(I2C_BASE_ADDRESS | addr_offset),
139  interrupt_pin(interrupt_pin) {}
140 
155  void begin() {
156  // Set the IOCON register (configuration register)
157  writeI2C(0x0A, // IOCON register for BANK=0
158  0b01100100);
159  // │││││││└─ Unimplemented
160  // ││││││└── INTPOL = Active-low
161  // │││││└─── ODR = Open-drain output (overrides the INTPOL bit)
162  // ││││└──── HAEN = Disables the MCP23S17 address pins
163  // │││└───── DISSLW = Slew rate enabled
164  // ││└────── SEQOP = Sequential operation disabled, address pointer does not increment
165  // │└─────── MIRROR = The INT pins are internally connected
166  // └──────── BANK = The registers are in the same bank (addresses are sequential)
167 
168  // Set all GPIO pins to input mode
169  writeI2C(0x00, // IODIRA
170  0xFF, // input mode for GPIO A
171  0xFF); // input mode for GPIO B
172 
173  // Enable all pin change interrupts
174  writeI2C(0x04, // GPINTENA
175  0xFF, // interrupt enable for GPIO A
176  0xFF); // interrupt enable for GPIO B
177 
178  // Enable all internal pullups
179  writeI2C(0x0C, // GPPUA
180  0xFF, // pullup enable for GPIO A
181  0xFF); // pullup enable for GPIO B
182 
183  // Interrupts are configured in open-drain mode, so enable
184  // the internal pullup resistor on the Arduino pin that
185  // reads the interrupt pin.
186  if (interrupt_pin != NO_PIN)
187  ExtIO::pinMode(interrupt_pin, INPUT_PULLUP);
188 
189  // Set the address pointer to the GPIOA register.
190  // This means that subsequent reads will all toggle between the
191  // GPIOA and GPIOB register, so we can speedup reading the GPIO
192  // by not having to send an opcode/register address each time.
193  writeI2C(0x12); // GPIOA
194 
196  state = readGPIO();
197  }
198 
209  void update() {
210  // Only update if a pin change interrupt happened
211  if (interrupt_pin != NO_PIN &&
212  ExtIO::digitalRead(interrupt_pin) == HIGH)
213  return;
214  // Read both GPIO A and B
215  uint16_t newstate = readGPIO();
216  uint16_t oldstate = state;
217 
218  // If the state didn't change, do nothing
219  if (newstate == oldstate)
220  return;
221 
222  // Save the new state
223  state = newstate;
224 
225  // For each encoder, compare the new state of its two pins
226  // to the old state. Combine the four states into a 4-bit
227  // number and use a lookup table to determine the delta between
228  // the two encoder positions.
229  for (uint8_t i = 0; i < 8; ++i) {
230  uint8_t change =
231  uint8_t(newstate) & 0b11; // Top two bits are new pin states
232  change <<= 2;
233  change |=
234  uint8_t(oldstate) & 0b11; // Bottom two bits are old pin states
235  auto delta =
236  static_cast<EncoderPositionType>(MCP23017Encoders_lut[change]);
237  if (delta != 0) { // small speedup on AVR
238  positions[i] += delta;
239  }
240  oldstate >>= 2;
241  newstate >>= 2;
242  }
243  }
244 
255  EncoderPositionType read(uint8_t idx) const {
256  if (InterruptSafe) {
257  noInterrupts();
258  EncoderPositionType ret = positions[idx];
259  interrupts();
260  return ret;
261  } else {
262  return positions[idx];
263  }
264  }
265 
276  EncoderPositionType readAndReset(uint8_t idx) {
277  if (InterruptSafe) {
278  noInterrupts();
279  EncoderPositionType ret = positions[idx];
280  positions[idx] = 0;
281  interrupts();
282  return ret;
283  } else {
284  EncoderPositionType ret = positions[idx];
285  positions[idx] = 0;
286  return ret;
287  }
288  }
289 
300  void write(uint8_t idx, EncoderPositionType pos) {
301  if (InterruptSafe) {
302  noInterrupts();
303  positions[idx] = pos;
304  interrupts();
305  } else {
306  positions[idx] = pos;
307  }
308  }
309 
315  private:
316  friend class MCP23017Encoders;
317 
320 
322  : position(position) {}
323 
324  public:
327  EncoderPositionType read() const {
328  if (InterruptSafe) {
329  noInterrupts();
330  EncoderPositionType ret = *position;
331  interrupts();
332  return ret;
333  } else {
334  return *position;
335  }
336  }
337 
340  EncoderPositionType readAndReset() {
341  if (InterruptSafe) {
342  noInterrupts();
343  EncoderPositionType ret = *position;
344  *position = 0;
345  interrupts();
346  return ret;
347  } else {
348  EncoderPositionType ret = *position;
349  *position = 0;
350  return ret;
351  }
352  }
353 
356  void write(EncoderPositionType pos) {
357  if (InterruptSafe) {
358  noInterrupts();
359  *position = pos;
360  interrupts();
361  } else {
362  *position = pos;
363  }
364  }
365  };
366 
374  MCP23017Encoder operator[](uint8_t index) {
375  if (index >= 8)
376  index = 7;
377  return &positions[index];
378  }
379 };
380 
382 
AH::MCP23017Encoders::interrupt_pin
pin_t interrupt_pin
Definition: MCP23017Encoders.hpp:71
Warnings.hpp
AH::MCP23017Encoders::address
uint8_t address
Definition: MCP23017Encoders.hpp:70
AH::ExtIO::digitalRead
int digitalRead(pin_t pin)
An ExtIO version of the Arduino function.
Definition: ExtendedInputOutput.cpp:60
AH::MCP23017Encoders::state
StateStorageType state
Definition: MCP23017Encoders.hpp:73
AH::pin_t
uint16_t pin_t
The type for Arduino pins (and ExtendedIOElement pins).
Definition: Hardware-Types.hpp:17
AH::MCP23017Encoders::readAndReset
EncoderPositionType readAndReset(uint8_t idx)
Read the position of the given encoder and reset it to zero.
Definition: MCP23017Encoders.hpp:276
AH::MCP23017Encoders::writeI2C
void writeI2C(uint8_t addr, Args... values)
Write any data to the MCP23017.
Definition: MCP23017Encoders.hpp:94
AH::MCP23017Encoders::EncoderPositionStorageType
typename std::conditional< InterruptSafe, volatile EncoderPositionType, EncoderPositionType >::type EncoderPositionStorageType
Definition: MCP23017Encoders.hpp:62
AH_DIAGNOSTIC_POP
#define AH_DIAGNOSTIC_POP()
Definition: Warnings.hpp:36
AH::MCP23017Encoders::readGPIO
uint16_t readGPIO()
Read the state of all GPIO pins.
Definition: MCP23017Encoders.hpp:100
AH::MCP23017Encoders::StateStorageType
typename std::conditional< InterruptSafe, volatile uint16_t, uint16_t >::type StateStorageType
Definition: MCP23017Encoders.hpp:65
AH::MCP23017Encoders::MCP23017Encoder::readAndReset
EncoderPositionType readAndReset()
Read the position of the encoder and reset it to zero.
Definition: MCP23017Encoders.hpp:340
AH::MCP23017Encoders::MCP23017Encoders
MCP23017Encoders(WireType &wire, uint8_t addr_offset=0, pin_t interrupt_pin=NO_PIN)
Constructor.
Definition: MCP23017Encoders.hpp:136
HIGH
const PinStatus_t HIGH
Definition: ExtendedInputOutput.hpp:56
AH::MCP23017Encoders::writeI2C
void writeI2C(const uint8_t(&values)[N])
Write any data to the MCP23017.
Definition: MCP23017Encoders.hpp:79
AH::MCP23017Encoders::operator[]
MCP23017Encoder operator[](uint8_t index)
Get a proxy to one of the encoders managed by this MCP23017.
Definition: MCP23017Encoders.hpp:374
AH::MCP23017Encoders::MCP23017Encoder::position
EncoderPositionStorageType * position
A pointer to the position value inside of the MCP23017Encoders class.
Definition: MCP23017Encoders.hpp:319
AH::MCP23017Encoders::update
void update()
If the state of the MCP23017's GPIO changed, read the new state and update the encoder positions.
Definition: MCP23017Encoders.hpp:209
AH::MCP23017Encoders::MCP23017Encoder::read
EncoderPositionType read() const
Read the position of the encoder.
Definition: MCP23017Encoders.hpp:327
AH::MCP23017Encoders::MCP23017Encoder::write
void write(EncoderPositionType pos)
Set the position of the encoder.
Definition: MCP23017Encoders.hpp:356
AH::MCP23017Encoders::MCP23017Encoder
Proxy to access a single encoder of the 8 encoders managed by MCP23017Encoders.
Definition: MCP23017Encoders.hpp:314
AH::NO_PIN
constexpr pin_t NO_PIN
A special pin number that indicates an unused or invalid pin.
Definition: Hardware-Types.hpp:24
AH_DIAGNOSTIC_EXTERNAL_HEADER
#define AH_DIAGNOSTIC_EXTERNAL_HEADER()
Definition: Warnings.hpp:37
AH::MCP23017Encoders_lut
constexpr static int8_t MCP23017Encoders_lut[16]
Definition: MCP23017Encoders.hpp:17
AH::MCP23017Encoders::MCP23017Encoder::MCP23017Encoder
MCP23017Encoder(EncoderPositionStorageType *position)
Definition: MCP23017Encoders.hpp:321
AH::MCP23017Encoders
Class for reading 8 rotary encoders using a MCP23017 I²C port expander.
Definition: MCP23017Encoders.hpp:58
AH::MCP23017Encoders::wire
WireType * wire
Definition: MCP23017Encoders.hpp:69
AH::ExtIO::pinMode
void pinMode(pin_t pin, PinMode_t mode)
An ExtIO version of the Arduino function.
Definition: ExtendedInputOutput.cpp:36
AH::MCP23017Encoders::begin
void begin()
Initialize the MCP23017.
Definition: MCP23017Encoders.hpp:155
AH_DIAGNOSTIC_WERROR
#define AH_DIAGNOSTIC_WERROR()
Definition: Warnings.hpp:35
BEGIN_AH_NAMESPACE
#define BEGIN_AH_NAMESPACE
Definition: AH/Settings/NamespaceSettings.hpp:9
AH::MCP23017Encoders::write
void write(uint8_t idx, EncoderPositionType pos)
Set the position of the given encoder.
Definition: MCP23017Encoders.hpp:300
END_AH_NAMESPACE
#define END_AH_NAMESPACE
Definition: AH/Settings/NamespaceSettings.hpp:10
NamespaceSettings.hpp
INPUT_PULLUP
const PinMode_t INPUT_PULLUP
Definition: ExtendedInputOutput.hpp:61
AH::MCP23017Encoders::read
EncoderPositionType read(uint8_t idx) const
Read the position of the given encoder.
Definition: MCP23017Encoders.hpp:255