© Warren Gay 2018
Warren GayAdvanced Raspberry Pihttps://doi.org/10.1007/978-1-4842-3948-3_23

23. Nunchuk-Mouse

Warren Gay1 
(1)
St. Catharine’s, Ontario, Canada
 

You may not have a practical use for the Nintendo Wii Nunchuk as a Raspberry Pi mouse, but it serves as a good example of how LCD touchscreens supply their input events.

The Nunchuk has two buttons: an X-Y joystick; and an X, Y, and Z accelerometer. The sensor data is communicated through the I2C bus. This will give us a practice with the I2C C API as well. Let’s have some fun implementing a Nunchuk pointing device for the X Window system desktop.

Project Overview

The challenge before us breaks down into two overall categories:
  • The I2C data communication of the Nunchuk device

  • Inserting the sensed data into the X Window system desktop event queue

Let’s first examine I2C from a Linux API perspective and then complete the chapter inserting received events into the X Window system.

Nunchuk Features

The basic physical and data characteristics of the Nunchuk are listed in Table 23-1.
Table 23-1

Nunchuk Controls and Data Characteristics

User-Interface Features

Bits

Data

Hardware/Chip

C Button

1

Boolean

Membrane switch

Z button

1

Boolean

Membrane switch

X-Y joystick

8x2

Integers

30 kohm potentiometers

X, Y, and Z accelerometer

10x3

Integers

ST LIS3L02 series

For application as a mouse, the C and Z buttons fill in for the left and right mouse buttons. The joystick is used to position the mouse cursor. While the Nunchuk normally operates at a clock rate of 400 kHz, it works just fine at the 100 kHz I2C rate.

Connector Pin-Out

There are four wires: two of which are power and ground (some units may have two additional wires, one that connects to the shield, and the other to the unused center pin). The remaining two wires are used for I2C communication (SDA and SCL). The connections looking into the cable-end connector are shown in Table 23-2 .
Table 23-2

Nuncheck Cable Connections

SCL

Notch

Gnd

+3.3 V

N/C

SDA

The Nunchuk connector is annoyingly nonstandard. Some folks have rolled their own adapters using a double-sided PCB to mate with the inner connections. Others have purchased adapters from eBay. Cheap Nunchuk clones may also be found on eBay. With the growing number of clone adapters becoming available at more-competitive prices, there is less reason to cut off the clone’s connector.

Tip

Beware of Nunchuk forgeries.

If you do cut off the connector, you will quickly discover that there is no standard wire color scheme. The only thing you can count on is that the pins are laid out as in Table 23-2. If you have a genuine Wii Nunchuk, the listed wire colors in Table 23-3 might be valid. The column labeled Clone Wire lists the wire colors of my own clone’s wires. Yours will likely differ.
Table 23-3

 Nunchuk Connector Wiring

Pin

Wii Wire

CloneWire

Description

P1

Gnd

White

White

Ground

P1-25

SDA

Green

Blue

Data

P1-03

+3.3 V

Red

Red

Power

P1-01

SCL

Yellow

Green

Clock

P1-05

Clone wire colors vary!

Before you cut that connector off that clone, consider that you’ll need to trace the connector to a wire color. Cut the cable, leaving about 3 inches of wire for the connector. Then you can cut the insulation off and trace the pins to a wire by using an ohmmeter (or by looking inside the cable-end connector).

Figure 23-1 shows the author’s clone Nunchuk with the connector cut off. In place of the connector, solid wire ends were soldered on and a piece of heat shrink applied over the solder joint. The solid wire ends are perfect for plugging into a prototyping breadboard.
../images/326071_2_En_23_Chapter/326071_2_En_23_Fig1_HTML.png
Figure 23-1

Nunchuk clone with wire ends soldered on

Enable I2C

You’ll need to enable your I2C support. Enter the Raspberry Pi Configuration panel and turn on I2C (Figure 23-2). Then reboot to make it take effect.
../images/326071_2_En_23_Chapter/326071_2_En_23_Fig2_HTML.jpg
Figure 23-2

Enabling I2C support in the Raspberry Pi Configuration panel

Testing the Connection

Plug in the I2C connection to the Pi and probe it with the i2cdetect command.
$ 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: -- -- 52 -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
$

If the Nunchuk is working, it will show up in this display at hexadecimal address 52. With the hardware verified, it is time to move on to the software.

