Pixy Camera with Teensy 3.5 and SPI

Status
Not open for further replies.

randomvibe

Well-known member
The Pixy camera is an object tracking device that communicates object information via UART, I2C, SPI, etc. I'm opting for SPI for speed and signal robustness and I have had good success with the ILI9341 display and an IMU. The Pixy SPI library was written for the Arduino, and peeking into it, I don't see how it will work with Teensy. Has anyone successfully linked the Pixy to a 32-bit Teensy with SPI?

9/17/2017 Update:
* Went ahead and developed SPI functions for Teensy 3.5 & Teensy 3.6 here: RandomVibe Code

9/13/2017 Update:
* Pixy board requires 5V, mainly for camera and peripherals.
* Pixy SPI pins are 3.3V, so no need for level shifting with Teensy 3.6.
* Pixy I2C SDA pin is 5V.
 
Last edited:
The Pixy SPI library was written for the Arduino, and peeking into it, I don't see how it will work with Teensy. Has anyone successfully linked the Pixy to a 32-bit Teensy with SPI?

More details please. Why?
Is EVL yours?
I have Pixy CMUcam5; only have used with Leonardo/Zumo as Pixy Pet by Adafruit.
I don’t see any reason why not to connect Teensy 3.5/3.6 to SPI all are 3.3 volts.
 
Last edited:
T...The Pixy SPI library was written for the Arduino, and peeking into it, I don't see how it will work with Teensy. Has anyone successfully linked the Pixy to a 32-bit Teensy with SPI?
Which library? Do you have a link?
 
All latest Pixy libraries by Charmed Labs are here: Wiki Releases

The Windows 10 Pixy library (v2.0.9) I downloaded is here: pixymon_windows-2.0.9.exe

The relevant header file for SPI with slave select is called "PixySPI_SS.h". Contents as follows:
Code:
//
// begin license header
//
// This file is part of Pixy CMUcam5 or "Pixy" for short
//
// All Pixy source code is provided under the terms of the
// GNU General Public License v2 (http://www.gnu.org/licenses/gpl-2.0.html).
// Those wishing to use Pixy source code, software and/or
// technologies under different licensing terms should contact us at
// cmucam@cs.cmu.edu. Such licensing terms are available for
// all portions of the Pixy codebase presented here.
//
// end license header
//
// This file is for defining the link class for SPI with Slave Select.  The 
// default communication for Arduino is through the ICSP connector, which uses
// SPI without a slave select.  The LinkSPI_SS allows you to use a slave select
// so you can share the SPI port with other devices, or use multiple Pixys. 
//
// Note, the PixySPI_SS class takes an optional argument, which is the pin 
// number of the slave select signal you wish to use.  The default pin is the 
// SS pin (used when no argument is used.)  So, for example, if you wished to 
// use pin 14 for slave select, declare like this:
//
// PixySPI_SS pixy(14);
//

#ifndef PIXYSPI_SS_H
#define PIXYSPI_SS_H

#include "TPixy.h"
#include "SPI.h"


#define PIXY_SYNC_BYTE              0x5a
#define PIXY_SYNC_BYTE_DATA         0x5b
#define PIXY_OUTBUF_SIZE            6

class LinkSPI_SS
{
  public:
    void init()
    {
      outLen = 0;
      SPI.begin();

      #ifdef __SAM3X8E__
      // DUE clock divider //
      SPI.setClockDivider(84);
      #else
      // Default clock divider //
      SPI.setClockDivider(SPI_CLOCK_DIV16);
      #endif
    }
    
    uint16_t getWord()
    {
      // ordering is different because Pixy is sending 16 bits through SPI 
      // instead of 2 bytes in a 16-bit word as with I2C
      uint16_t w;
      uint8_t c, cout = 0;
	  
	  // assert slave select
	  digitalWrite(ssPin, LOW);

      if (outLen)
      {
        w = SPI.transfer(PIXY_SYNC_BYTE_DATA);
        cout = outBuf[outIndex++];
        if (outIndex==outLen)
          outLen = 0; 
      }
      else
        w = SPI.transfer(0);

      w <<= 8;
      c = SPI.transfer(cout);
      w |= c;

	  // negate slave select
	  digitalWrite(ssPin, HIGH);
      return w;
    }
	
    uint8_t getByte() // this shouldn't be called normally
	// It should only be called if we get out of sync, but with slave select
	// we should stay in sync 
    {
	  uint8_t c;
 	  // assert slave select
	  digitalWrite(ssPin, LOW);
      c = SPI.transfer(0x00);
 	  // negate slave select
	  digitalWrite(ssPin, HIGH);
	  
	  return c;
   }
    
    int8_t send(uint8_t *data, uint8_t len)
    {
      if (len>PIXY_OUTBUF_SIZE || outLen!=0)
        return -1;
      memcpy(outBuf, data, len);
      outLen = len;
      outIndex = 0;
      return len;
    }

    void setArg(uint16_t arg)
    {
      if (arg==PIXY_DEFAULT_ARGVAL)
        ssPin = SS; // default slave select pin
	  else
	    ssPin = arg;
    }

