We will need to control the M series address lines (addr0-3) which are connected to the GPIO Header as follows:



All of these address lines are accessed via the GPIO Expander on the I2C bus. They're on port 0 using bits 0,1,2 & 3 that map to addr0-3 respectively. These will only need to be setup once in this tutorial within the "setup()" function. There's another tutorial that explains the GPIO Expander access titled ESP32 LED Blink if you're interested.

Below is an image of the EMBDZ19121 stacked on top of the ISOIOM17427 which is the configuration used for this tutorial. The stacking order is generally not important.


Be sure the address jumpers on the module you're using are cleared of solder so that they're open as shown.

The hardware and bus initialization code is shown below.



#include "Wire.h" #include "SPI.h" #define HDWR_SPI_CE0_PIN 15 #define HDWR_SPI_CE1_PIN 25 typedef union { struct { uint8_t b[4]; }; uint32_t val; } SPI_MSG_t; static uint8_t gpio0; // port 0 mirror variable of the GPIO expander static uint8_t gpio1; // port 1 mirror variable of the GPIO expander SPIClass *spi = NULL; // set the SPI clock to 1 (Mbps) static const int spiClk = 1000000; static SPI_MSG_t spiRxBfr; // SPI bus receive data buffer static SPI_MSG_t spiTxBfr; // SPI bus transmit data buffer // initialize the hardware GPIO Expander and Serial UART void setup() { Serial.begin(115200); // setup port mirror variables gpio0 = 0b00000000; gpio1 = 0b00000000; // setup the GPIO expander ports on EMBDZ19121 // port 0 // - bit 0 = input GPIO23 (header pin 16) // - bit 1 = input GPIO24 (header pin 18) // - bit 2 = input GPIO05 (header pin 29) // - bit 3 = input GPIO06 (header pin 31) // - bit 4 = input GPIO22 (header pin 15) // - bit 5 = input GPIO27 (header pin 13) // - bit 6 = input GPIO17 (header pin 11) // - bit 7 = input GPIO04 (header pin 7) // port 1 // - bit 0 = output (unused set low) // - bit 1 = output (unused set low) // - bit 2 = output (unused set low) // - bit 3 = output (unused set low) // - bit 4 = input GPIO26 (header pin 37) // - bit 5 = output GPIO25 (header pin 22) Status LED // - bit 6 = input GPIO16 (header pin 36) // - bit 7 = input GPIO21 (header pin 38) // setup the I2C bus Wire.begin(32, 33, 100000); // initialize GPIO expander port 0 direction register Wire.beginTransmission(0x20); Wire.write(0x06); // internal GPIO expander port 0 direction register address // set the M series address bits (0-3) to outputs Wire.write(0xf0); Wire.endTransmission(); // initialize GPIO expander port 1 direction register Wire.beginTransmission(0x20); Wire.write(0x07); // internal GPIO expander port 1 direction register address Wire.write(0xd0); Wire.endTransmission(); // initialize GPIO expander port 0 output values // set the M series module address bits value to all 1's // the address logic is inverted so 0b1111 = address 0 gpio0 = gpio0 | 0b00001111; Wire.beginTransmission(0x20); Wire.write(0x02); // internal GPIO expander port 0 output register address Wire.write(gpio0); Wire.endTransmission(); // initialize GPIO expander port 1 output values Wire.beginTransmission(0x20); Wire.write(0x03); // internal GPIO expander port 1 output register address Wire.write(gpio1); Wire.endTransmission(); Serial.println("GPIO Expander setup complete..."); // setup the system SPI bus // // setup pin of the ESP32 as the CE0 select line - initialize // the pin high to put CE0 into an inactive state digitalWrite(HDWR_SPI_CE0_PIN, HIGH); pinMode(HDWR_SPI_CE0_PIN, OUTPUT); // setup pin of the ESP32 as the CE1 select line - initialize // the pin high to put CE1 into an inactive state digitalWrite(HDWR_SPI_CE1_PIN, HIGH); pinMode(HDWR_SPI_CE1_PIN, OUTPUT); // instantiate the spi object and set the appropriate pins // on the ESP32 for the EMBDZ19121 // ESP32 GPIO14 - CLK // ESP32 GPIO12 - MISO // ESP32 GPIO13 - MOSI // ESP32 GPIO15 - CE0 spi = new SPIClass(HSPI); spi->begin(14,12,13,15); Serial.println("SPI bus setup complete..."); }

The initialization code includes the I2C bus GPIO Expander as in the tutorial ESP32 LED Blink. Since that is fully explained in that tutorial, we'll simply go through the additions for the SPI bus here.

The additions start with the inclusion of the "SPI.h" header file which provides the use of the SPI bus library functions.

Then we define a couple of pin assignments for the SPI slave select lines CE0 & CE1 (Chip Enable 0 & 1). We've added a typedefed union to define how we want the SPI buffers to be allocated and referenced. By using the union declaration we can access the individual bytes of the buffer or access the 32 bit word. Next is a pointer to the SPI Class which will be used to access the SPI library functionality. This is set to NULL here, but will be set to point to an object later. Next a constant is declared and initialized to set the SPI clock rate. Finally the SPI buffers are declared.

