Categories
Electronics Microcontrollers Programming

Arduino Uno as USB HID keyboard – Part 2

In part one, I looked at the hardware side of things, and the steps required to get our Arduino Uno ready to emulate a USB keyboard. In part two, I show the software the Arduino and the BlackBerry will run.

Arduino sketch

The programme run by the Arduino Uno is pretty simple. I based it off of the example I saw in Michael Mitchell’s tutorial. As we have only one true UART available on an Atmel ATmega328P, and that is our communications link to the ATmega16U2, in order to talk to the Bluetooth module, we’ll have to fall back to software serial.
Thankfully, to paraphrase a certain fruit-
based company- “There’s a library for that”.
There really isn’t much to the sketch- it loops looking for incoming bytes, and squirts them to the ATmega16U2, which is pretending to be a USB HID keyboard.
Note that, keyboards aren’t read like simple switches, being polled for their state, but they send events- key down, and key released. If the other end does not hear the key released event, it thinks the key is still being pressed. This makes it possible to send modifier keys- such as the Shift key, which would be pressed first, followed by, say, a letter key, which is then released, followed by the Shift key being released.
In a future modification, although I intended this to specifically control PowerPoint/LibreOffice Impress presentations, I would quite like to be able to Alt-Tab my way through different open windows, and that isn’t possible with this- the Alt key would end up being released before it had
sent the Tab key event.
Also note- you don’t send strings, or characters, but scancodes. Thankfully, all USB HID keyboards send the same codes, so it was just a matter of looking it up in a reference table. You’re interested in the column marked “HID Usage ID”.
[code lang=”C”]#include <SoftwareSerial.h>
uint8_t buf[8] = { 0 }; /* Keyboard report buffer */
SoftwareSerial btSerial(10, 11); // RX, TX
void setup() {
Serial.begin(9600);
btSerial.begin(9600);
delay(200);
}
void loop() {
if (btSerial.available() > 0 ) {
buf[2] = btSerial.read();
Serial.write(buf, 8); // Send keypress
releaseKey();
}
}
void releaseKey() {
buf[0] = 0;
buf[2] = 0;
Serial.write(buf, 8); // Release key
}[/code]

BlackBerry code

