Tags

,

As I got into building the Base System, the SPI bus was used by many of the components.  I had a conceptual view of how the SPI bus worked, after all, it is well described in wikipedia.  

However, I didn’t have a working knowledge.  This concerned me because I was running into “traffic jams”.  In my case, once I initialized the cc3000, the RFM12B could no longer communicate on the SPI bus.

It became frustrating and concerning to not know if I purchased a component that needed to share the SPI bus if indeed it was a good citizen.  This was turning out to mean more than being able to set the CS/SS pin.

I decided to take a step back and try to figure out why there is this failure to share the SPI bus.  It is an OPPORTUNITY TO LEARN!  Since learning is in many ways what gets me up too early each day, I was eager to get to know the details of this all important data transfer bus. 

Peeking into SPI Bus Traffic with a Logic Analyzer

In order to gain a deeper understanding of SPI bus communications, the first thing I did was order the Saleae Logic Analyzer from Adafruit.  I had no clue how to use this…and it wasn’t until I talked to Mark – the incredibly helpful support person at Saleae – who patiently stepped me through using the logic analyzer – as well as watched their “how to” video did it all make sense.  I’ve done phone support at both Microsoft and Sabi.  It is a very hard job.  Especially helping over the phone.  I was very impressed with Mark’s patience in what I thought was my hideously “duh” questions.    A lot of that loyalty comes from how the product is supported.

Steps to debug:

  • wire RFM12B and cc3000 to the Arduino
  • add logic analyzer wires to the four SPI channels.  
  • start the logic analyzer software and load the SPI profile (for both the RFM12B and cc3000).
  • capture and analyze the SPI channels when the config information is displayed

Setting up the Logic Analyzer

I’ll be using all eight lines of the logic analyzer.  Four for the cc3000’s SPI traffic and four for the RFM12B’s traffic:

ArduinoWithLogicAnalyzer

yet another snarly mess of wires. 

Basic SPI Traffic

Nick Gammon has an excellent post on interpreting the squiggles of the Logic Analyzer.  Reading this post gave me a great start at how the SPI lines are used and interpreted.  How terrific to find this resource and that Nick shared his knowledge in such a clear/understandable way.  THANK YOU NICK!!!!

As a sanity check, I started by analyzing the traffic generated by the RFM12B and then the cc3000.

Simple RFM12B SPI Traffic

To get a feel for the SPI bus traffic, I captured traffic that occurs when the function rf12_config() is called.  

The sketch (note: I am using pin 9 for the CS/SS pin):

#include <RF12.h>
void setup()
{
rf12_set_cs(9);
rf12_config(true);
}
void loop()
{
}

This image shows the start of the traffic: 

LogicStartrf12_config

 

Notice the traffic occurs on the lines connected to the RFM12B.  The Arduino is sending bytes to the RFM12B (MOSI). The RFM12B’s CS pin is taken low to enable it.  The cc3000’s CS remains high.  Once the two bytes are read by the RFM12B, the RFM12B takes the CS high to note the end of sending/receiving data.

One “gotcha” using this Logic Analyzer:  On the plus side it supports the Mac.  On the more challenging UI side is the UI to find the MOSI and MISO transfers and then zoom into them.  To find the MISO and MOSI transfers, I find I have to zoom out to a timeline that shows increments in seconds.  To read the hex values, I then need to zoom into the MOSI or MISO line.  Zooming in is best done by clicking on the line until the timeline expands to show the hex values (microsecond increments).  I can also go to new readings with the N or P (for Next and Previous) keys.  However, I lose context of where I am.  While I have become very used to a trackpad I found I must use a mouse to navigate.

The Logic Analyzer provides a way to export the readings into a CSV file.  This made it far easier to sanity check reading SPI traffic.

Looking at the code in RF12.cpp,rf12_config() calls rf12_initialize().  The code that transfers bytes over the SPI from the Arduino to the RFM12B is listed on the left side of the table below.  Note the bytes on the right side correspond to the code.  For example, RF_SLEEP_MODE is #define’d earlier in RF12.cpp to be  0x8205 and  RF_TXREG_WRITE is #define’d to be 0xB800

   rf12_xfer(0x0000); // intitial SPI transfer added to avoid power-up problem

   rf12_xfer(RF_SLEEP_MODE); // DC (disable clk pin), enable lbd

   

   // wait until RFM12B is out of power-up reset, this takes several *seconds*

   rf12_xfer(RF_TXREG_WRITE); // in case we’re still in OOK mode

   while (digitalRead(RFM_IRQ) == 0)

       rf12_xfer(0x0000);

       

   rf12_xfer(0x80C7 | (band << 4)); // EL (ena TX), EF (ena RX FIFO), 12.0pF

   rf12_xfer(0xA640); // 868MHz

   rf12_xfer(0xC606); // approx 49.2 Kbps, i.e. 10000/29/(1+6) Kbps

   rf12_xfer(0x94A2); // VDI,FAST,134kHz,0dBm,-91dBm

   rf12_xfer(0xC2AC); // AL,!ml,DIG,DQD4

   if (group != 0) {

       rf12_xfer(0xCA83); // FIFO8,2-SYNC,!ff,DR

       rf12_xfer(0xCE00 | group); // SYNC=2DXX;

   } else {

       rf12_xfer(0xCA8B); // FIFO8,1-SYNC,!ff,DR

       rf12_xfer(0xCE2D); // SYNC=2D;

   }

   rf12_xfer(0xC483); // @PWR,NO RSTRIC,!st,!fi,OE,EN

   rf12_xfer(0x9850); // !mp,90kHz,MAX OUT

   rf12_xfer(0xCC77); // OB1,OB0, LPX,!ddy,DDIT,BW0

   rf12_xfer(0xE000); // NOT USE

   rf12_xfer(0xC800); // NOT USE

   rf12_xfer(0xC049); // 1.66MHz,3.1V

