julianschuler / MCP23S08

Arduino library for the MCP23S08 SPI I/O expander
MIT License
1 stars 3 forks source link

How to use a new spiCLASS in the MCP23S08 library??? #5

Open fdxrate opened 2 years ago

fdxrate commented 2 years ago

I would like to create a new spiClass for the ESP32's HSPI spi bus and use that in the MCP23S08 library. (The default spi bus VHSPI is used for touch display and SD (3 devices).)

fdxrate commented 2 years ago

@monoidk - I see you have included spiClass in your MCP23S08 constructor library fork. If I understand it correctly this is a great idea.

I have tried creating a new spiCLASS...

;
;
//uninitalised pointer to SPI object
SPIClass* vspi = nullptr;
SPIClass* hspi = nullptr;

void setup() {
//initialise two instances of the SPIClass attached to VSPI and HSPI respectively
vspi = new SPIClass(VSPI);
hspi = new SPIClass(HSPI);

hspi->begin();
;
;
}

... and I then replaced all references to 'SPI.' with 'hspi->' in the MCP23S08.cpp file. This produced an error on compile. (hspi, in MCP23S08.cpp, 'type' not recognised.)

Can you tell me how to make this work? Or show how to modify an example to use the HSPI bus. Thank you.

Geoff - gjgsmithvr@gmail.com

fdxrate commented 2 years ago

This issue was also posted recently on the Espressif Forum https://www.esp32.com/viewtopic.php?f=2&t=28425

monoidk commented 2 years ago

I'm not really sure how you intended to use the library, but if you use code from my fork:

1) It requires that the SPI device class is named SPIClass (as is in teensy and hopefully typical Arduino). If you need to use a different class, you would need to change SPIClass to whatever C++ class is used for a compatible SPI interface.

2) When creating the MCP23S08 instance, you would supply the desired SPI device in the constructor. On teensy the SPI driver instances exist with names SPI (SPI0), SPI1, SPI2 and one would not create new instances of SPIClass SPI drivers, only referencing existing ones from MCP23S08 constructor. e.g.

// single instances declared directly in main ino file
MCP23S08 mcp1(SPI, PIN_mcp1_nCS, MCP1_ADDR);
MCP23S08 mcp2(SPI2, PIN_mcp2_nCS, MCP2_ADDR);

void setup() {
   // init SPI - set pins, ..
   delayMicroseconds(1) // wait for MCP HW init
   // init MCP - `begin()`, set output states, mode, ..)
}
monoidk commented 2 years ago

To be clear, my code doesn't create a new spi class, it only adds an instance variable to store a reference to an existing SPI class in the MCP driver instance.

fdxrate commented 2 years ago

@monoidk thanks.

The following is an extract from the ESP32 API Reference >> Peripherals API manual which gives a basic summary of the SPI controllers available and their names.

API Reference >> Peripherals API >>

SPI Master Driver.
ESP32 integrates 4 SPI peripherals.
•   SPI0 and SPI1 are used internally to access the ESP32’s 
attached flash memory. Both controllers share the same 
SPI bus signals, and there is an arbiter to determine 
which can access the bus.
•   SPI2 and SPI3 are general purpose SPI controllers, 
sometimes referred to as HSPI and VSPI, respectively. 
They are open to users. SPI2 and SPI3 have 
independent bus signals with the same respective names. 
Each bus has three CS lines to drive up to same number of SPI slaves.

SPI Slave Driver.
ESP32 integrates two general purpose SPI controllers which can be used as 
slave nodes driven by an off-chip SPI master.
•   SPI2, sometimes referred to as HSPI
•   SPI3, sometimes referred to as VSPI
SPI2 and SPI3 have independent signal buses with the same respective names.

As SPI3 is the default SPI class when initialising SPI.begin(); I will try your code using SPI2 as you have suggested, and post here the result.

fdxrate commented 2 years ago

This is the example I tried with your library code which produced an error on compile.

#include <Arduino.h>
#include <MCP23S08.h>
#include <SPI.h>

MCP23S08 expander1(SPI2, 15, 0x20);     // SPIClass, CS_pin, '0x20' - hardware device address.
MCP23S08 expander2(SPI2, 15, 0x21);