  private:
    uint8_t outBuf[PIXY_OUTBUF_SIZE];
    uint8_t outLen;
    uint8_t outIndex;
	uint16_t ssPin;
};


typedef TPixy<LinkSPI_SS> PixySPI_SS;

#endif


I don't see how it can work for Teensy 3.5 or 3.6 without the Teensy SPI initialization commands shown below. There may be other missing components too. Just thought I'd check with the community first on a working SPI library for Pixy. IMHO, I think video object tracking is the next big thing in robotics. Pixy is a good way to learn about it through practice.

Code:
#define   MOSIX   11
#define   SCKX    13
#define   MISOX   12
#define   CSX     15   // slave select

pinMode(CSX, OUTPUT);
digitalWrite(CSX,HIGH);
 
SPI.setMOSI(MOSIX);
SPI.setMISO(MISOX);
SPI.setSCK(SCKX);
SPI.begin();
 
I took a quick look through this and most of this looks like standard SPI stuff.

You do need to do the pinMode and probably digitalWrite calls, like you mentioned. If I looked correctly you can set the CS pin in the library by doing something like;
Code:
pixy.setArg(CSX);
Assuming pixy is your instance of the class...

As for setMOSI, setMISO, setSCK - Pins 11, 12, 13 are the default pins so you don't need to make these calls. As for needing a call to SPI.begin(), There is one in the init function.
so again;
Code:
pixy.init();
Will take care of that.

If I were using this, I would set it up to use SPI transactions instead of the older SPI.setClockDivider(SPI_CLOCK_DIV16), which I think is then trying to use SPI at 1MHz, I would instead
use SPI.beginTransaction/endTransaction and put the calls into the getWord call around where the digitalWrite calls are... Probably also in getByte...
 
I went ahead a developed functions to read Pixy data with an SPI connection for Teensy 3.5 & 3.6. It's simple and robust code designed to use with other SPI devices with a single SPI connection. You select the CS port for the Pixy. The main function "pixy_parse_data" outputs an array of PIXY structures for multiple object detections per frame. It makes use of a checksum output provided by the Pixy to ensure the data stream is correct. I tested the functions and all works properly with Normal objects or Color-Coded objects. Full example program with my functions included below. It's best to set the Pixy object signatures with PixyMON. I hope this helps you.


Code:
/*
PIXY SPI Reader for Teensy 3.5 and 3.6
Written By: randomvibe
Date: 9/7/2017

Main Functions:
   pixy_spi_read_16bit  ~ Reads PIXY data via SPI connection using Teensy 3.5/3.6 SPI functions
   pixy_parse_data      ~ Parses SPI data stream and saves to array of PIXY struct

*/

#include  <SPI.h> 



// Teensy 3.5/3.6 SPI Pins
#define     MOSIX   11
#define     SCKX    13
#define     MISOX   12
SPISettings PIXYSPI(1000000, MSBFIRST, SPI_MODE0); 

// Pixy Parameters
#define  PIXY_SPI_CS             15        // SPI Chip Select
#define  PIXY_SPI_TRY            24
#define  PIXY_SYNC_BYTE_DATA     0x5b      // Sync with Pixy Device
#define  PIXY_START_WORD         0xaa55    // Start word in Pixy Data Block
#define  PIXY_START_WORD_CC      0xaa56    // Stard word in Pixy Data Block with Color Codes
#define  PIXY_MAX_BLOCKS         10        // Max No. of detectable blocks 


// PIXY data block struct array
typedef struct 
{
    uint16_t  obj = 0;   // Object type: Normal (0xaa55), Color Code (0xaa56)
    uint16_t  sig = 0;   // Signature Number
    uint16_t  x   = 0;   // x center of object
    uint16_t  y   = 0;   // y center of object
    uint16_t  wd  = 0;   // width of object
    uint16_t  ht  = 0;   // height of object
    int16_t   phi = 0;   // angle of object (applies to Color-Code objects only)
} PIXY;




uint16_t  pixy_spi_read_16bit(uint16_t target, SPISettings SETX, uint8_t CSX)
{
    uint8_t   a, b;
    uint16_t  c;

    // Read First Byte
    SPI.beginTransaction(SETX);      
        digitalWrite(CSX,LOW);    
        a = SPI.transfer(target);    
    SPI.endTransaction();

    // Read Second Byte
    SPI.beginTransaction(SETX);
        b = SPI.transfer(0x00);              
        digitalWrite(CSX,HIGH); 
    SPI.endTransaction();

    // Build Pixy 16-bit Word 
    c = (uint16_t)(b | (a<<8));

    return  c;
}