I haven’t really got the inclination to explain all the steps of getting code designed, written, compiled and uploaded to a BlackBerry. Not in detail anyway. And I suspect a lot of what I know are really not ideal coding practises. Nevertheless, here’s a short summary, and if you want more detail, just ask.
First you need the development system. There are a number of options, such as HTML5, that we can’t use, because we need access to the hardware. Recently (at least I don’t remember seeing it when I looked originally) there is a native C++ option. But I went with Java, even though I didn’t know any Java. But one of our modules on the FdSc Computing and IT course is based on Java, I’d just picked up the assignment out of curiosity, and wanted to see how quickly I could complete it.
The Java environment is based on the popular Eclipse IDE, and comes with not just the editor, and inline documentation, but also a
version of the JDK with all the needed libraries, and a simulator for debugging. The latter wasn’t too handy for my purposes, because despite running on a laptop that had Bluetooth, the simulator requires an expensive bit of external hardware to run Bluetooth. The pre-packaged Eclipse is can be downloaded here: BlackBerry Plug-in for Eclipse
Because of reaching into the hardware, we are required to have signing keys. The simulator can be told to ignore unsigned code, the real phones can’t, I believe. However the keys are free, and apart from being labelled as spam by the college’s email system, and disappearing my keys into a black hole, getting them was painless.
For deploying it onto my phone, I forget what my problem was about hooking up the phone via USB and loading it that way- I think the software
might have been really slow. So the way I have it set up, I upload two files (ppremote.cod and ppremote.jad) created during compilation onto the webspace here, browse to the location on the phone’s web browser, download the ppremote.jad file and then I am prompted as to whether I wish to install it. The ppremote.jad file appears to contain version/developer information, and must also point at the ppremote.cod file that contains the code.
I will admit I found this quite a challenge. It took some time to get my head around the implementation of a separate thread for handling the comms, so that it didn’t block the application. And there’s still aspects I haven’t figured out- I’ve tried to use a regular timed poll of the Bluetooth status to set the display. But it never updates. The timed part of it is fine, I can use that to display a incrementing number. I suspect it is some kind of confusion between classes and objects, static fields and members.
Still, it works. I can use the keypad or buttons (on a touchscreen this would be much better) to sent scancodes to the Arduino, which sends them on to the computer, and if it is running LibreOffice Impress, it snappily responds to the requests, with no apparent penalty compared with using a real keyboard.
So, the code. It is broken into five files, corresponding to the five classes I created.
PPremote.java:
[code lang=”java”]package ppremote;
import net.rim.device.api.ui.UiApplication;
public class PPremote extends UiApplication {
public static CommThread comms;
private String status = "";
public static void main( String[] args ) {
// Create a new instance of the application and make the currently
// running thread the application’s event dispatch thread.
// Fire up the BT communications thread
PPremote theApp = new PPremote();
comms = new CommThread(theApp);
comms.start(
);
theApp.enterEventDispatcher();
}
public PPremote() {
status = "Connecting";
pushScreen( new PPremoteScreen(this) );
}
public void setStatus (String statusMessage) {
status = statusMessage;
}
public String getStatus () {
return status;
}
}[/code]
PPremoteScreen.java:
[code lang=”java”]package ppremote;
import java.util.Timer;
import net.rim.device.api.system.Alert;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.util.StringProvider;
public class PPremoteScreen extends MainScreen implements FieldChangeListener {
ButtonField buttonField_1, buttonField_2, buttonField_3,
buttonField_4, buttonField_5, buttonField_6;
LabelField status;
PPremote mainApp;
public PPremoteScreen(PPremote mainApp) {
super( MainScreen.VERTICAL_SCROLL | MainScreen.VERTICAL_SCROLLBAR );
setTitle( "ppRemote");
this.mainApp = mainApp;
Timer update = new Timer();
update.scheduleAtFixedRate(new CommsCheck(this), 0, 2000); // This ought to be allowing me to update the label based
// on the current Bluetooth connection status. It doesn’t.
addKeyListener(new KeyListenerImplementation());
LabelField blurb = new LabelField("", Field.USE_ALL_WIDTH | DrawStyle.LEFT) {
public int getPreferredWidth() {
return Display.getWidth() – 40;
}
protected void layout(int maxWidth,int maxHeight) {
super.layout(getPreferredWidth(),maxHeight);
setExtent(getPreferredWidth(),
getHeight());
}
};
blurb.setText("Use keypad 1, 2, 3 and 4, 5, 6 to send commands.");
add(blurb);
blurb.setMargin(10, 10, 0, 5);
status = new LabelField(mainApp.getStatus());
status.setMargin(5, 10, 10, 5);
add(status);
GridFieldManager numGrid = new GridFieldManager(2, 3, GridFieldManager.AUTO_SIZE);
buttonField_1 = new ButtonField( " <– ", ButtonField.CONSUME_CLICK | ButtonField.FIELD_HCENTER );
buttonField_2 = new ButtonField( " F5 ", ButtonField.CONSUME_CLICK | ButtonField.FIELD_HCENTER );
buttonField_3 = new ButtonField( " –> ", ButtonField.CONSUME_CLICK | ButtonField.FIELD_HCENTER );
buttonField_4 = new ButtonField( " |<< ", ButtonField.CONSUME_CLICK | ButtonField.FIELD_HCENTER );
buttonField_5 = new ButtonField( " >>| ", ButtonField.CONSUME_CLICK | ButtonField.FIELD_HCENTER );
buttonField_6 = new ButtonField( " blk ",
ButtonField.CONSUME_CLICK | ButtonField.FIELD_HCENTER );
numGrid.insert( buttonField_1, 0, 0);
numGrid.insert( buttonField_2, 0, 1);
numGrid.insert( buttonField_3, 0, 2);
numGrid.insert( buttonField_4, 1, 0);
numGrid.insert( buttonField_6, 1, 1);
numGrid.insert( buttonField_5, 1, 2);
numGrid.setRowPadding(6);
numGrid.setColumnPadding(10);
buttonField_1.setChangeListener(this);
buttonField_2.setChangeListener(this);
buttonField_3.setChangeListener(this);
buttonField_4.setChangeListener(this);
buttonField_5.setChangeListener(this);
buttonField_6.setChangeListener(this);
add(numGrid);
}
public void updateTitle() {
status.setText(mainApp.getStatus());
}
public void fieldChanged(Field field, int context) {
// All of these hex numbers are taken from the USB HID
keyboard scancode table
if (field == buttonField_1) {
PPremote.comms.sendChar((char) 0x50); // left arrow
} else if (field == buttonField_2) {
PPremote.comms.sendChar((char) 0x3E); // F5
} else if (field == buttonField_3) {
PPremote.comms.sendChar((char) 0x4F); // right arrow
} else if (field == buttonField_4) {
PPremote.comms.sendChar((char) 0x4A); // Home
} else if (field == buttonField_5) {
PPremote.comms.sendChar((char) 0x4D); // End
} else if (field == buttonField_6) {
PPremote.comms.sendChar((char) 0x05); // b (blacks out the screen)
}
}
public boolean onSavePrompt()
{
return true; // Prevents OS asking if you want to "save changes" on close.
}
}[/code]
KeyListenerImplementation.java:
[code lang=”java”]package ppremote;
//import net.rim.device.api.system.Alert;
//import net.rim.device.api.system.Characters;
import net.rim.device.api.system.
KeyListener;
public class KeyListenerImplementation implements KeyListener
{
// Implement methods in the KeyListener interface for handling keyboard events:
public boolean keyChar( char key, int status, int time )
{
char ch = 0;
switch (key) {
case 0x2e: // keypad 1
ch = 0x50;
break;
case 0x61: // keypad 2
ch = 0x3E;
break;
case 0x64: // keypad 3
ch = 0x4F;
break;
case 0x67: // keypad 4
ch = 0x4A;
break;
case 0x6a: // keypad 5
ch = 0x05;
break;
case 0x6d: // keypad 6
ch = 0x4D;
break;
}
if (ch != 0) {
PPremote.comms.sendChar(ch);
}
return false;
}
public boolean keyDown(int keycode, int time)
{
return false;
}
public boolean keyRepeat(int keycode, int time)
{
return
false;
}
public boolean keyStatus(int keycode, int time)
{
return false;
}
public boolean keyUp(int keycode, int time)
{
return false;
}
}</code></pre>
<i>CommThread.java:</i>
<pre><code lang="java">package ppremote;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import net.rim.device.api.bluetooth.BluetoothSerialPort;
public class CommThread extends Thread {
private static DataOutputStream _dout;
private static String info;
private static StreamConnection _bluetoothConnection;
private PPremote mainApp;
public CommThread(PPremote mainApp) {
this.mainApp = mainApp;
try {
info = BluetoothSerialPort.getSerialPortInfo()[0].toString();
this.mainApp.setStatus("Paired");
} catch (Exception e) {
mainApp.setStatus("Not paired");
}
}
public void run() {
try {
_bluetoothConnection = (StreamConnection)Connector.open( info, Connector.WRITE );
mainApp.setStatus("Connected");
} catch (IOException ioe) {
mainApp.setStatus("No connection");
}
try {
_dout = _bluetoothConnection.openDataOutputStream();
mainApp.setStatus("Online");
} catch (IOException ioe) {
mainApp.setStatus("No output stream");
}
}
public void sendChar (char data) {
try {
_dout.writeByte(data);
} catch (IOException ioe) {
}
}
}[/code]
CommsCheck.java:
[code lang=”java”]package ppremote;
import java.util.Timer;
import java.util.TimerTask;
import net.rim.device.api.system.Alert;
public class CommsCheck extends TimerTask {
PPremoteScreen screen;
public
CommsCheck(PPremoteScreen mainScreen) {
screen = mainScreen;
}
public void run() {
screen.updateTitle();
}
}[/code]

Leave a Reply

Your email address will not be published. Required fields are marked *