<< return to Pixycam.com

User Tools

Site Tools


wiki:v2:porting_guide

**This is an old revision of the document!**

How to talk to Pixy2

We currently have software support for Arduino, and Raspberry Pi and similar controllers

You may want Pixy2 to talk to a different controller and that's not a problem – Pixy2 is easy to strike up a conversation with! It's easier than the original Pixy. Pixy2 has three separate methods of communication:

  • Serial: this includes SPI, I2C and UART interfaces. The serial interfaces all use a simplified protocol with a small code and memory footprint. The code is simple to port to different microcontrollers. This is the method that is used for communication with Arduino. Using the serial protocol, Pixy sends complete information about what it detects, and it accepts simple commands for setting the pan/tilt servos, etc.
  • USB: the USB interface is intended for microcontrollers that have more memory resources (RAM and flash). The code for the USB protocol has a larger memory footprint. This is the method that is used to talk to the Raspberry Pi and BeagleBone Black, and it is used by PixyMon to stream live video and read/write configuration information.
  • Analog/digital: this is the simplest interface and doesn't even have a protocol.

Picking the right interface

You first need to determine how you want to talk to Pixy2. Pixy2 sends information to Arduino at 2 Mbits/second over SPI because the Arduino SPI clock rate is set to 2 MHz. UART serial on the other hand, is typically only 19.2 to 115 kbits/second. The goal with the communication interface is to make sure that you can get the information out of Pixy2 at the full 60 frames-per-second.

  • If your controller is Linux-based and has a USB host port, use libpixyusb2. It's fairly easy to port over and USB is the fastest interface.
  • if your controller doesn't support USB, but supports SPI, use that. It's typically faster than I2C and UART, or
  • if your controller doesn't support the above interfaces, but supports I2C, use that. It's about the same speed as UART serial, but more flexible, or
  • if your controller doesn't support the above interfaces, but supports UART serial, use that, or
  • if your controller doesn't support any of these interfaces, consider using simple analog and digital output. It's the simplest interface possible!

If you plan on using USB for communication

If you plan on using USB for communication with Pixy2, we have a library called libpixyusb2. It should be relatively easy to port to your controller, especially if your controller runs Linux. If your controller runs Linux, refer to this guide, which should get you close (at the very least).
Whereas the previous version of libpixyusb required libboost and libusb, libpixyusb2 only requires libusb. Libusb is available on lots of platforms besides Linux. So if your platform doesn't run Linux, start by locating a port of libusb for your platform/OS and go from there by first getting libpixyusb2 to compile and then testing with a simple example like the get_blocks_cpp_demo that is referred to in this guide.

The rest of this guide is for other (non USB) interfaces.

Setting the interface (other than USB)

You can configure the output interface through PixyMon. The “Data out port” parameter in the “Interface” tab determines which interface you're using.

  • Arduino ICSP SPI - this is the default port that uses 3 wires (pins 1, 3, and 4 of the I/O connector) and is used to communicate with Arduino through the ICSP connector. This version of SPI does not use a slave select signal.
  • SPI with SS - this is the same as the Arduino ICSP SPI except that it includes support for Slave Select through pin 7 (SPI SS). That is you need to drive SPI SS low before sending/receiving each byte.
  • I2C - this is a multi-drop 2-wire port (pins 5 and 9 of the I/O connector) that allows a single master to communicate with up to 127 slaves (up to 127 Pixy2s). You can configure the I2C address through the “I2C address” parameter.
  • UART - this is the common “serial port” (pins 1 and 4 of the I/O connector). Pixy receives data via pin 1 (input) and transmits data via pin 4 (output). You can configure the UART baudrate through the “UART baudrate” parameter.
  • analog/digital x - this will output the x value of the largest detected object as an analog value between 0 and 3.3V on pin 8. It also outputs whether an object is detected or not as a digital signal (pin 1 of the I/O connector).
  • analog/digital y - this will output the y value of the largest detected object as an analog value between 0 and 3.3V on pin 8. It also outputs whether an object is detected or not as a digital signal (pin 1 of the I/O connector).

Note, the USB interface and protocol is always enabled, while each of the interfaces above can only be enabled one at a time.

image_248_2.jpg

Notes on each interface

SPI

