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