*

A tool that can help with understanding RFM12B Commands is this  RFM12B Command Calculator.

A Taste of cc3000 SPI Traffic

In my last go around attempting to have the RFM12B and cc3000 boards share the SPI bus, the traffic jam occurred after the call to cc3000.begin().  After that, the RFM12B was not able to transfer data on the SPI bus.  So the taste of SPI traffic I’m interested happens in cc3000.begin()…what state is the SPI bus left in that shoves the RFM12B out of sending/receiving over the MISO and MOSI lines?

Here is the sketch I am using:

#include <Adafruit_CC3000.h>
#include <ccspi.h>
#include <SPI.h>

#define ADAFRUIT_CC3000_IRQ 3 // MUST be an interrupt pin!
// These can be any two pins
#define ADAFRUIT_CC3000_VBAT 5
#define ADAFRUIT_CC3000_CS 8
// Use hardware SPI for the remaining pins
// On an UNO, SCK = 13, MISO = 12, and MOSI = 11
Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT,
SPI_CLOCK_DIV2); // you can
void setup()
{
Serial.begin(57600);
if (cc3000.begin())Serial.println(“Initialized the cc3000″);
else Serial.println(“Did NOT initialize the cc3000″);
}
void loop()
{
}

I ran this sketch while capturing its SPI bus traffic through the Logic Analyzer.

First, a zoom in on a few bits of initial cc30000.  

cc3000SPITraffic 

I note two differences in how the cc3000 transfers bits:

  • Note the clock line for the cc3000.  Unlike the RFM12B – where data is sampled when the clock is going low to high on the leading edge, data is sampled when the clock is going from high to low on the trailing edge. Nick Gammon’s post does a great job explaining the SPI “data modes”.  I also thought a post on Sparkfun did a nice job :

The slave will read the data on either the rising edge or the falling edge of the clock pulse. Additionally, the clock can be considered “idle” when it is high or low. In the Arduino SPI library, both of these options are controlled by the setDataMode() function.

  • Sampling occurs at a faster rate than it does for the RFM12B. Back to the Sparkfun post for an explantation on sample rates:

SPI can operate at extremely high speeds (millions of bytes per second), which may be too fast for some devices. To accommodate such devices, you can adjust the data rate. In the Arduino SPI library, the speed is set by the setClockDivider() function, which divides the master clock (16MHz on most Arduinos) down to a frequency between 8MHz (/2) and 125kHz (/128).

When the RFM12B Stops Talking

if I run a sketch that initializes the cc3000 using the cc3000.begin() function and then run rf12_config(), something has changed with the MISO line.  Contrast the MISO line readings below with the readings I showed earlier:

Time [s] Packet ID MOSI MISO
4.39353675 0 0x00 0xFF
4.39354125 0 0x00 0xFF
4.393549 1 0x82 0xFF
4.3935535 1 0x05 0xFF
4.39356125 2 0xB8 0xFF
4.39356575 2 0x00 0xFF

All MISO readings are 0xFF.  This was noted by Martyn in a thread on the JeeLabs forum where I asked for help on what was going on:

It [the MISO line] is stuck high after the cc3000.begin(). Since the SPI bus is inverted logic, that maps to the FF (falsely) decoded. 

Once cc3000.begin() has been executed, the RFM12B is unable to communicate over the MISO.  As Martyn noted:

  • The breakout board level converter sees the 3V high as an input and drives a 5V high hard independent of CC3000 selected
  • The poor RFM12B working at 3V through a resistor divider doesn’t stand a chance to pull this signal down.

An answer on the Adafruit forum gave me pretty much the equivalent information.   

the breakout version of the cc3000 needs something to buffer MISO as it is not tristated

http://learn.adafruit.com/adafruit-cc30 … 

you can look at the cc3000+SD exam.ple sketches for how to share the SPI lines, but no, the CC3000 does not work great with other SPI devices without a lot of care & debugging. this kind of stuff is not easy to fix, if you’re “new to electronics”, you may find this project challenging and we don’t offer coding or library modification support and assistance.our suggestion is to find an RF module that does not require sharing those pins.

