Nunchuck Controller

Discuss development of drivers for external hardware and components, such as LCD screens, sensors, motor drivers, etc.
Target audience: Users and developers of drivers.
Post Reply
User avatar
kfricke
Posts: 342
Joined: Mon May 05, 2014 9:13 am
Location: Germany

Nunchuck Controller

Post by kfricke » Thu Dec 04, 2014 11:11 pm

Not a big deal, but i did manage to get the Wii Nunchuck controller running at the pyboard. It uses simple I2C for communication and a little bit-shifting to decode the various sensor readouts (2-axis joystick, 3-axis accelerometer and two buttons).

From the various examples (mostly from the Arduino world) and documentations read, i still have a few questions:
  • Which initialization sequence do you need with your controller? (one is working for me and the other one commented out in my example)
  • Which I2C bus speed can you use your nunchuk with? Other sources say 100kHz is "standard", i can use 200kHz. But what is a stable and reasonable maximum value for this? If possible I would like to use it at maximum rate with other I2C devices connected at the same time (e.g. SSD1306 based OLED display).
  • What use for are those decoding examples in the other libraries (readout = (byte ^ 0x17) + 0x17)? I still get reasonable readouts without "fixing" my sensor readings this way.
  • Maybe i am dumb, but how does one interpret accelerometer sensor readouts? Even with the pyboard accelerometer i do read "angels on the axis" instead of acceleration on those axis as i would assume for an accelerometer sensor
Of course i can place it on Github, but this was a quicker way...

Code: Select all

import pyb

class Nunchuck(object):
    """The Nunchuk object presents the sensor readings in a polling way. 
    Based on the fact that the controller does communicate using I2C we 
    cannot make it push sensor changes by using interrupts or similar 
    facilities. Instead a polling mechanism is implemented, which updates 
    the sensor readings based on "intent-driven" regular updates.

    If the "polling" way of updating the sensor readings is not sufficient
    a timer interrupt could be used to poll the controller. The 
    corresponding update() method does not allocate memory and thereby 
    could directly be used as an callback method for a timer interrupt."""
    
    def __init__(self, i2c_bus, poll=True):
        """Initialize the Nunchuk controller. If no polling is desired it 
        can be disabled. Only one controller is possible on one I2C bus, 
        because the address of the controller is fixed.
        The maximum stable I2C bus rate is 100kHz (200kHz seem to work on 
        mine as well, 400kHz does not)."""
        self.i2c = i2c_bus
        self.address = 0x52
        self.buffer = bytearray(b'\x00\x00\x00\x00\x00\x00')
        # There are two initialization sequences documented.
        #self.i2c.send(b'\x40\x00', self.address)
        #self.i2c.send(b'\x00', self.address)
        self.i2c.send(b'\xf0\x55', self.address)
        self.i2c.send(b'\xfb\x00', self.address)
        self.last_poll = 0
        if poll:
            # Only poll every 50 milliseconds
            self.polling_threshold = 50
        else:
            # If no polling is desired we disable it completely
            self.polling_threshold = -1
    
    def __send(self, bytes):
        """Sends bytes to the controller."""
        if self.i2c.is_ready(self.address):
            self.i2c.send(bytes, self.address)
        else:
            raise Exception("Could not send data to Nunchuck at address %s" % str(hex(self.address)))

    def update(self):
        """Requests a sensor readout from the controller and receives the 
        six data bits afterwards."""
        if self.i2c.is_ready(self.address):
            self.__send(0x00)
            self.i2c.recv(self.buffer, self.address)
        else:
            raise Exception("Could not receive data from Nunchuck at address %s" % str(hex(self.address)))
            
    def __poll(self):
        """Poll the sensor readouts if necessary."""
        if pyb.elapsed_millis(self.last_poll) > self.polling_threshold:
            self.update()
        
    def buttons(self):
        """Returns a tuple representing the states of the button C and Z 
        (in this order). The values are True if the corresponding button 
        is being pressed and False otherwise."""
        self.__poll()
        return (not (self.buffer[5] & 2 == 2), not (self.buffer[5] & 1 == 1))
    
    def joystick(self):
        """Get the X and Y positions of the thumb joystick. For both axis 
        a value of 0 means left, ~136 center and 255 right position."""
        self.__poll()
        return (self.buffer[0], self.buffer[1])
        
    def joystick_left(self):
        """Returns True if the joystick is being held to the left side."""
        self.__poll()
        return self.buffer[0] < 55
    
    def joystick_right(self):
        """Returns True if the joystick is being held to the right side."""
        self.__poll()
        return self.buffer[0] > 200
    
    def joystick_up(self):
        """Returns True if the joystick is being held to the upper side."""
        self.__poll()
        return self.buffer[1] > 200
    
    def joystick_down(self):
        """Returns True if the joystick is being held to the lower side."""
        self.__poll()
        return self.buffer[1] < 55
        
    def joystick_center(self):
        """Returns True if the joystick is not moved away fromthe center 
        position."""
        self.__poll()
        return self.buffer[0] > 100 and self.buffer[0] < 155 and self.buffer[1] > 100 and self.buffer[1] < 155
        
    def accel(self):
        """Retrieve the three axis of the last accelerometer reading. 
        Returns a tuple of three elements: The x, y and z axis"""
        self.__poll()
        return ((self.buffer[2] << 2) + ((self.buffer[5] << 4) >> 6),
            (self.buffer[3] << 2) + ((self.buffer[5] << 2 ) >> 6),
            (self.buffer[4] << 2) + (self.buffer[5] >> 6))
    
