Main Menu

search

You are here

SPI software

[last updated: 2024-08-18]
SPI home page
-----

  • There is a lot of stuff on the web about programming for SPI.
    As always, you must be careful about relying on things that may be for different versions of software or different boards than what you're using.
    I did lots of research, and found lots of stuff that was contradictory, but below is the code that worked for me on my rev. 3 Nano's.

  • There are several steps you must do to set up and initiate the SPI communication:

  • Once the basics are done, here are things to understand about the way the communication works:
    • When the slave's Chip Select is activated by going low, and a data byte is received by the slave from the master,,
      the received data byte is put into the slave's SPDR register.
      Before that, however, whatever data byte is already in the register is fetched and sent back to the master.
      I know that doesn't seem to make complete sense, and I confess to not understanding the timing of it all,
      but that's the way it works:
        the incoming byte is put into the SPDR register,
        and whatever was in the register before that is sent back to the master as the response.
    • When the slave's Chip Select is activated by going low, and a data byte is received,
      then the interrupt that you set up is triggered, and the interrupt handler is executed.
      You will want to put code into the interrupt handler to do whatever you desire with the data byte received,
      as well as perhaps loading something desired into the SPDR register to be sent back to the master in response to the next transfer that is received from the master.

    ===============================================================================

  • Software - programming Arduino:
    • Standard Arduino library is SPI.h
      #include <SPI.h> // in globals

    • Begin:
      In Setup:
        pinMode(CS_pin, OUTPUT);
        digitalWrite(CS_pin, HIGH); // start in inactive (high) state

      Start the SPI library with this line in setup:

        SPI.begin();
        "Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. ... but takes no parameters? [per arduino ref]"
        "SPI.begin() initializes all the lines and the CPU to use SPI.
        ??? "SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN);" ???
        or SPI1.begin(); for multiple instances?

    • Prepare to transfer data:
      Set the SPI parameters for your device. This is normally done in loop section, just prior to initiating a data transfer.
      This is done with the SPISettings command, followed by its 3 required parameters:
        speedMaximum: The maximum communication speed. Usu. your chips clock speed in Hz. (or append " *MHZ " ?)
        dataOrder: MSBFIRST or LSBFIRST
        dataMode: SPI_MODE0, SPI_MODE1, SPI_MODE2, or SPI_MODE3
          The 4 "data modes" specify whether data is shifted in and out on the rising or falling edge of the clock signal, and whether the clock is idle when high or low ("clock polarity").
          Both dataMode and dataOrder are set according to requirements of your peripheral device.


          It may be that your peripheral device is hard-coded, with its own firmware, in which case you must use what it specifies.
          OTOH if your peripheral is another MCU, then you are free to choose which formats you want to use, as long, of course, as both your "master" MCU and your peripheral are programmed with the same parameters.

        SPISettings can be defined in two different ways:
      • as a standalone, which you might want to do if your parameter values are variables that might change.
        In this case you create a settings object to hold your variable parameters:
          SPISettings mySettings(speedMaximum, dataOrder, dataMode)

        You then pass that object to the beginTransaction command when you do the actual data transfer:

          SPI.beginTransaction(mySettings)
      • Or, if all your settings parameters are constants, then it's more efficient to include the settings directly into the beginTransaction command:
          SPI.beginTransaction(SPISettings(speedMaximum, dataOrder, dataMode))
          for example:
          SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0))

        ------------------------------

      • Data Transfer:
        • A given data transfer consists of 5 steps:
          activate chip select
          beginTransaction
          transfer
          deactivate chip select
          endTransaction

        • Activate chip select:
            digitalWrite(CS_pin, LOW); // Activate the CS line (CS is active LOW)

        • BeginTransaction:
          (Note that earlier like 2011 forum posts do not use beginTransaction. Not sure what to say about that...)
          Once your settings parameters are specified, use beginTransaction() to start using the SPI port (ie. do a data transfer). As in the example above:
            SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0))

          Do this with each data transfer. It sets the SPI parameters, and blocks other interrupts that might affect the SPI bus.

        • Transfer:
            SPI.transfer(byteToSend);
            or: byteRcvd = SPI.transfer(byteToSend);
              Note that a curious thing occurs if using this format:
              Since the data returned from the slave happens immediately (in the same clock cycle?) upon receipt of the incoming byte from the master,
              the slave cannot respond in any way to the content of the message from the master.
              If you (the master) want something responsive from the slave, you must first send a "request" message to the slave, telling it what you want,
              giving it time to load the SPDR register with the desired data,
              then send some otherwise-meaningless data to get the slave to output (send back) its register contents with your desired data.

        • deactivate CS:
            digitalWrite(CS_pin, LOW); // Activate the CS line (CS is active LOW)

          One online source did this clever thing for unknown reasons:

            pinMode(CS_Pin, INPUT); // Set CS_Pin to high impedance to allow pull-up to reset CS to inactive.
            digitalWrite(CS_Pin, HIGH); // Enable internal pull-up

        • endTransaction:
          SPI.endTransaction(); // releases the bus for other user interrupts

      -------------------------------------------------------------------------