uint16_t  pixy_parse_data( uint16_t target, SPISettings SETX, uint8_t CSX, PIXY *pixy)
{
    int       ii, jj;
    uint16_t  word1, word2, wordx, checksum_spi, checksum_verify;
    uint16_t  nblock, data[8];
    bool      SYNC;


    // Initialize variables
    for (ii=0; ii<8; ii++)  data[ii]=0;
    

    // Sync with Pixy
    SYNC  = 0;
    word1 = 0;
    word2 = pixy_spi_read_16bit( target, SETX, CSX);  
    jj    = 0; 
    while ( SYNC==0 && jj<PIXY_SPI_TRY)
    {
        jj++;
        word1 = word2;
        word2 = pixy_spi_read_16bit( target, SETX, CSX);

        if (word1==PIXY_START_WORD && (word2==PIXY_START_WORD || word2==PIXY_START_WORD_CC) )  {
            SYNC  = 1;
            wordx = word2;
        }
    }

   
    // Read Pixy Data Blocks 
    nblock=0;

    while (SYNC==1)
    {                       
        SYNC=0;

        if ( wordx==PIXY_START_WORD || wordx==PIXY_START_WORD_CC )
        {
            data[0] = wordx;                                      // Object type: Normal (0xaa55), Color Code (0xaa56)
            data[1] = pixy_spi_read_16bit( target, SETX, CSX);    // Checksum
            data[2] = pixy_spi_read_16bit( target, SETX, CSX);    // Signature No.
            data[3] = pixy_spi_read_16bit( target, SETX, CSX);    // X Center of object
            data[4] = pixy_spi_read_16bit( target, SETX, CSX);    // Y Center of object
            data[5] = pixy_spi_read_16bit( target, SETX, CSX);    // Width of object
            data[6] = pixy_spi_read_16bit( target, SETX, CSX);    // Height of object

            if (wordx==PIXY_START_WORD)     data[7]=0;                                        // Angle not available for Normal Objects
            if (wordx==PIXY_START_WORD_CC)  data[7]=pixy_spi_read_16bit( target, SETX, CSX);  // Angle of object for Color-Coded-Objects

            // Verify CHECKSUM
            checksum_spi    = data[1];
            checksum_verify = data[2] + data[3] + data[4] + data[5] + data[6] + data[7];


            // Capture Block Data
            if (checksum_spi==checksum_verify  && nblock<PIXY_MAX_BLOCKS)
            {
                SYNC = 1;                
                nblock++;
                                
                pixy->sig = data[2];
                pixy->x   = data[3];
                pixy->y   = data[4];
                pixy->wd  = data[5];
                pixy->ht  = data[6];
                pixy->phi = (int16_t)data[7];
    
                pixy++;  // Increment pointer to PIXY struct array

                wordx = pixy_spi_read_16bit( target, SETX, CSX);    // Advance to next data block
            }             
            
        }  // if ( wordx==PIXY_START_WORD || wordx==PIXY_START_WORD_CC )
        
    }  // while (SYNC==1)

    return  nblock;
  
}
 



#define MAXLINE 136
char  nmeac[MAXLINE];


void setup() 
{
    int       ii, jj, kk;
    uint16_t  nblock;

    PIXY      pixy[PIXY_MAX_BLOCKS];

    
    // INITIALIZE SPI
    pinMode( PIXY_SPI_CS, OUTPUT);
    digitalWrite( PIXY_SPI_CS,HIGH);
 
    SPI.setMOSI(MOSIX);
    SPI.setMISO(MISOX);
    SPI.setSCK(SCKX);
    SPI.begin(); 

    Serial.begin(115200); 
    delayMicroseconds(1000000);    
    Serial.println("Initializing...");
    
     
    // Stream Pixy Data Blocks
    for( ii=0; ii<10000; ii++)
    {        
        nblock = pixy_parse_data( PIXY_SYNC_BYTE_DATA, PIXYSPI, PIXY_SPI_CS, pixy);

        if (nblock>0)
        {
            for (jj=0; jj<nblock; jj++)
            {
                sprintf(nmeac, "  %d %d %d %d %d %d %d\r\n", nblock, pixy[jj].sig, pixy[jj].x, pixy[jj].y, pixy[jj].wd, pixy[jj].ht, pixy[jj].phi ); 
                Serial.print(nmeac);
            }
        }
        else  Serial.println("0");

      
        delayMicroseconds(20000);  // Delay=0.02sec (50 Hz sample rate)
    }

}




void loop() 
{
}
 
The aforementioned Pixy functions for SPI communication with the Teensy 3.5 are working well. Now I want to try the setup on my Teensy 3.6 for processing speed, however it's not 5V tolerant. Researching PRJC threads on level shifting say bi-directional level shifters don't work. What's the recommended approach for level shifting SPI signals at 1 Mhz for the Teensy 3.6?
 
The aforementioned Pixy functions for SPI communication with the Teensy 3.5 are working well. Now I want to try the setup on my Teensy 3.6 for processing speed, however it's not 5V tolerant. Researching PRJC threads on level shifting say bi-directional level shifters don't work. What's the recommended approach for level shifting SPI signals at 1 Mhz for the Teensy 3.6?

This should help you.
http://cmucam.org/attachments/915/cmucam5_1_2.pdf
Teensy 3.6 connect directly to SPI.
 
Status
Not open for further replies.
Back
Top