Nunchuk I2C Protocol

The Nunchuk contains a quirky little controller that communicates through the I2C bus. In order to know where to store bytes written to it, the first byte written must be an 8-bit register address. In other words, each write to the Nunchuk requires the following:
  • One register address byte, followed by

  • Zero or more data bytes to be written to sequential locations

Thus for write operations, the first byte sent to the Nunchuk tells it where to begin. Any following write bytes received are written with the register address incremented.

Tip

Don’t confuse the register address with the Nunchuk’s I2C address of 0x52.

It is also possible to write the register address and then read bytes instead. This procedure specifies the starting location of data bytes to be read.

The quirky aspect of the Nunchuk controller is that there must be a short delay between writing the register address and reading the data. Performing the write followed by an immediate read does not work. Writing data immediately after the register address does succeed, however.

Encryption

The Nunchuk is designed to provide an encrypted link. However, that can be disabled by initializing it a certain way. The defeat procedure is as follows:
  1. 1.

    Write 0x55 to Nunchuk register location 0xF0.

     
  2. 2.

    Pause.

     
  3. 3.

    Write 0x00 to Nunchuk register location 0xFB.

     
The following illustrates the message sequence involved. Notice that this is performed as two separate I2C write operations:

Write

Pause

Write

F0

55

-

FB

00

Once this is successfully performed, all future data is returned unencrypted.

Read Sensor Data

The whole point of the Nunchuk is to read its sensor data. When requested, it returns six bytes of data formatted as shown in Table 23-4.
Table 23-4

Nunchuk Data

Byte

Bits

Description

1

 

Analog stick x-axis value

2

 

Analog stick y-axis value

3

 

X acceleration bits 9:2

4

 

Y acceleration bits 9:2

5

 

Z acceleration bits 9:2

6

0

Z button pressed (active low)

1

C button pressed (active low)

3:2

X acceleration bits 1:0

5:4

Y acceleration bits 1:0

7:6

Z acceleration bits 1:0

Some of the data is split over multiple bytes. For example, the X acceleration bits 9:2 are obtained from byte 3. The lowest 2 bits are found in byte 6, in bits 3 and 2. These together form the 9-bit X acceleration value.

To retrieve this data, we are always required to tell the Nunchuk where to begin. So the sequence always begins with a write of offset 0x00 followed by a pause:

Write

Pause

Read 6 bytes

00

-

01

02

03

04

05

06

The Nunchuk doesn’t allow us to do this in one ioctl(2) call, as two I/O messages. A write of zero must be followed by a pause. Then the six data bytes can be read as a separate I2C read operation. If the pause is too long, however, the Nunchuk controller times out, resulting in incorrect data being returned. So we must do things the Nunchuk way.

Linux uinput Interface

While reading the Nunchuk is fun, we need to apply it to our desktop as a mouse. We need to insert mouse events based on what we read from it.

The Linux uinput driver allows programmers to develop nonstandard input drivers so that events can be injected into the input stream. This approach allows new input streams to be added without changing application code (like touchscreen input).

Documentation for the uinput API can be found at the following site:
Another source of information is the device driver source code itself:
drivers/input/misc/uinput.c

The example program provided in this chapter can help pull all the necessary details together.

Working with Header Files

The header files required for the uinput API include the following:
#include <sys/ioctl.h>
#include <linux/input.h>
#include <linux/uinput.h>
To compile code, making use of I2C, you also need to install the libi2c development library, if you have not done so already:
$ sudo apt-get install libi2c-dev

Opening the Device Node

