FabGL
ESP32 Display Controller and Graphics Library
MCP23S17.cpp
1 /*
2  Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3  Copyright (c) 2019-2021 Fabrizio Di Vittorio.
4  All rights reserved.
5 
6 
7 * Please contact fdivitto2013@gmail.com if you need a commercial license.
8 
9 
10 * This library and related software is available under GPL v3.
11 
12  FabGL is free software: you can redistribute it and/or modify
13  it under the terms of the GNU General Public License as published by
14  the Free Software Foundation, either version 3 of the License, or
15  (at your option) any later version.
16 
17  FabGL is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 
27 #include <string.h>
28 
29 #include "MCP23S17.h"
30 
31 
32 namespace fabgl {
33 
34 
35 
36 
37 
38 MCP23S17::MCP23S17()
39  : m_SPIDevHandle(nullptr)
40 {
41 }
42 
43 
44 MCP23S17::~MCP23S17()
45 {
46  end();
47 }
48 
49 
50 bool MCP23S17::begin(int MISO, int MOSI, int CLK, int CS, int CSActiveState, int host)
51 {
52  // defaults
53  int defMISO = 35;
54  int defMOSI = 12;
55  int defCLK = 14;
56  int defCS = -1;
57  switch (getChipPackage()) {
59  // setup for TTGO VGA32
60  defMISO = 2;
61  defMOSI = 12;
62  break;
64  // setup for FabGL compatible board
65  defCS = 13;
66  if (CSActiveState == -1)
67  CSActiveState = 1;
68  break;
69  default:
70  break;
71  }
72 
73  if (MISO == -1)
74  MISO = defMISO;
75  if (MOSI == -1)
76  MOSI = defMOSI;
77  if (CS == -1)
78  CS = defCS;
79  if (CLK == -1)
80  CLK = defCLK;
81 
82  m_MISO = int2gpio(MISO);
83  m_MOSI = int2gpio(MOSI);
84  m_CLK = int2gpio(CLK);
85  m_CS = int2gpio(CS);
86  m_SPIHost = (spi_host_device_t) host;
87 
88  bool r = SPIBegin(CSActiveState);
89  if (r) {
90  // disable sequential mode
91  // select bank 0
92  m_IOCON[0] = MCP_IOCON_SEQOP;
93  writeReg(MCP_IOCON, m_IOCON[0]);
94  r = readReg(MCP_IOCON) == m_IOCON[0];
95  }
96  if (!r)
97  end();
98  return r;
99 }
100 
101 
102 // - disable sequential mode
103 // - select bank 0
104 // - enable hardware address
105 void MCP23S17::initDevice(uint8_t hwAddr)
106 {
107  if (hwAddr < MCP_MAXDEVICES)
108  writeReg(MCP_IOCON, MCP_IOCON_SEQOP | MCP_IOCON_HAEN);
109 }
110 
111 
113 {
114  SPIEnd();
115 }
116 
117 
118 bool MCP23S17::SPIBegin(int CSActiveState)
119 {
120  spi_bus_config_t busconf = { }; // zero init
121  busconf.mosi_io_num = m_MOSI;
122  busconf.miso_io_num = m_MISO;
123  busconf.sclk_io_num = m_CLK;
124  busconf.quadwp_io_num = -1;
125  busconf.quadhd_io_num = -1;
126  busconf.flags = SPICOMMON_BUSFLAG_MASTER;
127  auto r = spi_bus_initialize(m_SPIHost, &busconf, MCP_DMACHANNEL);
128  if (r == ESP_OK || r == ESP_ERR_INVALID_STATE) { // ESP_ERR_INVALID_STATE, maybe spi_bus_initialize already called
129  spi_device_interface_config_t devconf = { }; // zero init
130  devconf.mode = 0;
131  devconf.clock_speed_hz = MCP_SPI_FREQ;
132  devconf.spics_io_num = m_CS;
133  devconf.flags = (CSActiveState == 1 ? SPI_DEVICE_POSITIVE_CS : 0);
134  devconf.queue_size = 1;
135  r = spi_bus_add_device(m_SPIHost, &devconf, &m_SPIDevHandle);
136  if (r != ESP_OK)
137  spi_bus_free(m_SPIHost);
138  return r == ESP_OK;
139  }
140  return false;
141 }
142 
143 
144 void MCP23S17::SPIEnd()
145 {
146  if (m_SPIDevHandle) {
147  spi_bus_remove_device(m_SPIDevHandle);
148  m_SPIDevHandle = nullptr;
149  spi_bus_free(m_SPIHost); // this will not free bus if there is a device still connected (ie sdcard)
150  }
151 }
152 
153 
154 void MCP23S17::writeReg(uint8_t addr, uint8_t value, uint8_t hwAddr)
155 {
156  spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
157 
158  uint8_t txdata[3] = { (uint8_t)(0b01000000 | (hwAddr << 1)), addr, value };
159  spi_transaction_t ta;
160  ta.flags = 0;
161  ta.length = 24;
162  ta.rxlength = 0;
163  ta.rx_buffer = nullptr;
164  ta.tx_buffer = txdata;
165  spi_device_transmit(m_SPIDevHandle, &ta);
166 
167  spi_device_release_bus(m_SPIDevHandle);
168 }
169 
170 
171 uint8_t MCP23S17::readReg(uint8_t addr, uint8_t hwAddr)
172 {
173  spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
174 
175  uint8_t txdata[3] = { (uint8_t)(0b01000001 | (hwAddr << 1)), addr };
176  uint8_t rxdata[3] = { 0 };
177  spi_transaction_t ta;
178  ta.flags = 0;
179  ta.length = 24;
180  ta.rxlength = 24;
181  ta.rx_buffer = rxdata;
182  ta.tx_buffer = txdata;
183  uint8_t r = 0;
184  if (spi_device_transmit(m_SPIDevHandle, &ta) == ESP_OK)
185  r = rxdata[2];
186 
187  spi_device_release_bus(m_SPIDevHandle);
188 
189  return r;
190 }
191 
192 
193 void MCP23S17::writeReg16(uint8_t addr, uint16_t value, uint8_t hwAddr)
194 {
195  spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
196 
197  uint8_t txdata[4] = { (uint8_t)(0b01000000 | (hwAddr << 1)), addr, (uint8_t)(value & 0xff), (uint8_t)(value >> 8) };
198  spi_transaction_t ta;
199  ta.flags = 0;
200  ta.length = 32;
201  ta.rxlength = 0;
202  ta.rx_buffer = nullptr;
203  ta.tx_buffer = txdata;
204  spi_device_transmit(m_SPIDevHandle, &ta);
205 
206  spi_device_release_bus(m_SPIDevHandle);
207 }
208 
209 
210 uint16_t MCP23S17::readReg16(uint8_t addr, uint8_t hwAddr)
211 {
212  spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
213 
214  uint8_t txdata[4] = { (uint8_t)(0b01000001 | (hwAddr << 1)), addr };
215  uint8_t rxdata[4] = { 0 };
216  spi_transaction_t ta;
217  ta.flags = 0;
218  ta.length = 32;
219  ta.rxlength = 32;
220  ta.rx_buffer = rxdata;
221  ta.tx_buffer = txdata;
222  uint16_t r = 0;
223  if (spi_device_transmit(m_SPIDevHandle, &ta) == ESP_OK)
224  r = rxdata[2] | (rxdata[3] << 8);
225 
226  spi_device_release_bus(m_SPIDevHandle);
227 
228  return r;
229 }
230 
231 
232 void MCP23S17::enableINTMirroring(bool value, uint8_t hwAddr)
233 {
234  writeReg(MCP_IOCON, value ? m_IOCON[hwAddr] | MCP_IOCON_MIRROR : m_IOCON[hwAddr] & ~MCP_IOCON_MIRROR, hwAddr);
235 }
236 
237 
238 void MCP23S17::enableINTOpenDrain(bool value, uint8_t hwAddr)
239 {
240  writeReg(MCP_IOCON, value ? m_IOCON[hwAddr] | MCP_IOCON_ODR : m_IOCON[hwAddr] & ~MCP_IOCON_ODR, hwAddr);
241 }
242 
243 
244 void MCP23S17::setINTActiveHigh(bool value, uint8_t hwAddr)
245 {
246  writeReg(MCP_IOCON, value ? m_IOCON[hwAddr] | MCP_IOCON_INTPOL : m_IOCON[hwAddr] & ~MCP_IOCON_INTPOL, hwAddr);
247 }
248 
249 
250 void MCP23S17::configureGPIO(int gpio, MCPDir dir, bool pullup, uint8_t hwAddr)
251 {
252  uint8_t mask = MCP_GPIO2MASK(gpio);
253  // direction
254  uint8_t reg = MCP_GPIO2REG(MCP_IODIR, gpio);
255  if (dir == MCPDir::Input)
256  writeReg(reg, readReg(reg, hwAddr) | mask, hwAddr);
257  else
258  writeReg(reg, readReg(reg, hwAddr) & ~mask, hwAddr);
259  // pull-up
260  reg = MCP_GPIO2REG(MCP_GPPU, gpio);
261  writeReg(reg, (readReg(reg, hwAddr) & ~mask) | ((int)pullup * mask), hwAddr);
262 }
263 
264 
265 void MCP23S17::writeGPIO(int gpio, bool value, uint8_t hwAddr)
266 {
267  uint8_t olat = readReg(MCP_GPIO2REG(MCP_OLAT, gpio), hwAddr);
268  uint8_t mask = MCP_GPIO2MASK(gpio);
269  uint8_t reg = MCP_GPIO2REG(MCP_OLAT, gpio);
270  writeReg(reg, value ? olat | mask : olat & ~mask, hwAddr);
271 }
272 
273 
274 bool MCP23S17::readGPIO(int gpio, uint8_t hwAddr)
275 {
276  return readReg(MCP_GPIO2REG(MCP_GPIO, gpio), hwAddr) & MCP_GPIO2MASK(gpio);
277 }
278 
279 
280 void MCP23S17::enableInterrupt(int gpio, MCPIntTrigger trigger, bool defaultValue, uint8_t hwAddr)
281 {
282  uint8_t mask = MCP_GPIO2MASK(gpio);
283  // set interrupt trigger
284  if (trigger == MCPIntTrigger::DefaultChange) {
285  // interrupt triggered when value is different than "defaultValue)
286  writeReg(MCP_GPIO2REG(MCP_INTCON, gpio), readReg(MCP_GPIO2REG(MCP_INTCON, gpio), hwAddr) | mask, hwAddr);
287  writeReg(MCP_GPIO2REG(MCP_DEFVAL, gpio), (readReg(MCP_GPIO2REG(MCP_DEFVAL, gpio), hwAddr) & ~mask) | ((int)defaultValue * mask), hwAddr);
288  } else {
289  // interrupt triggered when value is different than previous value
290  writeReg(MCP_GPIO2REG(MCP_INTCON, gpio), readReg(MCP_GPIO2REG(MCP_INTCON, gpio), hwAddr) & ~mask, hwAddr);
291  }
292  // enable interrupt
293  writeReg(MCP_GPIO2REG(MCP_GPINTEN, gpio), readReg(MCP_GPIO2REG(MCP_GPINTEN, gpio), hwAddr) | mask, hwAddr);
294 }
295 
296 
297 void MCP23S17::disableInterrupt(int gpio, uint8_t hwAddr)
298 {
299  uint8_t reg = MCP_GPIO2REG(MCP_GPINTEN, gpio);
300  writeReg(reg, readReg(reg, hwAddr) & ~MCP_GPIO2MASK(gpio), hwAddr);
301 }
302 
303 
304 void MCP23S17::writePort(int port, void const * buffer, size_t length, uint8_t hwAddr)
305 {
306  // - disable sequential mode
307  // - select bank 1 (to avoid switching between A and B registers)
308  writeReg(MCP_IOCON, m_IOCON[hwAddr] | MCP_IOCON_SEQOP | MCP_IOCON_BANK);
309 
310  spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
311 
312  spi_transaction_ext_t ta = { };
313  ta.command_bits = 8;
314  ta.address_bits = 8;
315  ta.base.cmd = 0b01000000 | (hwAddr << 1); // write
316  ta.base.addr = MCP_BNK1_OLAT + port * 0x10;
317  ta.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;
318  ta.base.length = 16 + 8 * length;
319  ta.base.rxlength = 0;
320  ta.base.rx_buffer = nullptr;
321  ta.base.tx_buffer = buffer;
322  spi_device_polling_transmit(m_SPIDevHandle, (spi_transaction_t*) &ta);
323 
324  spi_device_release_bus(m_SPIDevHandle);
325 
326  // restore IOCON
327  writeReg(MCP_BNK1_IOCON, m_IOCON[hwAddr]);
328 }
329 
330 
331 void MCP23S17::readPort(int port, void * buffer, size_t length, uint8_t hwAddr)
332 {
333  // - disable sequential mode
334  // - select bank 1 (to avoid switching between A and B registers)
335  writeReg(MCP_IOCON, m_IOCON[hwAddr] | MCP_IOCON_SEQOP | MCP_IOCON_BANK);
336 
337  spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
338 
339  spi_transaction_ext_t ta = { };
340  ta.command_bits = 8;
341  ta.address_bits = 8;
342  ta.base.cmd = 0b01000001 | (hwAddr << 1); // read
343  ta.base.addr = MCP_BNK1_GPIO + port * 0x10;
344  ta.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;
345  ta.base.length = 16 + 8 * length;
346  ta.base.rxlength = 8 * length;
347  ta.base.rx_buffer = buffer;
348  ta.base.tx_buffer = nullptr;
349  spi_device_polling_transmit(m_SPIDevHandle, (spi_transaction_t*) &ta);
350 
351  spi_device_release_bus(m_SPIDevHandle);
352 
353  // restore IOCON
354  writeReg(MCP_BNK1_IOCON, m_IOCON[hwAddr]);
355 }
356 
357 
358 
359 
360 
361 
362 
363 } // fabgl namespace
364 
bool readGPIO(int gpio, uint8_t hwAddr=0)
Reads input status of a pin.
Definition: MCP23S17.cpp:274
void writeReg16(uint8_t addr, uint16_t value, uint8_t hwAddr=0)
Writes 16 bit value to two consecutive registers.
Definition: MCP23S17.cpp:193
uint8_t readReg(uint8_t addr, uint8_t hwAddr=0)
Reads 8 bit value from an internal register.
Definition: MCP23S17.cpp:171
MCPDir
Represents GPIO directioon.
Definition: MCP23S17.h:126
uint8_t readPort(int port, uint8_t hwAddr=0)
Gets status of input pins of specified port.
Definition: MCP23S17.h:365
bool begin(int MISO=-1, int MOSI=-1, int CLK=-1, int CS=-1, int CSActiveState=-1, int host=HSPI_HOST)
Initializes MCP23S17 driver.
Definition: MCP23S17.cpp:50
void writeGPIO(int gpio, bool value, uint8_t hwAddr=0)
Sets output status of a pin.
Definition: MCP23S17.cpp:265
void disableInterrupt(int gpio, uint8_t hwAddr=0)
Disables any interrupt on the specified pin.
Definition: MCP23S17.cpp:297
void end()
Deinitializes MCP23S17 driver.
Definition: MCP23S17.cpp:112
Definition: canvas.cpp:36
void writeReg(uint8_t addr, uint8_t value, uint8_t hwAddr=0)
Writes 8 bit value to an internal register.
Definition: MCP23S17.cpp:154
void enableINTOpenDrain(bool value, uint8_t hwAddr=0)
Enables/disables the INT pin open-drain.
Definition: MCP23S17.cpp:238
MCPIntTrigger
Represents interrupt trigger mode.
Definition: MCP23S17.h:135
This file contains the MCP23S17 driver class.
void setINTActiveHigh(bool value, uint8_t hwAddr=0)
Sets the polarity of the INT pins.
Definition: MCP23S17.cpp:244
void configureGPIO(int gpio, MCPDir dir, bool pullup=false, uint8_t hwAddr=0)
Configure a pin direction and pullup.
Definition: MCP23S17.cpp:250
void writePort(int port, uint8_t value, uint8_t hwAddr=0)
Sets status of output pins of specified port.
Definition: MCP23S17.h:350
void initDevice(uint8_t hwAddr)
Initializes additional MCP23S17 devices connected to the same SPI bus but with a different hardware a...
Definition: MCP23S17.cpp:105
void enableINTMirroring(bool value, uint8_t hwAddr=0)
Enables/disables INTs pins mirroring.
Definition: MCP23S17.cpp:232
uint16_t readReg16(uint8_t addr, uint8_t hwAddr=0)
Reads 16 bit value from two consecutive registers.
Definition: MCP23S17.cpp:210
void enableInterrupt(int gpio, MCPIntTrigger trigger, bool defaultValue=false, uint8_t hwAddr=0)
Enables interrupt on the specific pin.
Definition: MCP23S17.cpp:280