The ICSP SPI interface operates as an SPI slave. It is designed around the Arduino's ICSP port, which doesn't have a slave select signal. The default data rate is 2 mbits/sec, but this can be increased by modifying the PIXY_SPI_CLOCKRATE value in Pixy2.h in the Pixy2 Arduino library. The protocol has checksums to deal with bit errors. The specific type of SPI that Pixy2 supports:

  • Data is sent most significant bit first
  • SPI SCK is low when idle
  • Data bits are latched on the falling edge of SPI SCK
  • Slave select is active low

Pixy2 also supports SPI with slave select (SPI with SS).

Here's how to hook up your controller's SPI to Pixy2 (if you are not using an Arduino and the supplied cable):

  • Pin 6 ➜ your controller's ground signal
  • Pin 1 (SPI MISO) ➜ your controller's SPI MISO signal
  • Pin 4 (SPI MOSI) ➜ your controller's SPI MOSI signal
  • Pin 3 (SPI SCK) ➜ your controller's SPI SCK signal
  • Pin 7 (SPI SS) ➜ your controller's SPI SS signal (if you're using SPI with SS)

I2C

  • The I2C interface operates as an I2C slave and requires polling.
  • There are weak 82K pullups to 5V on SDA and SCL signals, via R14 and R15.
  • I2C signals are 5V tolerant.
  • The I2C address can be configured in the “Interface” tab of the Configure dialog in PixyMon
  • There is an Arduino example that uses I2C. Run it by selecting File➜Examples➜Pixy2➜ccci2cuart in the Arduino IDE and make sure that I2C is defined.

You'll need to make a special cable.

Here's how to hook up your controller's I2C to Pixy:

  • Pin 6 ➜ your controller's ground signal
  • Pin 9 (I2C SDA) ➜ your controller's I2C SDA signal
  • Pin 5 (I2C SCL) ➜ your controller's I2C SCL signal

Note, when talking to more than one Pixy over I2C, you will need to configure a different I2C address for each Pixy2 so they don't step on each other. You can make a “multi-crimp cable”, meaning you can take a 10-conductor ribbon cable and crimp N 10-pin IDC connectors to it and plug into to your N Pixy2s. That is, when selecting I2C as an interface, all signals on Pixy2's I/O connector go into a high-impedance state and won't interfere with each other, waste power, etc.

UART

  • The UART interface is 8 data bits, 1 stop bit, no parity, no handshaking
  • The baudrate can be configured in the “Interface” tab of the Configure dialog in PixyMon
  • RX signal (pin 1) is 5V tolerant input
  • TX signal (pin 4) is 0 to 3.3V signal output
  • Baudrates up to 230 kbaud supported.
  • There is an Arduino example that uses UART serial. Run it by selecting File➜Examples➜Pixy2➜ccci2cuart in the Arduino IDE. You'll need to make a special cable.

Here's how to hook up your controller's UART to Pixy:

  • Pin 10 ➜ your controller's ground signal
  • Pin 1 (UART RX) ➜ your controller's UART TX output
  • Pin 4 (UART TX) ➜ your controller's UART RX input

Analog and digital output

  • Pixy has a single analog (DAC) output, so there are two modes for analog/digital output.
  • Setting Data out port to “analog/digital x” will output the x value of the center of the biggest detected object to pin 8 of the I/O connector.
  • Setting Data out port to “analog/digital y” will output the y value of the biggest detected object to pin 8 of the I/O connector.
  • Pin 1 goes high (3.3V) when an object is detected, and low (0V) when no object is detected.
  • Pixy's digital output is 0 to 3.3V logic and can source/sink 5 mA.
  • Pixy's analog (DAC) output ranges between 0 to 3.3V with roughly 200 ohm impedance.
  • Pixy's analog (DAC) output voltage is directly, linearly proportional to the object position in the image (depending on the mode)
  • In “analog/digital x” mode the analog output is 0V if object is on the far left of the image and 3.3V is object on the far right (PixyMon perspective).
  • In “analog/digital y” mode the analog output is 0V if object is on the bottom of the image and 3.3V is object on the top (PixyMon perspective).

