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