Search code examples
arduinomicrocontrollerspi

Arduino - managing two SPI devices at once


I'm trying to control two SPI devices at once using identical MISO, MOSI, and CLOCK pins but different SS pins.

One is the Wifly shield from SparkFun that uses a SPI-to-UART chip, the other is a MAX31855.

They work independently, but not together..

The SPI-to-UART code that I'm using is listed below. The only changes I've made is in the header file; I set select() and deselect() to public.

#include "SpiUart.h"

// See section 8.10 of the datasheet for definitions
// of bits in the Enhanced Features Register (EFR)
#define EFR_ENABLE_CTS 1 << 7
#define EFR_ENABLE_RTS 1 << 6
#define EFR_ENABLE_ENHANCED_FUNCTIONS 1 << 4

// See section 8.4 of the datasheet for definitions
// of bits in the Line Control Register (LCR)
#define LCR_ENABLE_DIVISOR_LATCH 1 << 7

// The original crystal frequency used on the board (~12 MHz) didn't
// give a good range of baud rates so around July 2010 the crystal
// was replaced with a better frequency (~14 MHz).
#ifndef USE_14_MHZ_CRYSTAL
#define USE_14_MHZ_CRYSTAL true // true (14 MHz) , false (12 MHz)
#endif

#if USE_14_MHZ_CRYSTAL
#define XTAL_FREQUENCY 14745600UL // On-board crystal (New mid-2010 Version)
#else
#define XTAL_FREQUENCY 12288000UL // On-board crystal (Original Version)
#endif

// See datasheet section 7.8 for configuring the
// "Programmable baud rate generator"
#define PRESCALER 1 // Default prescaler after reset
#define BAUD_RATE_DIVISOR(baud) ((XTAL_FREQUENCY/PRESCALER)/(baud*16UL))

// TODO: Handle configuration better
// SC16IS750 register values
struct SPI_UART_cfg {
    char DataFormat;
    char Flow;
};

struct SPI_UART_cfg SPI_Uart_config = {
    0x03,
    // We need to enable flow control or we overflow buffers and
    // lose data when used with the WiFly. Note that flow control
    // needs to be enabled on the WiFly for this to work but it's
    // possible to do that with flow control enabled here but not there.
    // TODO: Make this able to be configured externally?
    EFR_ENABLE_CTS | EFR_ENABLE_RTS | EFR_ENABLE_ENHANCED_FUNCTIONS
};


void SpiUartDevice::begin(unsigned long baudrate) {
    /*
     * Initialize SPI and UART communications
     *
     * Uses BAUD_RATE_DEFAULT as baudrate if none is given
     */

    SPI.begin();
    initUart(baudrate);
}

void SpiUartDevice::deselect() {
    /*
     * Deslects the SPI device
     */

    digitalWrite(SS, HIGH);
}


void SpiUartDevice::select() {
    /*
     * Selects the SPI device
     */

    digitalWrite(SS, LOW);
}


void SpiUartDevice::initUart(unsigned long baudrate) {
    /*
     * Initialise the UART.
     *
     * If initialisation fails this method does not return.
     */

    // Initialise and test SC16IS750
    configureUart(baudrate);

    if(!uartConnected()){
        while(1) {
          // Lock up if we fail to initialise SPI UART bridge.
        };
    }

  // The SPI UART bridge is now successfully initialised.
}


void SpiUartDevice::setBaudRate(unsigned long baudrate) {
    unsigned long divisor = BAUD_RATE_DIVISOR(baudrate);

    writeRegister(LCR, LCR_ENABLE_DIVISOR_LATCH); // "Program baudrate"
    writeRegister(DLL, lowByte(divisor));
    writeRegister(DLM, highByte(divisor));
}


void SpiUartDevice::configureUart(unsigned long baudrate) {
    /*
     * Configure the settings of the UART.
     */

    // TODO: Improve with use of constants and calculations.
    setBaudRate(baudrate);

    writeRegister(LCR, 0xBF); // Access EFR register
    writeRegister(EFR, SPI_Uart_config.Flow); // Enable enhanced registers
    writeRegister(LCR, SPI_Uart_config.DataFormat); // 8 data bit, 1 stop bit, no parity
    writeRegister(FCR, 0x06); // Reset TXFIFO, reset RXFIFO, non FIFO mode
    writeRegister(FCR, 0x01); // Enable FIFO mode
}


