DevastarRaspberry PI

Devastar DFRobot IR Positioning Camera and Raspberry PI

2024/02/28

DFRobot IR Positioning Camera For Arduino

DF Robot IR Trackiers I’ve been looking to improve the latency my light gun project. My code spends the most time grabbing images from the camera and processing the images. So I’ve been looking for faster, monochromatic, global-shutter IR and visible light cameras. I’ve ordered some from from a couple of sources, but they’re weeks away. While searching for better IR cameras I stumbled upon DFRobot’s IR Positioning Camera for Arduino. It’s an IR camera with built in tracking for up to four IR sources. I took a look at the specifications and they’re pretty good.

  • Operating voltage: 3.3-5v
  • Interface: I2C​
  • Detecting distance: 0~3m
  • Horizontal detecting angle: 33 degrees
  • Vertical detecting angel: 23 degrees
  • Dimensions: 32mm x 16mm(1.26x0.63")
  • Resolution is 128x96 pixel, with hardware image processing, which can track four objects (IR emitting or reflecting objects)

The distance isn’t great, but not bad. At slightly less than 10 feet, it’s close the distance I use my Sega Menacer with my CRT television. The resolution looks low, but the “Datasheet” (it’s really not) says it can provide 1024x768 resolution for the tracked points. This is actually a great idea, BTW. I may be able to reduce some of my processing time by using lower resolution images. That’ll be a different article

Raspberry Pi I2C

So, right now, most of my light gun code runs on a Raspberry PI 4 and not an Arduino. This shouldn’t be a problem because the IR tracking device actually uses an I2C interface to transmit tracked points. Raspberry Pis have built-in I2C support, so it should be possible to communicate with it without too much trouble

Enable I2C

The first thing to do is enable I2C support. This can be done with raspi-config.

  1. open a terminal and type sudo raspi-config
  2. Use the arrow keys to navigate to “3 Interface Options” and press Enter 3 Interface Options Screen
  3. Use the arrow keys to navigate to “I5 I2C” and press Enter I5 I2C
  4. Use the arrow keys to select <Yes> and press Enter Enable ARM I2C Interface
  5. Press Enter to select <Ok> Enabled screen
Finding the I2C device.

First, install relevant packages

sudo apt install i2c-tools  libi2c-dev

You can find the device name by listing /dev/

$ ls /dev/i2c*
/dev/i2c-1

The i2c-tools can be used to scan for connected I2C devices with:

sudo i2cdetect -y 1

On my system, the command shows the DFRobot IR camera has address 0x58

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- 58 -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Controlling the DFRobot IR Camera

DFRobot has example code for Arduinos on their wiki. I’ve tried the code out with an Arduino Leonardo and it works. They’re using Wire to handle the I2C communication. I’m not using an Arduino for my project though, so I’ll be using file descriptors and ioctl to communicate with the camera.

Setting up an I2C device isn’t difficult. You need the device name from /dev/ and the I2C address.

    // Setup I2C device
    std::string deviceName = "/dev/i2c-1";
    auto file = open(deviceName.c_str(), O_RDWR);
    if (file < 0) {
        std::cerr << "Failed to open device: " << deviceName << std::endl;
        exit(1);
    }

    int addr = 0x58; // i2c address
    if (ioctl(file, I2C_SLAVE, addr) < 0) {
        std::cerr << "Failed to access device at address: " << std::hex << addr << std::endl;
        exit(1);
    }

Once the file descriptor is ready, you send a sequence of bytes to initialize the IR sensor. 10 millisecond delays are used after every two bytes sent.

    struct timespec delay;
    struct timespec remains;
    delay.tv_sec = 0;
    delay.tv_nsec = 10000000; // 10 milliseconds
    write2bytes(file,0x30,0x01); nanosleep(&delay, &remains);
    write2bytes(file,0x30,0x08); nanosleep(&delay, &remains);
    write2bytes(file,0x06,0x90); nanosleep(&delay, &remains);
    write2bytes(file,0x08,0xC0); nanosleep(&delay, &remains);
    write2bytes(file,0x1A,0x40); nanosleep(&delay, &remains);
    write2bytes(file,0x33,0x33); nanosleep(&delay, &remains);

After initialization, you get coordinates from the device by sending it 0x36 and reading the next 16 bytes that come from it.

        // send IR sensor read command
        buffer[0] = 0x36;
        size_t len = 1;
        if (write(file, buffer, len) != len)        
        {
            std::cerr << "Failed to write to the i2c bus.\n";
        }   

        // read 16 bytes over i2C
        len = 16;
        if (read(file, buffer, len) != len) 
        {
            std::cerr << "Failed to read from the i2c bus.\n";
        }

The device can return 0-1023 for the X values and 0-767 for the Y values. Both values are larger than a byte ( 10-bits for both ). Rather than take up 4 bytes, they pack both values into 3 bytes. You can unpack the values with a bitwise & and some bit shifting:

        Ix[0] = buffer[1];
        Iy[0] = buffer[2];
        s   = buffer[3];
        Ix[0] += (s & 0x30) <<4;
        Iy[0] += (s & 0xC0) <<2;

As an initial test, I made a simple program that sets up the I2C file descriptor, initializes the IR sensor, and continuously polls the device for X and Y values. The points are then drawn with OpenCV.

Here’s the code:

#include <linux/i2c-dev.h>
//#include <i2c/smbus.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h> 

#include <iostream>
#include <string>


#include <opencv2/opencv.hpp>

// largely based on  Arduino code at https://wiki.dfrobot.com/Positioning_ir_camera__SKU_SEN0158



bool write2bytes(int file, char d1, char d2)
{
    char buffer[2];
    buffer[0] = d1;
    buffer[1] = d2;
    if (write(file, buffer, 2) != 2) {
        return false;
    }
    return true;
}

int main(int argc, char* argv[] ) {

    // Setup I2C device
    std::string deviceName = "/dev/i2c-1";
    auto file = open(deviceName.c_str(), O_RDWR);
    if (file < 0) {
        std::cerr << "Failed to open device: " << deviceName << std::endl;
        exit(1);
    }

    int addr = 0x58; // i2c address
    if (ioctl(file, I2C_SLAVE, addr) < 0) {
        std::cerr << "Failed to access device at address: " << std::hex << addr << std::endl;
        exit(1);
    }


    // IR sensor initialize (sequence from DFRobot arduino example)
    struct timespec delay;
    struct timespec remains;
    delay.tv_sec = 0;
    delay.tv_nsec = 10000000; // 10 miliseconds
    write2bytes(file,0x30,0x01); nanosleep(&delay, &remains);
    write2bytes(file,0x30,0x08); nanosleep(&delay, &remains);
    write2bytes(file,0x06,0x90); nanosleep(&delay, &remains);
    write2bytes(file,0x08,0xC0); nanosleep(&delay, &remains);
    write2bytes(file,0x1A,0x40); nanosleep(&delay, &remains);
    write2bytes(file,0x33,0x33); nanosleep(&delay, &remains);
    delay.tv_nsec = 100000000; // 100 miliseconds
    nanosleep(&delay, &remains);


    // Read continuously
    char buffer[255] = {0};
    int Ix[4];
    int Iy[4];
    int s;
    cv::namedWindow("IR", cv::WINDOW_AUTOSIZE);
    while(true) {
        // send IR sensor read command
        buffer[0] = 0x36;
        size_t len = 1;
        if (write(file, buffer, len) != len)        
        {
            std::cerr << "Failed to write to the i2c bus.\n";
        }   

        // read 16 bytes over i2C
        len = 16;
        if (read(file, buffer, len) != len) 
        {
            std::cerr << "Failed to read from the i2c bus.\n";
        }

        Ix[0] = buffer[1];
        Iy[0] = buffer[2];
        s   = buffer[3];
        Ix[0] += (s & 0x30) <<4;
        Iy[0] += (s & 0xC0) <<2;

        Ix[1] = buffer[4];
        Iy[1] = buffer[5];
        s   = buffer[6];
        Ix[1] += (s & 0x30) <<4;
        Iy[1] += (s & 0xC0) <<2;

        Ix[2] = buffer[7];
        Iy[2] = buffer[8];
        s   = buffer[9];
        Ix[2] += (s & 0x30) <<4;
        Iy[2] += (s & 0xC0) <<2;

        Ix[3] = buffer[10];
        Iy[3] = buffer[11];
        s   = buffer[12];
        Ix[3] += (s & 0x30) <<4;
        Iy[3] += (s & 0xC0) <<2;

        std::cout << "----" << std::endl;
        cv::Mat displayImg( 1024, 768, CV_8UC3, cv::Scalar(0,0,0));
        for(int i=0; i<4; i++)
        {
            std::cout << i << ": " <<  int(Ix[i])  << "," << int(Iy[i]) << std::endl;
            cv::Point2f pt( Ix[i], Iy[i] );
            cv::Scalar color( 0,0,255);
            if( i == 1 ) {
                color = cv::Scalar( 0,255, 0);
            } else if (i == 2 ) {
                color = cv::Scalar(255,0, 0);
            } else if( i == 3 ) {
                color = cv::Scalar(255,255, 0);
            }
            cv::circle( displayImg, pt, 1.0, color, 2.0f );
        }
        // slight delay
        cv::imshow("IR", displayImg);
        auto key = cv::waitKey(5);
        if( key == 'q' ) {
            break;
        }

    }   
    cv::destroyWindow("IR");
    return 0;
}

It shouldn’t be difficult to add this to Devastar.

About Me

Greg Gallardo

I'm a software developer and sys-admin in Iowa. I use C++, C#, Java, Swift, Python, JavaScript and TypeScript in various projects. I also maintain Windows and Linux systems on-premise and in the cloud ( Linode, AWS, and Azure )

Github

Mastodon

YouTube

About you

IP Address: 18.117.94.180

User Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)

Language:

Latest Posts

Iowa City Weather

Overnight

42 ˚F / 46 ˚F

Friday Night

41 ˚F / 50 ˚F

Saturday Night

35 ˚F / 48 ˚F

Sunday Night

32 ˚F / 44 ˚F

Monday Night

30 ˚F / 35 ˚F

Tuesday Night

24 ˚F / 31 ˚F

Wednesday Night

19 ˚F / 28 ˚F