Here's how to hook up your controller's ADC and digital I/O to Pixy:

  • Pin 6 ➜ your controller's ground signal
  • Pin 8 (DAC OUT) ➜ one of your controller's ADC input signals
  • Pin 1 (GPIO0) ➜ one of your controller's digital input signals

Note, all digital output signals on Pixy2 are 3.3V CMOS logic. All digital input signals on Pixy2 are 5V tolerant.

The serial protocol

Whether you're using SPI, I2C or UART serial, the protocol is exactly the same.

  • The protocol is data-efficient, binary encoded (not ASCII text).
  • All numbers are represented as little-endian, two's complement.
  • The protocol uses a simple request-response data exchange.
  • All queries have a guaranteed response latency of less than 100 microseconds.

Pixy2 communicates with “packets” in both directions – request and response. Each packet has the following structure, if no checksums are used:

  • 2 bytes of no_checksum_sync (0xc1ae)
  • 1 byte for packet_type
  • 1 byte for data_length
  • data_length number of data bytes


    or the following structure, if checksums are used:

  • 2 bytes of checksum_sync (0xc1af)
  • 1 byte for packet_type
  • 1 byte for data_length
  • 2 bytes of data_checksum (sum of data bytes)
  • data_length number of data bytes

Communication with Pixy2 begins by sending a request packet. For example, here is a packet to request the hardware/firmware version data chunk. (Normally, request packets are sent without using checksums.):

0xae  // first byte of no_checksum_sync (little endian -> least-significant byte first)
0xc1  // second byte of no_checksum_sync
0x0e  // this is the version request type
0x00  // data_length is 0

Pixy2 will respond with the packet bytes below (or similar, depending on the hardware/firmware versions.) Response packets always have checksums so the receiver (your controller) can test data integrity:

0xaf // first byte of checksum_sync (little endian -> least-significant byte first)
0xc1 // second byte of checksum_sync
0x0f // this is the version response type
0x10 // data_length is 0x10 (16) bytes 
0x0d // first byte of data checksum (little endian -> least-significant byte first)
0x03 // second byte of data checksum
0x00 // first byte of hardware version (little endian -> least-significant byte first)
0x22 // second byte of hardware version
0x03 // firmware major version number
0x00 // firmware minor version number
0x0a // first byte of firmware build number (little endian -> least-significant byte first)
0x00 // second byte of firmware build number
0x67 // byte 0 of firmware type ASCII string
0x65 // byte 1 of firmware type ASCII string
0x6e // byte 2 of firmware type ASCII string
0x65 // byte 3 of firmware type ASCII string
0x72 // byte 4 of firmware type ASCII string
0x61 // byte 5 of firmware type ASCII string
0x6c // byte 6 of firmware type ASCII string
0x00 // byte 7 of firmware type ASCII string
0x00 // byte 8 of firmware type ASCII string
0x00 // byte 9 of firmware type ASCII string

The version request-response (above) is a simple way to test communication between your controller and Pixy2. Go ahead and code it up for your controller, paying special attention to how to initialize the communication interface on your platform, whether it's SPI, I2C or UART serial. Here is some code that might help get you started:

  uint8_t i, lenReceived, recvBuf[32];
  uint8_t versionRequest[] =
  {
    0xae,  // first byte of no_checksum_sync (little endian -> least-significant byte first)
    0xc1,  // second byte of no_checksum_sync
    0x0e,  // this is the version request type
    0x00   // data_length is 0
  };
  
  // You need to write send(), which takes a pointer to the data you want to send and the number of 
  // bytes to send via your serial port (SPI, I2C or UART).  It returns the number of bytes successfully sent.
  extern uint8_t send(uint8_t *data, uint8_t len);
  
  // You also need to write recv(), which takes a pointer to a data buffer where the received data
  // will be written/returned and the number of bytes to receive via your serial port (SPI, I2C or UART).  
  // It returns the number of bytes immediately available and written into the buffer (without needing 
  // to busy-wait.)
  extern uint8_t recv(uint8_t *data, uint8_t len);
  
  // clear out any stale data
  while(recv(recvBuf, 1));
  
  send(versionRequest, 4);   
  delayMillisecond(1); // delay a little so we don't receive too fast (may not be necessary.)  
  lenReceived = recv(recvBuf, 6 + 16); // 6 bytes of header and checksum and 16 bytes of version data
  
  // print result
  printf("Received %hhu bytes.\n", lenReceived);
  for (i=0; i<lenReceived; i++)
    printf("%hhu: 0x%hhx\n", i, recvBuf[i]); 
  