boolean SpiUartDevice::uartConnected() {
    /*
     * Check that UART is connected and operational.
     */

    // Perform read/write test to check if the UART is working
    const char TEST_CHARACTER = 'H';

    writeRegister(SPR, TEST_CHARACTER);

    return (readRegister(SPR) == TEST_CHARACTER);
}


void SpiUartDevice::writeRegister(byte registerAddress, byte data) {
    /*
     * Write <data> byte to the SC16IS750 register <registerAddress>
     */

    select();
    SPI.transfer(registerAddress);
    SPI.transfer(data);
    deselect();
}


byte SpiUartDevice::readRegister(byte registerAddress) {
    /*
     * Read byte from SC16IS750 register at <registerAddress>.
     */

    // Used in SPI read operations to flush slave's shift register
    const byte SPI_DUMMY_BYTE = 0xFF;

    char result;

    select();
    SPI.transfer(SPI_READ_MODE_FLAG | registerAddress);
    result = SPI.transfer(SPI_DUMMY_BYTE);
    deselect();
    return result;
}


int SpiUartDevice::available() {
    /*
     * Get the number of bytes (characters) available for reading.
     *
     * This is data that's already arrived and stored in the receive
     * buffer (which holds 64 bytes).
     */

    // This alternative just checks if there's data but doesn't
    // return how many characters are in the buffer:
    //    readRegister(LSR) & 0x01
    return readRegister(RXLVL);
}


int SpiUartDevice::read() {
    /*
     * Read byte from UART.
     *
     * Returns byte read or or -1 if no data available.
     *
     * Acts in the same manner as 'Serial.read()'.
     */

    if (!available()) {
        return -1;
    }

    return readRegister(RHR);
}


size_t SpiUartDevice::write(byte value) {
    /*
     * Write byte to UART.
     */

    while (readRegister(TXLVL) == 0) {
        // Wait for space in TX buffer
    };
    writeRegister(THR, value);
}


size_t SpiUartDevice::write(const char *str, size_t size) {
    /*
     * Write string to UART.
     */

    while (size--)
        write(*str++);

    while (readRegister(TXLVL) < 64) {
        // Wait for empty TX buffer (slow).
        // (But apparently still not slow enough to ensure delivery.)
    };
}

void SpiUartDevice::flush() {
    /*
     * Flush characters from SC16IS750 receive buffer.
     */

    // Note: This may not be the most appropriate flush approach.
    //       It might be better to just flush the UART's buffer
    //       rather than the buffer of the connected device
    //       which is essentially what this does.
    while(available() > 0) {
        read();
    }
}


void SpiUartDevice::ioSetDirection(unsigned char bits) {
    writeRegister(IODIR, bits);
}


void SpiUartDevice::ioSetState(unsigned char bits) {
    writeRegister(IOSTATE, bits);
}

I attempted to use it like this:

SpiSerial.deselect(); //Deselect Wi-Fi
delay(100);           //Wait, just for the heck of it.
currentTemp = thermocouple.readFarenheit(); //Read from max31855... readFarenheit selects and unselects its own SS pin.
SpiSerial.select();   //Reselect Wi-Fi

But it's still unable to function. What further things should I attempt in order to get this to function?


Solution

  • TWO issues:

    • You don't mention anything about the MAX31855 library or how it is used. It looks like you are using Adafruit_MAX31855. Did you set the second argument of the constructors instance to "SS" or the value of the pin for chip select tied to the MAX31855? Their respective chip selects can not share the same pin.

    • The Adafruit_MAX31855 bit bangs out an emulated SPI over GPIO. This is not the same as the Wi-Fi's true hard SPI. Once spi.begin(); from SpiUartDevice::begin() then the GPIO pins shared with the hard SPI are no longer available as GPIO. As a result, the Adafruit_MAX31855 bit bangs do nothing. There are several options for the latter problem.

      • Use a different MAX31855 library that does not bit bang it. But I don't quickly see one.
      • Fix the Adafruit_MAX31855 to hard SPI.
      • Use different pins for the MAX31855 other than the Hard SPI pins.
      • Use the SPI.end() function to shut down the SPI and return GPIO back to normal IO use, and then restart SPI.begin() after bit banging out the Adafruit_MAX31855 function calls.

    Such as the following:

    SpiSerial.deselect(); //Deselect Wi-Fi
    SPI.end();            //Restore GPIO mode of pins
    delay(100);           //Wait, just for the heck of it
    currentTemp = thermocouple.readFarenheit();
    SPI.begin();          //Restore SPI mode of pins
    SpiSerial.select();   //Reselect Wi-Fi