Arduino based Human Interface Device

Part 2

Originally published at siliconrepublic.blogspot.com on August 10, 2010.

Continuing from my last blog-post about creating Arduino based Human Interface devices, here is the video I promised:

The PS2dev library (http://www.arduino.cc/playground/ComponentLib/Ps2mouse) lets the Arduino emulate a PS/2 device and I used a PS/2 to USB converter off the shelf for 3 bucks since my laptop does not have a PS/2 port. To use the PS2dev library, drag and drop the files ps2dev.h and ps2dev.cpp into your Arduino IDE before you upload it. They should appear as tabs with the corresponding names. Also, you have to go to the ps2dev.h tab and comment out #include “WConstants.h and add a line #include <Wprogram.h> until the start of the header file appears like:

#ifndef ps2dev_h
#define ps2dev_h
//#include “WConstants.h”
#include <Wprogram.h>

Code:

Now comes the code :) It is extremely shabby since I probably have unnecessary headers and variables lying around. And also I did not organize the code into any objects or classes whatsoever. However, I am planning to write a nice keyboard and mouse class using the PS2dev library so that future Arduino HIDers can effortlessly implement absolute/relative mouse movements, key-presses without referring to PS/2 scan codes and have a much much cleaner code :D

#include "ps2dev.h" // to emulate a PS/2 device
#include <Wire.h> // to communicate over the I2C port
#include <string.h>
#include <math.h>
#undef int
#include <stdio.h>
uint8_t outbuf[6]; // array to store arduino input
int cnt = 0;
int ledPin = 13;
PS2dev keyboard(3,2); // PS2dev object (2:data, 3:clock)
int enabled =0; // pseudo variable for state of "keyboard"
int joy_x_axis; //byte 1
int joy_y_axis; //byte 2
int accel_x_axis; //byte 3
int accel_y_axis; //byte 4
int accel_z_axis; //byte 5
int z_button = 0;
int c_button = 0;
int roll = 0;
int pitch = 0;
void ack() {
//acknowledge commands
while(keyboard.write(0xFA));
}
int keyboardcommand(int command) {
unsigned char val;
switch (command) {
case 0xFF: //reset
ack();
//the while loop lets us wait for the host to be ready
while(keyboard.write(0xAA)!=0);
break;
case 0xFE: //resend
ack();
break;
case 0xF6: //set defaults
//enter stream mode
ack();
break;
case 0xF5: //disable data reporting
//FM
enabled = 0;
ack();
break;
case 0xF4: //enable data reporting
//FM
enabled = 1;
ack();
break;
case 0xF3: //set typematic rate
ack();
keyboard.read(&val); //do nothing with the rate
ack();
break;
case 0xF2: //get device id
ack();
keyboard.write(0xAB);
keyboard.write(0x83);
break;
case 0xF0: //set scan code set
ack();
keyboard.read(&val); //do nothing with the rate
ack();
break;
case 0xEE: //echo
//ack();
keyboard.write(0xEE);
break;
case 0xED: //set/reset LEDs
ack();
keyboard.read(&val); //do nothing with the rate
ack();
break;
}
}
void setup() {
Wire.begin (); // join i2c bus with address 0x52
nunchuck_init (); // send the initilization handshake
delay(2000); //initialization time just in case
// send the keyboard start up
while(keyboard.write(0xAA)!=0);
}
void nunchuck_init ()
{
Wire.beginTransmission (0x52);// transmit to device 0x52
Wire.send (0x40); // sends memory address
Wire.send (0x00); // sends sent a zero.
Wire.endTransmission (); // stop transmitting
}
void send_zero ()
{
Wire.beginTransmission (0x52);// transmit to device 0x52
Wire.send (0x00); // sends one byte
Wire.endTransmission (); // stop transmitting
}
void loop() {
Wire.requestFrom (0x52, 6); // request data from nunchuck
while (Wire.available ())
{
outbuf[cnt] = nunchuk_decode_byte (Wire.receive ());
// receive byte as an integer
digitalWrite (ledPin, HIGH); // sets the LED on
cnt++;
}
// If we recieved the 6 bytes, then go process them
if (cnt >= 5)
{
process ();
}
unsigned char c;
//if host device wants to send a command:
if( (digitalRead(3)==LOW) || (digitalRead(2) == LOW)) {
while(keyboard.read(&c)) ;
keyboardcommand(c);
}
else{ //send keypresses accordingly using scancodes
if(joy_y_axis > 198){keyboard.write(0x1D);}
else{ keyboard.write(0xF0); keyboard.write(0x1D);}
if(joy_y_axis < 68){keyboard.write(0x1B);}
else{ keyboard.write(0xF0); keyboard.write(0x1B);}
if(joy_x_axis < 58){keyboard.write(0x1C);}
else{ keyboard.write(0xF0); keyboard.write(0x1C);}
if(joy_x_axis > 190){keyboard.write(0x23);}
else{ keyboard.write(0xF0); keyboard.write(0x23);}
if(pitch < 65){keyboard.write(0x43);}
else{ keyboard.write(0xF0); keyboard.write(0x43);}
if(pitch > 115){keyboard.write(0x42);}
else{ keyboard.write(0xF0); keyboard.write(0x42);}
if(roll > 125 ){keyboard.write(0x3B);}
else{ keyboard.write(0xF0); keyboard.write(0x3B);}
if(roll < 55){keyboard.write(0x4B);}
else{ keyboard.write(0xF0); keyboard.write(0x4B);}
if(!z_button){keyboard.write(0x29);}
else{ keyboard.write(0xF0); keyboard.write(0x29);}
if(!c_button){keyboard.write(0x12);}
else{ keyboard.write(0xF0); keyboard.write(0x12);}
}
cnt = 0;
send_zero (); // send the request for next bytes
delay (10);
}
void process (){
joy_x_axis = outbuf[0]; //byte 1
joy_y_axis = outbuf[1]; //byte 2
accel_x_axis = outbuf[2] * 2 * 2; //byte 3
accel_y_axis = outbuf[3] * 2 * 2; //byte 4
accel_z_axis = outbuf[4] * 2 * 2; //byte 5
z_button = 0;
c_button = 0;
roll = 0;
pitch = 0;
// byte outbuf[5] contains bits for z and c buttons
// it also contains the least significant bits for the accelerometer data
// so we have to check each bit of byte outbuf[5]
if ((outbuf[5] >> 0) & 1) //'&1' to see only LSB after left shifts
{
z_button = 1; //bit 0
}
if ((outbuf[5] >> 1) & 1)
{
c_button = 1; //bit 1
}
if ((outbuf[5] >> 2) & 1)
{
accel_x_axis += 1;
}
if ((outbuf[5] >> 3) & 1)
{
accel_x_axis += 2;
}
if ((outbuf[5] >> 4) & 1)
{
accel_y_axis += 1;
}
if ((outbuf[5] >> 5) & 1)
{
accel_y_axis += 2;
}
if ((outbuf[5] >> 6) & 1)
{
accel_z_axis += 1;
}
if ((outbuf[5] >> 7) & 1)
{
accel_z_axis += 2;
}
//out here the values 499, 518 and 216.0 might differ
//due to the slight differences nunchucks
roll = (atan2( (accel_z_axis-499), (accel_x_axis-515) )/3.14159*180);
pitch = (acos( (accel_y_axis-518)/216.0 )/3.14159*180);
}
// Encode data to format that most wiimote drivers except
// only needed if you use one of the regular wiimote drivers
char nunchuk_decode_byte (char x)
{
x = (x ^ 0x17) + 0x17;
return x;
}

Schematic:

Enjoy!


Originally published at siliconrepublic.blogspot.com on August 10, 2010.