void setup() {

  SPI2.begin();                          // (14, 12, 13, 15)

  expander1.begin();                     // begin communication with the I/O expander
  expander1.setPinModes(B11111111);      // set all pins to output
  expander1.setOutputStates(B11111111);  // turn all pins on.

  expander2.begin();                     // begin communication with the I/O expander
  expander2.setPinModes(B11111111);      // set all pins to output
  expander2.setOutputStates(B11111111);  // turn all pins on.
}

void loop() {
  uint8_t outputStates1 = expander1.getOutputStates();  // get output states of all pins
  expander1.setOutputStates(~(outputStates1));          // set output states to am inverse of the previous states

  delay(1500); 

  uint8_t outputStates2 = expander2.getOutputStates();  // get output states of all pins
  expander2.setOutputStates(~(outputStates2));          // set output states to am inverse of the previous states

  delay(1500);                                         // wait
}

...produced ERROR...

Using library MCP23S08-master in folder: C:\Users\gjgsm\OneDrive\ArduinoProjects\Arduino\Sketches\libraries\MCP23S08-master (legacy)

Using library SPI at version 1.0 in folder: C:\Users\gjgsm\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\libraries\SPI 

exit status 1

'SPI2' was not declared in this scope
monoidk commented 2 years ago

If the error is 'SPI2' was not declared in this scope, it doesn't look like an error with the MCP library. I would recommend you try compiling something using SPI2 without MCP, and plugging SPI2 into the MCP constructor once it is available.

Maybe you need to include some other file for SPI2, or perhaps initialize or even declare the bus instance?

fdxrate commented 2 years ago

I tried these suggestions, without success, found here - github.com/greiman/SdFat/issues...