The connection to the uinput device driver is made by opening the device node:
/dev/uinput
The following is an example of the required open(2) call:
int fd;
fd = open("/dev/uinput",O_WRONLY|O_NONBLOCK);
if ( fd < 0 ) {
    perror("Opening /dev/uinput");
    ...

Configuring Events

In order to inject events, the driver must be configured to accept them. Each call to ioctl(2) in the following code enables one class of events based on the argument event. The following is a generalized example:
int rc;
unsigned long event = EV_KEY;
rc = ioctl(fd,UI_SET_EVBIT,event);
assert(!rc);
The list of UI_SET_EVBIT event types is provided in Table 23-5 . The most commonly needed event types are EV_SYN, EV_KEY, and EV_REL (or EV_ABS).
Table 23-5

List of uinput Event Types

From Header File input.h

Macro

Description

EV_SYN

Event synchronization/separation

EV_KEY

Key/button state changes

EV_REL

Relative axis mouse-like changes

EV_ABS

Absolute axis mouse-like changes

EV_MSC

Miscellaneous events

EV_SW

Binary (switch) state changes

EV_LED

LED on/off changes

EV_SND

Output to sound devices

EV_REP

For use with autorepeating devices

EV_FF

Force feedback commands to input device

EV_PWR

Power button/switch event

EV_FF_STATUS

Receive force feedback device status

Caution

Do not or (|) the event types together. The device driver expects each event type to be registered separately .

Configure EV_KEY

Once you have registered your intention to provide EV_KEY events, you need to register all key codes that might be used. While this seems a nuisance, it does guard against garbage being injected by an errant program. The following code registers its intention to inject an Escape key code:
int rc;
rc = ioctl(fd,UI_SET_KEYBIT,KEY_ESC);
assert(!rc);
To configure all possible keys, a loop can be used. But do not register key code 0 (KEY_RESERVED) nor 255; the include file indicates that code 255 is reserved for the special needs of the AT keyboard driver.
int rc;
unsigned long key;
for ( key=1; key<255; ++key ) {
    rc = ioctl(fd,UI_SET_KEYBIT,key);
    assert(!rc);
}

Mouse Buttons

In addition to key codes, the same ioctl(2,UI_SET_KEYBIT) call is used to register mouse, joystick, and other button events. This includes touch events from trackpads, tablets, and touchscreens. The long list of button codes is defined in header file linux/input.h. The usual suspects are shown in Table 23-6 .
Table 23-6

Key Event Macros

Macro

Synonym

Description

BTN_LEFT

BTN_MOUSE

Left mouse button

BTN_RIGHT

 

Right mouse button

BTN_MIDDLE

 

Middle mouse button

BTN_SIDE

 

Side mouse button

The following example shows the application’s intention to inject left and right mouse button events:
int rc;
rc=ioctl(fd,UI_SET_KEYBIT,BTN_LEFT);
assert(!rc);
rc = ioctl(fd,UI_SET_KEYBIT,BTN_RIGHT);
assert(!rc);

Configure EV_REL

In order to inject EV_REL events, the types of relative movements must be registered in advance. The complete list of valid argument codes is shown in Table 23-7. The following example indicates an intention to inject x and y relative axis movements:
rc = ioctl(fd,UI_SET_RELBIT,REL_X);
assert(!rc);
rc = ioctl(fd,UI_SET_RELBIT,REL_Y);
assert(!rc);
Table 23-7

 UI_SET_RELBIT Options

Macro

Intention

REL_X

Send relative X changes

REL_Y

Send relative Y changes

REL_Z

Send relative Z changes

REL_RX

x-axis tilt

REL_RY

y-axis tilt

REL_RZ

z-axis tilt

REL_HWHEEL

Horizontal wheel change

REL_DIAL

Dial-turn change

REL_WHEEL

Wheel change

REL_MISC

Miscellaneous

Configure EV_ABS

While this project doesn’t use the EV_ABS option, it may be useful to know about this feature. This event represents absolute cursor movements and it too requires registration of intentions. The complete list of EV_ABS codes is defined in linux/input.h. The usual suspects are defined in Table 23-8.
Table 23-8

 Absolute Cursor Movement Event Macros

Macro

Description

ABS_X

Move X to this absolute X coordinate

ABS_Y

Move Y to this absolute Y coordinate

The following is an example of registering intent for absolute x- and y-axis events:
int rc;
rc = ioctl(fd,UI_SET_ABSBIT,ABS_X);
assert(!rc);
rc = ioctl(fd,UI_SET_ABSBIT,ABS_X);
assert(!rc);
In addition to registering your intentions to inject these events, you need to define some coordinate parameters. The following is an example:
struct uinput_user_dev uinp;
uinp.absmin[ABS_X] = 0;
uinp.absmax[ABS_X] = 1023;
uinp.absfuzz[ABS_X] = 0;
uinp.absflat[ABS_X] = 0;
uinp.absmin[ABS_Y] = 0;
uinp.absmax[ABS_Y] = 767;
uinp.absfuzz[ABS_Y] = 0;
uinp.absflat[ABS_Y] = 0;

These values must be established as part of your ioctl(2,UI_DEV_CREATE) operation, which is described next.

Creating the Node

After all registrations with the uinput device driver have been completed, the final step is to create the uinput node. This will be used by the receiving application, in order to read injected events. This involves two programming steps:
  1. 1.

    Write the struct uinput_user_dev information to the file descriptor with write(2).

     
  2. 2.

    Perform an ioctl(2,UI_DEV_CREATE) to cause the uinput node to be created.

     
The first step involves populating the following structures:
struct input_id {
    __u16       bustype;
    __u16       vendor;
    __u16       product;
    __u16       version;
};
struct uinput_user_dev {
    char        name[UINPUT_MAX_NAME_SIZE];
    struct input_id id;
    int         ff_effects_max;
    int         absmax[ABS_CNT];
    int         absmin[ABS_CNT];
    int         absfuzz[ABS_CNT];
    int         absflat[ABS_CNT];
};
An example populating these structures is provided next. If you plan to inject EV_ABS events, you must also populate the abs members, mentioned in the “Configure EV_ABSsection.
       struct uinput_user_dev uinp;
       int rc;
       memset(&uinp,0,sizeof uinp);
       strncpy(uinp.name,"nunchuk",UINPUT_MAX_NAME_SIZE);
       uinp.id.bustype = BUS_USB;
       uinp.id.vendor = 0x1;
       uinp.id.product = 0x1;
       uinp.id.version = 1;
//       uinp.absmax[ABS_X] = 1023; /∗EV_ABS only ∗/
//       ...
       rc = write(fd,&uinp,sizeof(uinp));
       assert(rc == sizeof(uinp));
The call to write(2) passes all of this important information to the uinput driver. Now all that remains is to request a device node to be created for application use:
int rc;
rc = ioctl(fd,UI_DEV_CREATE);
assert(!rc);
This step causes the uinput driver to make a device node appear in the pseudo directory /dev/input. An example is shown here:
$ ls -l /dev/input
total 0
crw-rw---- 1 root input 13, 64 Jul 26 06:11 event0
crw-rw---- 1 root input 13, 63 Jul 26 04:50 mice
crw-rw---- 1 root input 13, 32 Jul 26 06:11 mouse0

The device /dev/input/event0 was the Nunchuck’s created uinput node, when the program was run.

Posting EV_KEY Events

The following code snippet shows how to post a key down event, followed by a key up event:
 1 static void
 2 uinput_postkey(int fd,unsigned key) {
 3     struct input_event ev;
 4     int rc;
 5
 6     memset(&ev,0,sizeof(ev));
 7     ev.type = EV_KEY;
 8     ev.code = key;
 9     ev.value = 1;
10
11     rc = write(fd,&ev,sizeof(ev));
12     assert(rc == sizeof(ev));
13
14     ev.value = 0;
15     rc = write(fd,&ev,sizeof(ev));
16     assert(rc == sizeof(ev));
17 }

From this example, you see that each event is posted by writing a suitably initialized input_event structure. The example illustrates that the member named type was set to EV_KEY, code was set to the key code, and a keypress was indicated by setting the member value to 1 (line 9).

To inject a key up event, value is reset to 0 (line 14) and the structure is written again.

Mouse button events work the same way, except that you supply mouse button codes for the code member. For example:
memset(&ev,0,sizeof(ev));
ev.type = EV_KEY;
ev.code = BTN_RIGHT;        /∗Right click ∗/
ev.value = 1;

Posting EV_REL Events

To post a relative mouse movement, we populate the input_event as a type EV_REL. The member code is set to the type of event (REL_X or REL_Y in this example), with the value for the relative movement established in the member value:
static void
uinput_movement(int fd,int x,inty) {
     struct input_event ev;
     int rc;
     memset(&ev,0,sizeof(ev));
     ev.type = EV_REL;
     ev.code = REL_X;
     ev.value = x;
     rc = write(fd,&ev,sizeof(ev));
     assert(rc == sizeof(ev));
     ev.code = REL_Y;
     ev.value = y;
     rc = write(fd,&ev,sizeof(ev));
     assert (rc == sizeof(ev));
}

Notice that the REL_X and REL_Y events are created separately. What if you want the receiving application to avoid acting on these separately? The EV_SYN event helps out in this regard (next) .

Posting EV_SYN Events

The uinput driver postpones delivery of events until the EV_SYN event has been injected. The SYN_REPORT type of EV_SYN event causes the queued events to be flushed out and reported to the interested application. The following is an example:
static void
uinput_syn(int fd) {
    struct input_event ev;
    int rc;
    memset(&ev,0,sizeof(ev));
    ev.type = EV_SYN;
    ev.code = SYN_REPORT;
    ev.value = 0;
    rc = write(fd,&ev,sizeof(ev));
    assert(rc == sizeof(ev));
}

For a mouse relative movement event, for example, you can inject a REL_X and REL_Y, followed by a SYN_REPORT event to have them seen by the application as a group.

Closing uinput

There are two steps involved:
  1. 1.

    Destruction of the /dev/input/event%d node

     
  2. 2.

    Closing of the file descriptor

     
The following example shows both:
int rc;
rc = ioctl(fd,UI_DEV_DESTROY);
assert(!rc);
close(fd);

Closing the file descriptor implies the ioctl(2,UI_DEV_DESTROY) operation. The application has the option of destroying the device node while keeping the file descriptor open.

X-Window

The creation of our new uinput device node is useful only if our desktop system is listening to it. Raspbian Linux’s X-Window system needs a little configuration help to notice our Frankenstein creation. The following definition can be added to the /usr/share/X11/xorg.conf.d directory. Name the file 20-nunchuk.conf:
# Nunchuck event queue
Section "InputClass"
        Identifier "Raspberry Pi Nunchuk"
        Option "Mode" "Relative"
        MatchDevicePath "/dev/input/event0"
        Driver "evdev"
EndSection
# End 20−nunchuk.conf

This configuration change works only if your Nunchuk uinput device shows up as /dev/input/event0. If you have other specialized input device creations on your Raspberry Pi, it could well be named event1 or some other number. See the upcoming section “Testing the Nunchuk” for troubleshooting information.

Restart your X-Window server to have the configuration file noticed.

Tip

Normally, your Nunchuk program should be running already. But the X-Window server will notice it when the Nunchuk does start.

Input Utilities

When writing uinput event-based code, you will find the package input-utils to be extremely helpful. The package can be installed from the command line as follows:
$ sudo apt-get install input-utils
The following commands are installed:
  • lsinput(8): List uinput devices

  • input-events(8): Dump selected uinput events

  • input-kbd(8): Keyboard map display

This chapter uses the first two utilities: lsinput(8) and input-events(8).

Testing the Nunchuk

Now that the hardware, drivers, and software are ready, it is time to exercise the Nunchuk. Unfortunately, there is no direct way for applications to identify your created uinput node. When the Nunchuk program runs, the node may show up as /dev/input/event0 or some other numbered node if it already exists. If you wanted to start a Nunchuk driver as part of the Linux boot process, you need to create a script to edit the file with the actual device name registered. The affected X-Windows config file is as follows:
/usr/share/X11/xord.conf.d/20-nunchuk.conf
The script (shown next) determines which node the Nunchuk program created. The following is an example run, while the Nunchuk program was running:
$ ./findchuk
/dev/input/event0
When the node is not found, the findchuk script exits with a non-zero code and prints a message to stderr:
$ ./findchuk
Nunchuk uinput device not found.
$ echo $?
1
The findchuk script is shown in Listing 23-1.
#!/bin/bash
###############################################################
# Find the Nunchuck
###############################################################
#
# This script locates the Nunchuk uinput device by searching the
# /sys/devices/virtual/input pseudo directory for names of the form:
# input[0_9]∗. For all subdirectories found, check the ./name pseudo
# file, which will contain "nunchuk". Then we derive the /dev path
# from a sibling entry named event[0_9]∗. That will tell use the
# /dev/input/event%d pathname, for the Nunchuk.
DIR=/sys/devices/virtual/input  # Top level directory
set_eu
cd "$DIR"
find . −type d −name 'input[0−9]∗' | (
      set −eu
      while read dirname ; do
              cd "$DIR/$dirname"
              if [−f "name"] ; then
                     set +e
                     name=$(cat name)
                     set −e
                     if [ $(cat name) = nunchuk ] ; then
                            event="/dev/input/$ (ls−devent[0−9]∗)"
                            echo $event
                            exit 0             # Found it
                     fi
              fi
      done
      echo "Nunchuk uinput device not found." >&2
      exit 1
)
# End findchuk
Listing 23-1

The findchuk shell script 

Testing ./nunchuk

When you want to see what Nunchuk data is being received, you can add the -d command-line option:
$ ./nunchuk −d
Raw nunchuk data: [83] [83] [5C] [89] [A2] [63]
.stick_x = 0083 (131)
.stick_y = 0083 (131)
.accel_x = 0170 (368)
.accel_y = 0226 (550)
.accel_z = 0289 (649)
.z_button= 0
.c_button= 0

The first line reports the raw bytes of data that were received. The remainder of the lines report the data in its decoded form. While the raw data reports the button presses as active low, the Z and C buttons are reported as 1 in the decoded data. The value in the left column is in hexadecimal format, while the value in parentheses is shown in decimal.

Utility lsinputs

When the Nunchuk program is running, you should be able to see the Nunchuk uinput device in the list:
$ lsinput
/dev/input/event0
   bustype : BUS_USB
   vendor  : 0x1
   product : 0x1
   version : 1
   name    : "nunchuk"
   bits ev : EV_SYN EV_KEY EV_REL

In this example, the Nunchuk shows up as event0 .

Utility input-events

When developing uinput-related code, the input-events utility is a great help. Here we run it for event0 (the argument 0 on the command line), where the Nunchuk mouse device is:
$ input-events 0
/dev/input/event0
   bustype   : BUS_USB
   vendor    : 0x1
   product   : 0x1
   version   : 1
   name      : "nunchuk"
   bits ev   : EV_SYN EV_KEY EV_REL
waiting for events
23:35:15.345105: EV_KEY BTN_LEFT (0x110) pressed
23:35:15.345190: EV_SYN code=0 value=0
23:35:15.517611: EV_KEY BTN_LEFT (0x110) released
23:35:15.517713: EV_SYN code=0 value=0
23:35:15.833640: EV_KEY BTN_RIGHT (0x111) pressed
23:35:15.833727: EV_SYN code=0 value=0
23:35:16.019363: EV_KEY BTN_RIGHT (0x111) released
23:35:16.019383: EV_SYN code=0 value=0
23:35:16.564129: EV_REL REL_X −1
23:35:16.564213: EV_REL REL_Y 1
23:35:16.564261: EV_SYN code=0 value=0
...

The Program

Some of the interesting aspects of the I2C programming are presented as an example for I2C device programming in C. The project directory is:
$ cd ~/RPi/nunchuk

The source module that will be presented is named nunchuck.c.

To gain access to the I2C bus, we must first open it as it is done in Listing 23-2. Line 44 opens the bus, identified by /dev/i2s-1. This will fail if the I2C bus has not been enabled in the Raspberry Pi control panel.
0039: static void
0040: i2c_init(const char *node) {
0041:  unsigned long i2c_funcs = 0;    /* Support flags */
0042:  int rc;
0043:
0044:  i2c_fd = open(node,O_RDWR);   /* Open driver /dev/i2s-1 */
0045:  if ( i2c_fd < 0 ) {
0046:        perror("Opening /dev/i2s-1");
0047:        puts("Check that I2C has been enabled in the "
                  "control panel ");
0048:        abort();
0049:  }
0050:
0051:  /*
0052:   * Make sure the driver supports plain I2C I/O:
0053:   */
0054:  rc = ioctl(i2c_fd,I2C_FUNCS,&i2c_funcs);
0055:  assert(rc >= 0);
0056:  assert(i2c_funcs & I2C_FUNC_I2C);
0057: }
Listing 23-2

Opening the I2C bus in nunchuk.c

Once the bus has been opened successfully, the ioctl(2) call in line 54 returns the functional support available. The assert macro in line 56 tests that normal I2C functions are available using the macro I2C_FUNC_I2C. If no bits remain true after applying the macro, the assertion will abort the program.

The function nunchuk_init() is used to initialize the nunchuk and defeat the encryption for it (Listing 23-3) .
0062: static void
0063: nunchuk_init(void) {
0064:  static char init_msg1[] = { 0xF0, 0x55 };
0065:  static char init_msg2[] = { 0xFB, 0x00 };
0066:  struct i2c_rdwr_ioctl_data msgset;
0067:  struct i2c_msg iomsgs[1];
0068:  int rc;
0069:
0070:  iomsgs[0].addr = 0x52;          /* Address of Nunchuk */
0071:  iomsgs[0].flags = 0;            /* Write */
0072:  iomsgs[0].buf = init_msg1;      /* Nunchuk 2 byte sequence */
0073:  iomsgs[0].len = 2;              /* 2 bytes */
0074:
0075:  msgset.msgs = iomsgs;
0076:  msgset.nmsgs = 1;
0077:
0078:  rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
0079:  assert(rc == 1);
0080:
0081:  timed_wait(0,200,0);            /* Nunchuk needs time */
0082:
0083:  iomsgs[0].addr = 0x52;          /* Address of Nunchuk */
0084:  iomsgs[0].flags = 0;            /* Write */
0085:  iomsgs[0].buf = init_msg2;      /* Nunchuk 2 byte sequence */
0086:  iomsgs[0].len = 2;              /* 2 bytes */
0087:
0088:  msgset.msgs = iomsgs;
0089:  msgset.nmsgs = 1;
0090:
0091:  rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
0092:  assert(rc == 1);
0093: }
Listing 23-3

Initializing the Nunchuk in nunchuk.c

Lines 70 to 76 initialize the structures to send the initial message to the nunchuk. Lines 78 and 79 communicate this information to the device. Line 81 provides the necessary pause for the nunchuk controller. Lines 83 to 92 send the followup message to defeat the encryption.

The last code listing presented is the code used to read the nunchuk (Listing 23-4). Line 106 is a delay to prevent multiple reads from overwhelming the I2C device.
0098: static int
0099: nunchuk_read(nunchuk_t *data) {
0100:   struct i2c_rdwr_ioctl_data msgset;
0101:   struct i2c_msg iomsgs[1];
0102:   char zero[1] = { 0x00 };        /* Written byte */
0103:   unsigned t;
0104:   int rc;
0105:
0106:   timed_wait(0,15000,0);
0107:
0108:   /*
0109:    * Write the nunchuk register address of 0x00 :
0110:    */
0111:   iomsgs[0].addr = 0x52;          /* Nunchuk address */
0112:   iomsgs[0].flags = 0;            /* Write */
0113:   iomsgs[0].buf = zero;           /* Sending buf */
0114:   iomsgs[0].len = 1;              /* 6 bytes */
0115:
0116:   msgset.msgs = iomsgs;
0117:   msgset.nmsgs = 1;
0118:
0119:   rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
0120:   if ( rc < 0 )
0121:           return -1;              /* I/O error */
0122:
0123:   timed_wait(0,200,0);            /* Zzzz, nunchuk needs time */
0124:
0125:   /*
0126:    * Read 6 bytes starting at 0x00 :
0127:    */
0128:   iomsgs[0].addr = 0x52;                  /* Nunchuk address */
0129:   iomsgs[0].flags = I2C_M_RD;             /* Read */
0130:   iomsgs[0].buf = (char *)data->raw;      /* Receive raw bytes here */
0131:   iomsgs[0].len = 6;                      /* 6 bytes */
0132:
0133:   msgset.msgs = iomsgs;
0134:   msgset.nmsgs = 1;
0135:
0136:   rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
0137:   if ( rc < 0 )
0138:           return -1;                      /* Failed */
0139:
0140:   data->stick_x = data->raw[0];
0141:   data->stick_y = data->raw[1];
0142:   data->accel_x = data->raw[2] << 2;
0143:   data->accel_y = data->raw[3] << 2;
0144:   data->accel_z = data->raw[4] << 2;
0145:
0146:   t = data->raw[5];
0147:   data->z_button = t & 1 ? 0 : 1;
0148:   data->c_button = t & 2 ? 0 : 1;
0149:   t >>= 2;
0150:   data->accel_x |= t & 3;
0151:   t >>= 2;
0152:   data->accel_y |= t & 3;
0153:   t >>= 2;
0154:   data->accel_z |= t & 3;
0155:   return 0;
0156: }
Listing 23-4

The nunchuk_read() function in nunchuk.c

Lines 111 to 117 prepare a message to tell the nunchuk that we want to read it starting from register zero for six bytes. The ioctl(2) call in line 199 initiates it. Again, time is given to the quirky nunchuk controller in line 123. After that, it is safe to issue the read command of six bytes (lines 128 to 136) .

The remaining lines 140 to 155 extract the relevant information out of the nunchuk register information returned.

Summary

This chapter introduced the uinput mechanism for adding devices to the Raspberry Pi’s graphical desktop. Additionally, it served to provide a working example of an I2C C program. With the programming and input utilities provided, you are on your way to build custom interfaces of your own creation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.93.44