If you can't get this request-response to work, spend some time trying to determine what might be going wrong. Try using an oscilloscope to see if your controller is outputting data as expected (first), and Pixy2 is outputting data as expected in response (second). The challenge with getting any communication interface up and running is the strict requirement that the wiring, connections, and code (everything) need to be correct in order for it to work. If any single detail is not correct, e.g. missed/swapped connection or incorrect initialization code, communication will not work (at all). So we start with this simple exchange. The good news is that once you get this working, the hard part is over, and the rest is quick/easy.

We have a complete protocol reference. It will be very helpful if you are porting to a platform in a language other than C++.

TPixy2.h

TPixy2.h is a template class that can be easily reused, if your porting language is C/C++. If your porting language isn't C/C++, it's still a good reference for the port translation, as it contains request-response code for all possible requests and responses. Pixy2CCC.h and Pixy2Line.h are also needed to round out the Pixy2 API.

TPixy2.h uses the LinkType class to communicate with Pixy2. It assumes some member functions and an implementation of millis() and delayMicroseconds(), as shown in the header code below:

// YourLink example class declaration 
// 
// This is an example class for use in the template class TPixy2
// (https://github.com/charmedlabs/pixy2/blob/master/src/host/arduino/libraries/Pixy2/TPixy2.h)
//
// Refer to 
// https://github.com/charmedlabs/pixy2/blob/master/src/host/arduino/libraries/Pixy2/Pixy2UART.h 
// or 
// https://github.com/charmedlabs/pixy2/blob/master/src/host/arduino/libraries/Pixy2/Pixy2.h 
// for working examples of template classes that work with TPixy2. 

#include "TPixy2.h"

class YourLink
{

public:
  // open() is called upon initialization.  A 32-bit arg is passed as-is from the TPixy2 init() function.  
  // You can ignore the arg or use it to set up an address, data rate, whatever you want. 
  // Returns 0 if successful, or <0 if it fails
  int8_t open(uint32_t arg);
  
  // close is called when the TPixy2 instance is destroyed. 
  void close();
  
  // recv() takes a pointer to a data buffer where the received data will be written/returned and 
  // the number of bytes to receive via your serial port (SPI, I2C or UART).  It returns the number of 
  // bytes immediately available and written into the buffer (without needing to busy-wait.)  
  // It also takes a pointer to a 16-bit unsigned int that it will write the simple checksum of all 
  // bytes received, but only if the pointer is non-null.  Refer to the example classes in the links above.
  int16_t recv(uint8_t *data, uint8_t len, uint16_t *checksumCalculation=NULL);
  
  // send() takes a pointer to the data you want to send and the number of bytes to send via your serial 
  // port (SPI, I2C or UART).  It returns the number of bytes successfully sent.  
  int16_t send(uint8_t *data, uint8_t len);
  
  // ...
};


// These two functions are borrowed from the Arduino API and need to be implemented in some form 
// in order for the existing TPixy2 code to work as-is.  millis() may be difficult to implement, 
// but its use isn't hugely important.  Look in TPixy2.h for its references, and remove if necessary.  
// delayMicroseconds() can be easily implemented by a simple nested for-loop, and calibrated. 
uint32_t millis();
void delayMicroseconds(uint32_t us);


// Define an easy-to-use type for Pixy2 that uses your link as the class parameter to TPixy2
typedef TPixy2<YourLink> Pixy2WithYourLink;

millis() and delayMicroseconds() are borrowed from the Arduino API.

Debugging

When attempting to get your link class integrated and everything working, problems may arise and you may have to track down the problem. A good place to start is in the getSync() function in TPixy2.h of the code. Is it finding the sync codes from Pixy2? If not, why? Is it getting the correct bytes? If it is successfully getting the sync codes, there is a problem occuring after getting the sync. This will get you started. :-)

If you need help, post on our forum or send us an email ([email protected]). Happy coding!

wiki/v2/porting_guide.1539200822.txt.gz · Last modified: 2018/10/10 19:47 by pixycam