[greiman](https://github.com/greiman) commented [on 28 Apr 2020](https://github.com/greiman/SdFat/issues/183#issuecomment-620625774)

I have not implemented SPI port selection for ESP32.
You could try the following.
At line 80 of SdFatConfig.h set USE_STANDARD_SPI_LIBRARY to 2 like this.

#define USE_STANDARD_SPI_LIBRARY 2 // 0

This at least causes the following to compile.

#include "SdFat.h"
SPIClass SD_SPI(HSPI);
SdFat sd(&SD_SPI);

void setup() {
  sd.begin();
}

void loop() {
}

Explanation for 'USE_STANDARD_SPI_LIBRARY'...

 * If the symbol USE_STANDARD_SPI_LIBRARY is zero, an optimized custom SPI
 * driver is used if it exists.  If the symbol USE_STANDARD_SPI_LIBRARY is
 * one, the standard Arduino SPI.h library is used with SPI. If the symbol
 * USE_STANDARD_SPI_LIBRARY is two, the SPI port can be selected with the
 * constructors SdFat(SPIClass* spiPort) and SdFatEX(SPIClass* spiPort).
fdxrate commented 2 years ago

I had a quick look through the SPI library in the platform = espressif32 framework = arduino and found no reference to SPI controllers SPI2 or SPI3.

The normal SPIClass is assigned with different parameters by the user - SPI.begin(VSPI); (18, 19, 18, 5) - default SPI. Or just use SPI.begin(); SPI.begin(HSPI); (14, 191213, 15) SPI.begin(SCLK, MISO, MOSI, SS); (alternate pins)

A new SPIClass can be created and assigned pins in the same way and used concurrently with the default SPI.

<<< **EDIT**: The following may not be true. Perhaps it is the pin assignment that determines SPI_bus and performance. >>>
The SPI used by the arduino/esp32 platform I suspect are not the SPI2 and SPI3 mentioned in the Espressif API Reference and
are probably only accessed using ESP-IDK software development framework. Consequently they will have a max speed of
around 26Mhz and will possibly be unstable above that.
fdxrate commented 2 years ago

@monoidk thanks again for you input. Using your library fork code I have test code running successfully using two MCP23S08's and two SPI's. I don't need two SPI's to run two expanders, they just demonstrate that it works.

/**************************************************************************
 *
 * File: SimultanPinAccess.ino
 * Author: Julian Schuler (https://github.com/julianschuler)
 * License: MIT License, see LICENSE.txt
 * monoidk/MCP23S08 fork code.
 * MODIFIED by G Smith for two expanders.
 * 
 **************************************************************************/

#include <Arduino.h>
#include <MCP23S08.h>
#include <SPI.h>

SPIClass SPIexp(HSPI);

MCP23S08 expander1(SPIexp, 15, 0x20, 79000000);     // SPIClass, CS_pin, '0x20' - hardware device address.
MCP23S08 expander2(SPI, 15, 0x21, 79000000);

void setup() {

  SPI.begin();                            // VSPI bus - (18, 19, 18, 5) - (SCLK, MISO, MOSI, SS)
  SPIexp.begin();                       // HSPI bus - (14, 12, 13, 15) - (SCLK, MISO, MOSI, SS)

  expander1.begin();                     // begin communication with the I/O expander
  expander1.setPinModes(B11111111);      // set all pins to output
  expander1.setOutputStates(B11111111);  // turn all pins on.

  expander2.begin();                     // begin communication with the I/O expander
  expander2.setPinModes(B11111111);      // set all pins to output
  expander2.setOutputStates(B11111111);  // turn all pins on.
}

void loop() {
  uint8_t outputStates1 = expander1.getOutputStates();  // get output states of all pins
  expander1.setOutputStates(~(outputStates1));          // set output states to am inverse of the previous states

  delay(1500); 

  uint8_t outputStates2 = expander2.getOutputStates();  // get output states of all pins
  expander2.setOutputStates(~(outputStates2));          // set output states to am inverse of the previous states

  delay(1500);                                         // wait
}
fdxrate commented 2 years ago

I have successfully run this code at 79Mhz which seems to be about max speed with this setup.

This is consistent with the ESP32 API reference manual which states 80Mhz VHSPI and HSPI buses and 26Mhz for GPIO pins. Which means that if were to use with alternate pins... SPI.begin(SCLK, MISO, MOSI, SS); with alternate pins, then the max SPI bus speed would be 26Mhz.

fdxrate commented 2 years ago

@monoidk It would appear that neither of the libraries accept multiple device addresses and therefore it appears there is only one deviceOpcode, consequently, individual chips are not addressed and HAEN (Hardware Address ENables) are cannot be enabled and the libraries will only work with one chip. I have basic non-library code that works with multiple chips (max 4 chips) if you are interested in using it in your library fork. My email: gjgsmithvr@gmail.com

monoidk commented 2 years ago

@fdxrate I'm not currently working with the code, but generally, you would use multiple instances of the same class for your multiple expanders. The initializations would be slightly redundant, but it should all work after the chips are initialized.

Something like:

MCP23S08 expander1(SPI, 15, 0x20, 79000000);     // SPIClass, CS_pin, '0x20' - hardware device address.
MCP23S08 expander2(SPI, 15, 0x21, 79000000);     // SPIClass, CS_pin, '0x21' - hardware device address.
// Note: the address and speed may be inappropriate.

...
expander1.begin(); // initialize together, as the initialization code may access
expander2.begin(); // expanders on same SPI+CS before chip addressing is enabled.
                   // Alternatively, read datasheet and initialization code for specifics.

Note: I haven't checked whether HAEN is enabled.

fdxrate commented 2 years ago

@monoidk the chips will not initialise as expected. In the begin() code (.cpp file), the chips are not individually addressed with their deviceOpCode before writing to the registers so therefore data is not written and HAEN is not enabled. BTW, via the POR (Power-On-Reset) circuit, all registers are reset to defaults on power-up.

There are only 4 possible deviceOpcode(s), one for each of a maximum 4 chips: 0x40, 0x42, 0x44 and 0x46 that have to be transferred before addressing registers and transfering data (these deviceOpcodes have the 'write' bit enabled) .

Only one chip atm will read/write data as the two functions 'writeRegister' and 'readRegister' do include the deviceOpcode transmission. Also, any other chip regardless of their address pins setup, using the same SPI bus and CS, will be treated as if they are the same chip.

monoidk commented 2 years ago

Yes, you're correct that the current library version does not implement addressing. You may try the haen branch in my repository, but it's not really good there either.

One approach is to send configuration on all device opcodes as you say.

Another approach, for individual chips, would be to rely on the fact that there are at most 2 opcodes for a given chip: with and without HAEN enabled. To initialize an (almost) individual chip, one could first set the HEAN in IOCON on generic address and then use individual addressing to setup the rest. On POR, HAEN is disabled, but the code would work regardless. A minor issue is that it may set IOCON on other chips, so one would need to take care of compatibility.

I was thinking of MCP23S17 library where the second approach is implemented and the provided code would be appropriate.

fdxrate commented 2 years ago

@monoidk your 'haen' library is now fully working with basically one simple change to the constructor begin() code with the addition of pinMode for csPin. See below. I have tested the library successfully with two chips but the library will work with one chip and up to four chips without further modification using the addresses 0x20, 0x21, 0x22 and 0x23 in the constructors for each chip.

This is part of the MCP23S08.cpp code which shows the changes I made to the Monoidk (haen) library. Two points to note:

  1. Reset of all registers to defaults is not necessary because POR does that.
  2. pinMode for csPin is required here, at least for the ESP32, I suspect because spi is not yet initialised at this stage for the 'writeRegister' to work correctly. The addressing of individual chips will not work without it. (NOTE: The pinMode requirement might also be related to the to the fact that the ESP32 has its own version of the SPI library.)

void MCP23S08::begin() { pinMode(csPin, OUTPUT); writeRegister(MCP23S08_IOCON, 0x08); }

This is part of the Arduino code/sketch... `

include

include

include

SPIClass SPIexp(HSPI); // (14, 12, 13, 15)

MCP23S08 expander1(SPIexp, 15, 0x20, 25000000); // SPIClass, CS_pin, '0x20' - hardware device address. MCP23S08 expander2(SPIexp, 15, 0x21, 25000000); //MCP23S08 expander2(SPI, 15, 0x21, 79000000); // VSPI can also be used.

void setup() {

Serial.begin(115200); SPIexp.begin(); // (14, 12, 13, 15) `

fdxrate commented 2 years ago

@monoidk if you think this is ok and you update your library fork, I can give you an example to include.

monoidk commented 2 years ago

@fdxrate Does your code work if you don't initialize a MCP23S08 with address 0?

I have added code to my fork to optionally enable HAEN. I think it should work, though I haven't tested it. Please note that the constructor arguments changed.

fdxrate commented 2 years ago

@monoidk as per my previous posts here, enabling HAEN will not happen unless each chip is address individually (also detailed in the datasheet). Your added code to your fork to optionally enable HAEN does not work, see my previous post for the code that does work, and has been tested using two chips.

monoidk commented 2 years ago

@fdxrate Thank you for your comment. I'm sure your code works for you and you are free to keep using it. Note that I'm not actually using the code, and plan to use a different library.

I may have neglected to explain why I don't want set HAEN on all addresses - it is a matter of encapsulation, modeling, dividing responsibilities and control to individual instances, so that a single MCP instance would not affect unrelated instances, which a user may wish to configure differently (or at least would be non-obvious from the API, that one object would affect another). I did not find a reasonable way to prevent all interaction, but at least it doesn't affect chips with non-default address and HAEN already enabled.

That being said, I did read (parts) of the documentation and the algorithm to set HAEN is from a library for MCP23S17, so I do not understand why it should not work.

According to my reading of documentation: When chip is initialized on POR, HAEN is disabled. Meaning all chips respond to a single address. Sending a HAEN enable to this address will enable HAEN, and all further instructions need to address the chip with it's individual address.

The code may be incorrect (it was not tested), but I do not see why one would need to send HAEN enable to individual chip addresses.

julianschuler commented 1 year ago

Hi everyone, sorry for the late answer (I wrongly assumed Github would send me an email for new issues/PRs).

Were you able to resolve all issues in this context (i.e. can I close this one)? Feel free to open a PR if you have any code changes you would like me to include!