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 if (getChipPackage() == ChipPackage::ESP32D0WDQ5) {
152 fabgl::configureGPIO(m_CS, GPIO_MODE_OUTPUT);
153 gpio_set_level(m_CS, 1);
154 }
155 } else {
156 spi_bus_free(m_SPIHost);
157 }
158 }
159}
160
161
162void MCP23S17::writeReg(uint8_t addr, uint8_t value, uint8_t hwAddr)
163{
164 spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
165
166 uint8_t txdata[3] = { (uint8_t)(0b01000000 | (hwAddr << 1)), addr, value };
167 spi_transaction_t ta;
168 ta.flags = 0;
169 ta.length = 24;
170 ta.rxlength = 0;
171 ta.rx_buffer = nullptr;
172 ta.tx_buffer = txdata;
173 spi_device_transmit(m_SPIDevHandle, &ta);
174
175 spi_device_release_bus(m_SPIDevHandle);
176}
177
178
179uint8_t MCP23S17::readReg(uint8_t addr, uint8_t hwAddr)
180{
181 spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
182
183 uint8_t txdata[3] = { (uint8_t)(0b01000001 | (hwAddr << 1)), addr };
184 uint8_t rxdata[3] = { 0 };
185 spi_transaction_t ta;
186 ta.flags = 0;
187 ta.length = 24;
188 ta.rxlength = 24;
189 ta.rx_buffer = rxdata;
190 ta.tx_buffer = txdata;
191 uint8_t r = 0;
192 if (spi_device_transmit(m_SPIDevHandle, &ta) == ESP_OK)
193 r = rxdata[2];
194
195 spi_device_release_bus(m_SPIDevHandle);
196
197 return r;
198}
199
200
201void MCP23S17::writeReg16(uint8_t addr, uint16_t value, uint8_t hwAddr)
202{
203 spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
204
205 uint8_t txdata[4] = { (uint8_t)(0b01000000 | (hwAddr << 1)), addr, (uint8_t)(value & 0xff), (uint8_t)(value >> 8) };
206 spi_transaction_t ta;
207 ta.flags = 0;
208 ta.length = 32;
209 ta.rxlength = 0;
210 ta.rx_buffer = nullptr;
211 ta.tx_buffer = txdata;
212 spi_device_transmit(m_SPIDevHandle, &ta);
213
214 spi_device_release_bus(m_SPIDevHandle);
215}
216
217
218uint16_t MCP23S17::readReg16(uint8_t addr, uint8_t hwAddr)
219{
220 spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
221
222 uint8_t txdata[4] = { (uint8_t)(0b01000001 | (hwAddr << 1)), addr };
223 uint8_t rxdata[4] = { 0 };
224 spi_transaction_t ta;
225 ta.flags = 0;
226 ta.length = 32;
227 ta.rxlength = 32;
228 ta.rx_buffer = rxdata;
229 ta.tx_buffer = txdata;
230 uint16_t r = 0;
231 if (spi_device_transmit(m_SPIDevHandle, &ta) == ESP_OK)
232 r = rxdata[2] | (rxdata[3] << 8);
233
234 spi_device_release_bus(m_SPIDevHandle);
235
236 return r;
237}
238
239
240void MCP23S17::enableINTMirroring(bool value, uint8_t hwAddr)
241{
242 writeReg(MCP_IOCON, value ? m_IOCON[hwAddr] | MCP_IOCON_MIRROR : m_IOCON[hwAddr] & ~MCP_IOCON_MIRROR, hwAddr);
243}
244
245
246void MCP23S17::enableINTOpenDrain(bool value, uint8_t hwAddr)
247{
248 writeReg(MCP_IOCON, value ? m_IOCON[hwAddr] | MCP_IOCON_ODR : m_IOCON[hwAddr] & ~MCP_IOCON_ODR, hwAddr);
249}
250
251
252void MCP23S17::setINTActiveHigh(bool value, uint8_t hwAddr)
253{
254 writeReg(MCP_IOCON, value ? m_IOCON[hwAddr] | MCP_IOCON_INTPOL : m_IOCON[hwAddr] & ~MCP_IOCON_INTPOL, hwAddr);
255}
256
257
258void MCP23S17::configureGPIO(int gpio, MCPDir dir, bool pullup, uint8_t hwAddr)
259{
260 uint8_t mask = MCP_GPIO2MASK(gpio);
261 // direction
262 uint8_t reg = MCP_GPIO2REG(MCP_IODIR, gpio);
263 if (dir == MCPDir::Input)
264 writeReg(reg, readReg(reg, hwAddr) | mask, hwAddr);
265 else
266 writeReg(reg, readReg(reg, hwAddr) & ~mask, hwAddr);
267 // pull-up
268 reg = MCP_GPIO2REG(MCP_GPPU, gpio);
269 writeReg(reg, (readReg(reg, hwAddr) & ~mask) | ((int)pullup * mask), hwAddr);
270}
271
272
273void MCP23S17::writeGPIO(int gpio, bool value, uint8_t hwAddr)
274{
275 uint8_t olat = readReg(MCP_GPIO2REG(MCP_OLAT, gpio), hwAddr);
276 uint8_t mask = MCP_GPIO2MASK(gpio);
277 uint8_t reg = MCP_GPIO2REG(MCP_OLAT, gpio);
278 writeReg(reg, value ? olat | mask : olat & ~mask, hwAddr);
279}
280
281
282bool MCP23S17::readGPIO(int gpio, uint8_t hwAddr)
283{
284 return readReg(MCP_GPIO2REG(MCP_GPIO, gpio), hwAddr) & MCP_GPIO2MASK(gpio);
285}
286
287
288void MCP23S17::enableInterrupt(int gpio, MCPIntTrigger trigger, bool defaultValue, uint8_t hwAddr)
289{
290 uint8_t mask = MCP_GPIO2MASK(gpio);
291 // set interrupt trigger
292 if (trigger == MCPIntTrigger::DefaultChange) {
293 // interrupt triggered when value is different than "defaultValue)
294 writeReg(MCP_GPIO2REG(MCP_INTCON, gpio), readReg(MCP_GPIO2REG(MCP_INTCON, gpio), hwAddr) | mask, hwAddr);
295 writeReg(MCP_GPIO2REG(MCP_DEFVAL, gpio), (readReg(MCP_GPIO2REG(MCP_DEFVAL, gpio), hwAddr) & ~mask) | ((int)defaultValue * mask), hwAddr);
296 } else {
297 // interrupt triggered when value is different than previous value
298 writeReg(MCP_GPIO2REG(MCP_INTCON, gpio), readReg(MCP_GPIO2REG(MCP_INTCON, gpio), hwAddr) & ~mask, hwAddr);
299 }
300 // enable interrupt
301 writeReg(MCP_GPIO2REG(MCP_GPINTEN, gpio), readReg(MCP_GPIO2REG(MCP_GPINTEN, gpio), hwAddr) | mask, hwAddr);
302}
303
304
305void MCP23S17::disableInterrupt(int gpio, uint8_t hwAddr)
306{
307 uint8_t reg = MCP_GPIO2REG(MCP_GPINTEN, gpio);
308 writeReg(reg, readReg(reg, hwAddr) & ~MCP_GPIO2MASK(gpio), hwAddr);
309}
310
311
312void MCP23S17::writePort(int port, void const * buffer, size_t length, uint8_t hwAddr)
313{
314 // - disable sequential mode
315 // - select bank 1 (to avoid switching between A and B registers)
316 writeReg(MCP_IOCON, m_IOCON[hwAddr] | MCP_IOCON_SEQOP | MCP_IOCON_BANK);
317
318 spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
319
320 spi_transaction_ext_t ta = { };
321 ta.command_bits = 8;
322 ta.address_bits = 8;
323 ta.base.cmd = 0b01000000 | (hwAddr << 1); // write
324 ta.base.addr = MCP_BNK1_OLAT + port * 0x10;
325 ta.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;
326 ta.base.length = 16 + 8 * length;
327 ta.base.rxlength = 0;
328 ta.base.rx_buffer = nullptr;
329 ta.base.tx_buffer = buffer;
330 spi_device_polling_transmit(m_SPIDevHandle, (spi_transaction_t*) &ta);
331
332 spi_device_release_bus(m_SPIDevHandle);
333
334 // restore IOCON
335 writeReg(MCP_BNK1_IOCON, m_IOCON[hwAddr]);
336}
337
338
339void MCP23S17::readPort(int port, void * buffer, size_t length, uint8_t hwAddr)
340{
341 // - disable sequential mode
342 // - select bank 1 (to avoid switching between A and B registers)
343 writeReg(MCP_IOCON, m_IOCON[hwAddr] | MCP_IOCON_SEQOP | MCP_IOCON_BANK);
344
345 spi_device_acquire_bus(m_SPIDevHandle, portMAX_DELAY);
346
347 spi_transaction_ext_t ta = { };
348 ta.command_bits = 8;
349 ta.address_bits = 8;
350 ta.base.cmd = 0b01000001 | (hwAddr << 1); // read
351 ta.base.addr = MCP_BNK1_GPIO + port * 0x10;
352 ta.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;
353 ta.base.length = 16 + 8 * length;
354 ta.base.rxlength = 8 * length;
355 ta.base.rx_buffer = buffer;
356 ta.base.tx_buffer = nullptr;
357 spi_device_polling_transmit(m_SPIDevHandle, (spi_transaction_t*) &ta);
358
359 spi_device_release_bus(m_SPIDevHandle);
360
361 // restore IOCON
362 writeReg(MCP_BNK1_IOCON, m_IOCON[hwAddr]);
363}
364
365
366
367
368
369
370
371} // fabgl namespace
372
This file contains the MCP23S17 driver class.
MCPDir
Represents GPIO directioon.
Definition: MCP23S17.h:126
MCPIntTrigger
Represents interrupt trigger mode.
Definition: MCP23S17.h:135