DFRobot IR Positioning Camera For Arduino
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
.
- open a terminal and type
sudo raspi-config
- Use the arrow keys to navigate to “3 Interface Options” and press
Enter
- Use the arrow keys to navigate to “I5 I2C” and press
Enter
- Use the arrow keys to select
<Yes>
and pressEnter
- Press
Enter
to select<Ok>
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;
1);
exit(
}
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;
1);
exit( }
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;
0;
delay.tv_sec = 10000000; // 10 milliseconds
delay.tv_nsec = 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); write2bytes(file,
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
0] = 0x36;
buffer[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
16;
len = 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:
0] = buffer[1];
Ix[0] = buffer[2];
Iy[3];
s = buffer[0] += (s & 0x30) <<4;
Ix[0] += (s & 0xC0) <<2; Iy[
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];
0] = d1;
buffer[1] = d2;
buffer[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;
1);
exit(
}
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;
1);
exit(
}
// IR sensor initialize (sequence from DFRobot arduino example)
struct timespec delay;
struct timespec remains;
0;
delay.tv_sec = 10000000; // 10 miliseconds
delay.tv_nsec = 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);
write2bytes(file,100000000; // 100 miliseconds
delay.tv_nsec =
nanosleep(&delay, &remains);
// Read continuously
char buffer[255] = {0};
int Ix[4];
int Iy[4];
int s;
"IR", cv::WINDOW_AUTOSIZE);
cv::namedWindow(while(true) {
// send IR sensor read command
0] = 0x36;
buffer[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
16;
len = if (read(file, buffer, len) != len)
{std::cerr << "Failed to read from the i2c bus.\n";
}
0] = buffer[1];
Ix[0] = buffer[2];
Iy[3];
s = buffer[0] += (s & 0x30) <<4;
Ix[0] += (s & 0xC0) <<2;
Iy[
1] = buffer[4];
Ix[1] = buffer[5];
Iy[6];
s = buffer[1] += (s & 0x30) <<4;
Ix[1] += (s & 0xC0) <<2;
Iy[
2] = buffer[7];
Ix[2] = buffer[8];
Iy[9];
s = buffer[2] += (s & 0x30) <<4;
Ix[2] += (s & 0xC0) <<2;
Iy[
3] = buffer[10];
Ix[3] = buffer[11];
Iy[12];
s = buffer[3] += (s & 0x30) <<4;
Ix[3] += (s & 0xC0) <<2;
Iy[
std::cout << "----" << std::endl;
1024, 768, CV_8UC3, cv::Scalar(0,0,0));
cv::Mat displayImg( 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] );0,0,255);
cv::Scalar color( if( i == 1 ) {
0,255, 0);
color = cv::Scalar( else if (i == 2 ) {
} 255,0, 0);
color = cv::Scalar(else if( i == 3 ) {
} 255,255, 0);
color = cv::Scalar(
}1.0, color, 2.0f );
cv::circle( displayImg, pt,
}// slight delay
"IR", displayImg);
cv::imshow(auto key = cv::waitKey(5);
if( key == 'q' ) {
break;
}
} "IR");
cv::destroyWindow(return 0;
}
It shouldn’t be difficult to add this to Devastar.