At the time I read the replay on the Adafruit forum, I was more or less flailing like a fish on the deck of a boat in my understanding of what was being said.  My  lack of understanding is what compelled me to at least gain a better understanding of the SPI bus and perhaps the challenge of fixing this challenge.  Alsom I got a little thrown with some of the reply:

  • what does buffer MISO mean?  I get that the cc3000 leaves the MISO stuck high (see Martyn’s comment below).  Perhaps it just means bytes sent by the cc3000 over the MISO line?
  • the suggestion to look at the example sketches.  This made sense if it was a conflict with the CS pin.  However, I’m using different CS pins.  The challenge is in the state of the MISO after cc3000.begin() is called.  Of more interest is the schematic of the cc3000 Shield, which uses the 74AHC125 to tri-state the MISO pin.

 Oleg Mazurov sums up the issue in his post on the SPI bus:

Only one device, the one whose SS line is asserted low, is participating in the transfer by driving its MISO line – all other devices are expected to have their MISO line in a third state.

Can the cc3000 and RFM12B Co-Exist in a Circuit?

A Software Fix

Given my background is in software, the easiest solution I can think of was a software solution.  

1) initialize the cc3000 with cc3000.begin();

2) call wlan_stop()

(see wlan.cpp: Stop WLAN device by putting it into reset state.)

3) when switching RF traffic, wrap the cc3000 calls with wlan_start(0) and wlan_stop()

Here is some snippet of a sketch that does this:

//initialize the cc3000 then stop it to allow RFM12B to use the MISO line

if (cc3000.begin())Serial.println(“Initialized the cc3000″);
else Serial.println(“Did NOT initialize the cc3000″);
wlan_stop();
Serial.println(“cc3000 stopped”);

//nitialize the rrm23b

rf12_set_cs(9);
rf12_config();

//do wiFi traffic

Serial.println(“Start cc3000″);
wlan_start(0);
if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY)) {
Serial.println(F(“Failed!”));
while(1);
}
Serial.println(F(“Connected!”));
/* Wait for DHCP to complete */
Serial.println(F(“Request DHCP”));
while (!cc3000.checkDHCP())
{
delay(100); // ToDo: Insert a DHCP timeout!
}

/* Try looking up http://www.adafruit.com */
// uint32_t ip = 0;
Serial.print(F(“www.adafruit.com -> “));
while (ip == 0) {
if (! cc3000.getHostByName(“www.adafruit.com”, &ip)) {
Serial.println(F(“Couldn’t resolve!”));
}
delay(500);
}
cc3000.printIPdotsRev(ip);
//
// /* Do a quick ping test on adafruit.com */
Serial.print(F(“\n\rPinging “));
cc3000.printIPdotsRev(ip);
Serial.print(“…”);
replies = cc3000.ping(ip, 5);
Serial.print(replies);
Serial.println(F(” replies”));
if (replies)
     Serial.println(F(“Ping successful!”));
Serial.println(“stopping cc3000″);
wlan_stop();
Serial.println(“cc3000 stopped”);

//send traffic back and forth between RFM12B nodes and the Base System…

A Hardware Fix

I have stumbled into the 5V – 3.3V clash of the Arduino and breakout boards.  As noted in this SparkFun learning post on voltage dividers (see the section on Level Shifters):

Many of those sensors operate at a relatively low voltage, in order to conserve power. Unfortunately, it’s not uncommon that those low-voltage sensors are ultimately interfacing with a microcontroller operating at a higher system voltage. This leads to a problem of level shifting, which has a number of solutions including voltage dividing.

Recall what Martyn said:

  • The breakout board level converter sees the 3V high as an input and drives a 5V high hard independent of CC3000 selected
  • The poor RFM12B working at 3V through a resistor divider doesn’t stand a chance to pull this signal down.

 While I haven’t tried it, I read this to mean I would need to shift the 5v high to a 3.3V so that the RFM12B could pull the MISO line down.  This would be done by a level shifter – which as the cc3000 breakout board and shield schematics show are needed to shift between the 5v of the Arduino’s i/o to the 3.3v of the chip’s.

In my quest to learn, I will try this.  And YIPPEE!! I will be aided by the BitScope 10 oscilloscope I anxiously await to receive in the mail!  Too bad the Logic Analyzer doesn’t support a scope mode.  If I make progress with using a level shifter to solve the cc3000 sharing the MISO line with the RFM12B I’ll let you know through a new post.

Finally

I think I found a workaround.  In the process, I learned a lot about using a logic analyzer and SPI traffic.  As usual, folks on forums were kind in sharing their knowledge – giving insightful and actionable advice.  It is a great feeling to have a better understanding of why a circuit is not working.  I found learning how the logic analyzer can debug in serial communications to be valuable and will certainly come in handy as I continue my quest to build a hydroponics sensor network.  I’m a long way off from a never ending supply of vegetables to feed my family, but I feel one step closer in my ability to build the system.

About these ads