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-2022 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
35namespace fabgl {
36
37
38
39
40
41MCP23S17::MCP23S17()
42 : m_SPIDevHandle(nullptr)
43{
44}
45
46
47MCP23S17::~MCP23S17()
48{
49 end();
50}
51
52
53bool 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()) {
61 case ChipPackage::ESP32PICOD4:
62 // setup for TTGO VGA32
63 defMISO = 2;
64 defMOSI = 12;
65 break;
66 case ChipPackage::ESP32D0WDQ5:
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
101bool 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
113void MCP23S17::end()
114{
115 SPIEnd();
116}
117
118
119bool 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
145void 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
156void 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
173uint8_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
195void 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
212uint16_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
234void 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
240void 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
246void 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
252void 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
267void 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
276bool MCP23S17::readGPIO(int gpio, uint8_t hwAddr)
277{
278 return readReg(MCP_GPIO2REG(MCP_GPIO, gpio), hwAddr) & MCP_GPIO2MASK(gpio);
279}
280
281
282void 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
299void 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
306void 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
333void 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
This file contains the MCP23S17 driver class.
MCPDir
Represents GPIO directioon.
Definition: MCP23S17.h:126
MCPIntTrigger
Represents interrupt trigger mode.
Definition: MCP23S17.h:135