The setup of the GPIO Expander is the same as the ESP32 LED Blink tutorial so we won't go into detail here. The SPI bus setup consists of setting the select pins (CE0 & CE1) as outputs and initializing them high (inactive) then instantiating the "spi" object and setting the pointer to it. The call to spi.begin(14,12,13,15) sets up the pin assignments and gets the SPI bus ready to use.

In the next section we turn the status LED on and off of the EMBDZ19121 and the status LED of the attached "M" series module.



// loop forever toggling the Status LED on/off and setting the LED on // the a la mods "M" series smart module void loop() { // turn the EMBDZ19121 Status LED on // setup the gpio1 mirror register to set bit 5 high without // changing the other bits in the gpio1 mirror register gpio1 = gpio1 | 0b00100000; // write the port value to the GPIO Expander Wire.beginTransmission(0x20); Wire.write(0x03); // internal GPIO expander port 1 output register address Wire.write(gpio1); Wire.endTransmission(); Serial.println("Set Status LED on..."); // turn the "M" series module LED on // // The GPIO header module address lines were setup in the // initialization of the GPIO Expander port 0 (see setup above) // // set up the SPI command to send to the "M" series module spiTxBfr.b[3] = 0x02; // register write command spiTxBfr.b[2] = 0x90; // internal regsiter to write to spiTxBfr.b[1] = 0x80; // set the LED control bit of register 0x90 spiTxBfr.b[0] = 0x05; // set the RGB led bits to turn on color // bit 0 = RED // bit 1 = GRN // bit 2 = BLU // set the SPI bus mode and clock spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0)); // set the CE0 select line active digitalWrite(HDWR_SPI_CE0_PIN, LOW); // transfer the SPI a la mods command (4 bytes) spiRxBfr.b[3] = spi->transfer(spiTxBfr.b[3]); spiRxBfr.b[2] = spi->transfer(spiTxBfr.b[2]); spiRxBfr.b[1] = spi->transfer(spiTxBfr.b[1]); spiRxBfr.b[0] = spi->transfer(spiTxBfr.b[0]); // set the CE0 select line inactive digitalWrite(HDWR_SPI_CE0_PIN, HIGH); spi->endTransaction(); Serial.println("Set Module LED on..."); delay(500); // wait for 500 mSec // turn the EMBDZ19121 Status LED off // setup the gpio1 mirror register to set bit 5 low without // changing the other bits in the gpio1 mirror register gpio1 = gpio1 & 0b11011111; // write the port value to the GPIO Expander Wire.beginTransmission(0x20); Wire.write(0x03); // internal GPIO expander port 1 output register address Wire.write(gpio1); Wire.endTransmission(); Serial.println("Set Status LED off..."); // turn the "M" series module LED off // // The GPIO header module address lines were setup in the // initialization of the GPIO Expander port 0 (see setup above) // // set up the SPI command to send to the "M" series module spiTxBfr.b[3] = 0x02; // register write command spiTxBfr.b[2] = 0x90; // internal regsiter to write to spiTxBfr.b[1] = 0x80; // set the LED control bit of register 0x90 spiTxBfr.b[0] = 0x00; // set the RGB led bits to turn off // bit 0 = RED // bit 1 = GRN // bit 2 = BLU // set the SPI bus mode and clock spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0)); // set the CE0 select line active digitalWrite(HDWR_SPI_CE0_PIN, LOW); // transfer the SPI a la mods command (4 bytes) spiRxBfr.b[3] = spi->transfer(spiTxBfr.b[3]); spiRxBfr.b[2] = spi->transfer(spiTxBfr.b[2]); spiRxBfr.b[1] = spi->transfer(spiTxBfr.b[1]); spiRxBfr.b[0] = spi->transfer(spiTxBfr.b[0]); // set the CE0 select line inactive digitalWrite(HDWR_SPI_CE0_PIN, HIGH); spi->endTransaction(); Serial.println("Set Module LED off..."); delay(500); // wait for 500 mSec }

The SPI bus is a synchronous bi-directional serial bus that transmits data in both direction simultaneously. Therefore, when we transmit on the SPI bus we also receive the same number of bytes. In this case the transmit or receive process is generally referred to as a "transfer".

Before we transfer data we need to setup the transmit buffer. Since the a la mods commands are always 4 bytes we load 4 bytes into the transmit buffer. The first byte is the "command" byte according to the a la mods SPI protocol. The second byte is the internal virtual register address we intend to write to and the last two bytes is the data we want to store in that internal register.

The CE0 select line is set active (low) and each byte starting with the MSByte is transferred. As we transfer each byte we also recieve each byte into a receive register. Because we're not interested in the receive data in this case we do nothing with it.

Once all four bytes are transferred, the CE0 line is pulled high (inactive) again and the end of the SPI bus transaction is signaled.

© à la mods. All rights reserved