The software side can be decomposed into 3 'subsystems':
1) USB HID mouse code
As said, example code from the PICDEM USB demo board will be used. The project we will use is in the directory 'MCHPFSUSB\fw\Hid\Mouse'. It has various files, implementing the USB and HID protocol. The 'user\user_mouse.c' file is where our project's specific code will be. This project was originally written with a PIC18F4550 in mind, so one must make some minor changes to the code:
'io_cfg.h' – since the 2550 doesn't have a D port, remove all references to the D port in the LED definitions (or replace with B)
'autofiles/usbcfg.h' – due to differences between our board and the PICDEM USB board, we must comment these 2 lines: #if defined(PIC18F4550_PICDEM_FS_USB)
#define USE_SELF_POWER_SENSE_IO
#define USE_USB_BUS_SENSE_IO
to
//#define USE_SELF_POWER_SENSE_IO
//#define USE_USB_BUS_SENSE_IO
'rm18f4550.lkr' – change 'FILES p18f4550.lib' to 'FILES p18f2550.lib'
In MPLAB, Configure->Select Device , change to 18f2550.
Programming the PIC18F2550 with this project should create a USB HID mouse that moves the pointer in a circle. If not there is something wrong! To diagnose USB connection problems I've used two programs: SnoopyPro and USBVIEW . With USBVIEW you can check if the device failed enumeration or was recognized successfully. With SnoopyPro you can log the traffic between the device and the computer – very nice!
Now one is able to send mouse events to the computer, by controlling the buffer array in the Emulate_Mouse() function.
2) Nunchuck I2C Communication Code
For some reason I wasn't being able to get the C18 library I2C functions to work with the nunchuck. Luckily, I've found some routines in a forum (written by pommie[4]) that worked perfectly. The communication with the nunchuck works as follows:
MCU sends nunchuck address (0x52) + write bit (1) = 0xA5
MCU writes 0 (8 bits)
MCU stops communication
Wait a bit for nunchuck to get data ready
MCU sends nunchuck address (0x52) + read bit (0) = 0xA4
(MCU reads byte + sends ACK ) 5 times
MCU reads byte, sends NACK
MCU stops communication
And there you go. 6 bytes of data with button presses, accelerometer status and joystick status. The meaning of each byte is explained in a nice table in
[3].
3) Generating mouse events with the Nunchuck Data
The buffer array in 'Emulate_Mouse()' has the following structure:
- 1st byte: first bit for left click, second bit for right click, remaining bits are padding
- 2nd and 3rd bytes: X and Y desired dislocation of the cursor.
The whole work goes in the Emulate_Mouse() function.
Buttons
The two buttons on the nunchuck are good candidates for left and right mouse buttons. The C button (bigger) will be the left, and the Z the right. The code to check for button presses is the following:
nunchuck_getdata(array);
buffer[1] = buffer[2] = buffer[0] = 0;
//buttons
if((array[5] & 0b11) == 0b01)
{
buffer[0] = 2;
dirty = 2; //to keep it sending signal, even 0, to release the buttons
}
if((array[5] & 0b11) == 0b0)
{
buffer[0] = 1;
dirty = 2;
}
Where array if a 6 byte array and dirty is a decreasing counter that makes the MCU send mouse signals to the computer even when the buttons are not pressed. The problem is that without it, when we released the button the MCU wouldn't send any message to the computer saying that the button was released. The nunchuck 5th byte is a bit strange: when the Z button is down, it's first 2 bits are 0x01, when C is down, it's 0x00! Testing for that we can fill the buffer[0] variable with the appropriate values.
Potentiometer
We have a potentiometer in the circuit to control the mouse movement speed. Reading it is simple: just read the ADC in port AN8. Initialization:
OpenADC( ADC_FOSC_32 & ADC_RIGHT_JUST & ADC_12_TAD, ADC_CH8 & ADC_REF_VDD_VSS & ADC_INT_OFF, 6 );
SetChanADC(ADC_CH8);
Reading the value:
ConvertADC(); // Start conversion
while( BusyADC() ); // Wait for ADC conversion
adc = ReadADC(); // Read 10bits
Calculating the speed:
delta = 1+(adc >> 7);
if(delta == 8)
delta = 14;
if(delta == 7)
delta = 10;
if(delta == 6)
delta = 8;
The ifs are to induce a little nonlinearity, so that we can reach higher velocities than our linear scale allows.
Joystick
The nunchuck's joystick is easy to control, and while it lacks precision it is not that bad as a mouse.
I used a threshold approach, so that small vibrations of the joystick produce no effect. The code:
if(array[0] > MAXTH)
buffer[1] = delta+delayAdd;
if(array[0] <> MAXTH)
buffer[2] = -delta-delayAdd;
delayAdd is a variable that is incremented while there is movement, to create a feeling of acceleration. MAXTH & MINTH are constants chosen by experimentation. (The full code is attached to the post)
Accelerometer
I've used a similar threshold approach for the accelerometer values:
if(ax > ThAccX)
buffer[1] = delta;
if(ax < -ThAccX) buffer[1] = -delta; if(ay > ThAccY)
buffer[2] = -delta;
if(ay < -ThAccY) buffer[2] = delta;
Again ThAccX,ThAccY were chosen by experimentation.
Sending the Data
if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || dirty > 0)
{
dirty--;
HIDTxReport(buffer,3);
}
If there is data to send or release events, send. Simple.
So that's it. This post covers the main ideas behind the code inside the mouse.
The full MPLAB workspace is here.