def decode(byte):
    return (byte ^ 0x17) + 0x17
    
def test():
    nun = Nunchuck(pyb.I2C(2, mode=pyb.I2C.MASTER, baudrate=100000))
    x = 0
    y = 0
    while True:
        if not nun.joystick_center():
            if nun.joystick_up():
                y += 1
            elif nun.joystick_down():
                y -= 1
            if nun.joystick_left():
                x -= 1
            elif nun.joystick_right():
                x += 1
        print("Joy-X: %3d Joy-Y: %3d Accel-X: %3d Accel-Y: %3d Accel-Z: %3d C: %s Z: %s" % (nun.joystick()[0], nun.joystick()[1], nun.accel()[0], nun.accel()[1], nun.accel()[2], nun.buttons()[0], nun.buttons()[1]))
        print(x, y)
        pyb.delay(100)
Plug a Nunchuk controller using those widely available adapter boards onto one I2C bus of your pyboard (i do use bus #2 on port Y9 and Y10). After copying the code into the file "nunchuck.py" on your pyboard you can start the example with the following commands in your REPL:

Code: Select all

Micro Python v1.3.7-1-gd96e6b1 on 2014-11-29; PYBv1.0 with STM32F405RG
Type "help()" for more information.
>>> import nunchuck
>>> nunchuck.test()
...
(enjoy until you press Ctrl-C)

User avatar
kfricke
Posts: 342
Joined: Mon May 05, 2014 9:13 am
Location: Germany

Re: Nunchuck Controller

Post by kfricke » Fri Dec 05, 2014 10:17 am

For completeness... i am using this Nunchuck adapter and simple transition from the pyboard headers to the UEXT connector, which i am a big fan of.

Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

Re: Nunchuck Controller

Post by Turbinenreiter » Fri Dec 05, 2014 11:49 am

Will try this later, I also tried hacking in the Nunchuck, but it behaved super-weired. When I connect it, on the first i2c.scan(), it would show [1,2,3,4,5,6], on the second scan it would come up empty.

I hope I didn't fry it, at first I had connectet the power lines wrong (GND-3V3, PWR-VIN).

User avatar
kfricke
Posts: 342
Joined: Mon May 05, 2014 9:13 am
Location: Germany

Re: Nunchuck Controller

Post by kfricke » Mon Nov 21, 2016 10:28 pm

Now on Github and ported to MicroPython 1.8.6 (current machine API):
https://github.com/kfricke/micropython-nunchuck

(yet only tested on the esp8266 port, but will check the others soon(tm))

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: Nunchuck Controller

Post by deshipu » Wed Nov 23, 2016 12:22 pm

The thing with angles on the axis is that if you leave anywhere nearby, you will have a constant acceleration of about 1g pointing downwards (also known as gravity). Since it's impossible to tell acceleration from gravity (Einstein mentioned that they are the same thing, actually), you are basically getting two measurements, merged together: where the gravity vector is pointing (this is basically the current position of the accelerometer, in angles), and how the speed is changing (this is the angle of the vector of change). You can separate them somewhat by using low-pass and high-pass filter, or something more elaborate, like Kalman filtering.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: Nunchuck Controller

Post by deshipu » Wed Nov 23, 2016 12:23 pm

As for the speed, it should work at any reasonable speed, since the slaves will do clock stretching when the transmission is too fast for them. Since the software I2C for ESP8266 has now been fixed to support clock stretching too, you shouldn't have problems.

User avatar
mcauser
Posts: 507
Joined: Mon Jun 15, 2015 8:03 am

Re: Nunchuck Controller

Post by mcauser » Wed Dec 14, 2016 12:54 pm

I tried this library on my WeMos D1 Mini and received nothing but error: OSError: [Errno 19] ENODEV

Added a time.sleep_us(10) between i2c.writeto() and i2c.readfrom_into() and all my problems went away.

Details here: https://github.com/kfricke/micropython- ... k/issues/1

User avatar
mcauser
Posts: 507
Joined: Mon Jun 15, 2015 8:03 am

Re: Nunchuck Controller

Post by mcauser » Wed Dec 14, 2016 1:23 pm

btw, the official Nintendo spelling is "Nunchuk", instead of "Nunchuck" :ugeek:
Source: https://en.wikipedia.org/wiki/Wii_Remote

User avatar
kfricke
Posts: 342
Joined: Mon May 05, 2014 9:13 am
Location: Germany

Re: Nunchuck Controller

Post by kfricke » Wed Dec 14, 2016 1:33 pm

So they can not claim intellectual property or trademarks infringement from us :lol:

Post Reply