Close
0%
0%

Wii Nunchuk as a USB HID controller

Project in which Wii Nunchuk controller is used as a USB HID controller for computer graphics apps.

Public Chat
Similar projects worth following
I'm always interested in experimenting with human-computer interaction. That means I sometimes build controllers. This time I've decided to take the Wii Nunchuk controller and use it as a one-hand controller for apps like Photoshop, Blender etc.

Some constraints:
- cheap STM32 micro-controller needs to handle the controller USB firmware
- libopencm3 will be the framework for the USB firmware
- the host driver has to use minimal (ideally none) amount of libraries
- for host OS - at minimum Windows needs to be supported

At the time of posting this project here, at Hackaday, everything works. I'll document the process here in reasonably sized steps. Also at some point I will share the code.

The motivation for this project comes from the irritation of using keyboard when creating art. Usually I use a drawing tablet's pen in one hand and keep the other hand on the keyboard. It's necessary because working without hotkeys would be excruciatingly slow. Keyboard is a universal tool. Good for a lot of things. Perfect for typing... nothing else though.

Keyboard layout isn't optimized for tasks like creating art. Often times CG programs use mnemonics to help you, i.e. in Photoshop the Brush hotkey is B, while Eraser is E. This logic works well if your goal is to learn the software hotkeys. It isn't ergonomical though. Usually you use way more hotkeys. That means you jump all over the keyboard. Some of the hotkeys are combinations, like CTRL + S. It adds a lot of friction when you work. Everyone gets used to it but I it never gets comfortable.

That's why I'm experimenting with things like that. The amount of functions/tools you use in those programs is limited. 90% of your work you switch between those again and again. Those tools should be easily available and give you control appropriate for the task (some tools are more analog in nature).

I think the Wii Nunchuk is a nice base for experimentation.

  • 1 × Blue pill board with STM32F103C8 micro controller It's cheap, has some flaws (like wrong resistors for the USB pins) but rocks a USB periphery.
  • 1 × Wii Nunchuk controller Also cheap if it's a knock-off - those are very easy to get.

  • The USB dance

    Michal02/12/2019 at 18:55 0 comments

    In this log I wanted to describe my experience with implementing the USB firmware. Unfortunately I'm not writing this log as I go. Most of the things I have to recall from memory, so I might omit some struggles I went through.

    The biggest problem with implementing reliable USB device firmware came from combining both I2C communication with USB functionality. Let me explain my initial idea.

    I wanted the main loop to look like so:

    main
    {
      init_i2c();
      init_usb();
      systick_ON();
    
      while
      {
        data = i2c_read();
        usb_poll();
      }
    }
    
    SysTick
    {
      usb_write_packet(data);
    }

    Before we go any further: 

    • usb_poll() checks if anything has been received and if so it decides if it should handle it as a SETUP (host configures the device) or a OUT situation (device gets asked for the controller readings).
    • usb_write_packet() pushes data to the buffer that corresponds to the specific endpoints OUT buffer. This micro controller supports up to 16 endpoints and each of those has an IN and OUT buffer. That means that you can't overwrite data in IN buffer by pushing to the OUT buffer with usb_write_packet() - seemed important when I tried to debug what's not working.
    • init_i2c() in this situation is setting the I2C peripheral, configures the Wii Nunchuk and reads it's ID to confirm everything is fine.

    This resulted in the OS failing to enumerate device. After some experimentation it was clear that running a while loop in which I2C polled the controller was not working well with the SysTick exception.

    The next iteration looked like so:

    main
    {
      init_i2c();
      init_usb();
      systick_ON();
    
      while
      {
        usb_poll();
      }
    }
    
    SysTick
    {
      data = i2c_read();
      usb_write_packet(data);
    }

    The problem with enumeration persisted. What actually gave me a reliable results was something as follows:

    control_request_callback ()
    {
      systick_ON();
    }
    
    main
    {
      init_i2c_peripheral_only();
      init_usb();
    
      while
      {
        usb_poll();
      }
    }
    
    SysTick
    {
      if (controller == UNITILIZED)
      {
        i2c_configure_controller();
        controller = INITILIZED;
      }
      else if (controller == INITILIZED)
      {
        i2c_read_controller_ID();
        controller = PRESENT;
      }
      else if (controller == PRESENT)
      {
        i2c_read();
      }
    
      usb_write_packet();
    }

     That basically meant that first 2 SysTicks are used for setting up the controller. In the previous code snippets I've omitted the control_request_callback() function. That function handles SETUP requests sent by the host. Before the host sends this type of request only usb_poll() function runs - it's necessary to actually recognise that the SETUP packet has been received.

    That honestly became contrived and I'm not a fan of this solution. Unfortunately not having any logic analyser I could only reason on the timings. I would like to revisit this loop when I can my hands back on some proper equipment.

    Another thing I wanted to mention is some descriptors configuration. USB has a multitude of descriptors which are used for describing the device. The top level descriptor is called the Device Descriptor. What seems to be important is that by setting bDeviceClass, bDeviceSubClass and bDeviceProtocol fields to 0 the responsibility of holding the data the identifies the device lies on the Interface Descriptor. 

    What's also important is the idVendor (VID) and idProduct (PID) numbers. At some point I've decided that my device will pretend it's a game pad. What it pretends to be isn't that important at this stage. My understanding was that for a HID device that Windows doesn't have drivers it will use generic HID driver. I've opened the Linux list of known VIDs and PIDs. I've chosen 0x0079 for the VID and 0x0011 for the PID. Why? Because DragonRise Inc. Gamepad sounds awesome. I've also hoped that Windows will be too cool to know what to do with something that was made by such a cringy company.

    One thing that is specific for the HID device is an additional descriptor, so called HID Descriptor. Its role is to be a wrapper for the HID Report - a description of what the actual data that gets polled means....

    Read more »

  • First steps with STM32 USB firmware

    Michal02/10/2019 at 15:59 0 comments

    I started by setting up the project structure in a similar way satoshinm did. As I've mentioned before, this article by him - making a Rubber Ducky with Blue Pill - helped me a bit.

    The project references the libopencm3 repository and provides 2 Makefiles. Top level Makefile fetches the libopencm3, builds it and builds the firmware. Makefile in the src directory only builds the firmware binary.

    Before I could actually flash the boards I decided to check the resistors on the USB lines. The Blue Pill boards suffer from a wrong resistor values. The boards I received used 4.7k resistors, while they should be 1.5k. I desoldered them and soldered in ones with the proper values (1.5k).

    I started by copying the code from this example. I stripped it from the DFU functionality.

    I also changed the clock source to external 8MHz. In libopencm3 that meant going from:

    rcc_clock_setup_in_hsi_out_48mhz()

    to

    rcc_clock_setup_in_hse_8mhz_out_72mhz()

    What puzzled me a bit in libopencm3 is that you can stumble on functions that seem to do the same yet are named differently. An example of that would be:

    gpio_mode_setup()

    and

    gpio_set_mode()
    

    If you look into this file though you can find the answer:

    /*
     * Note: The F2 and F4 series have a completely new GPIO peripheral with
     * different configuration options. Here we implement a different API partly to
     * more closely match the peripheral capabilities and also to deliberately
     * break compatibility with old F1 code so there is no confusion with similar
     * sounding functions that have very different functionality.
     */ 

     ...oh, ok then. That meant that for a STM32F1 the gpio_set_mode() was the way to go.

    After that I worked on setting SysTick in such a way that it fired 100 times per second.

      systick_set_clocksource(STK_CSR_CLKSOURCE_AHB_DIV8);
      // SysTick interrupt every N clock pulses: set reload to N-1
      // 72MHz / 8 = 9MHz
      // 9000000Hz / 90000 = 100Hz
      systick_set_reload(89999);
      systick_interrupt_enable();
    

     After plugging the board to the PC it did get recognised. I didn't went too deep into what happens when I plug the board in. I went on to add the I2C functionality so I could poll the Wii Nunchuk controller. Little did I know...

    But that's a story for the next episode! Stay tuned!

  • Gathering information

    Michal02/06/2019 at 17:35 0 comments

    I've started by ordering 5 Blue pill boards... they are super cheap but it takes awhile for them to get here (I ordered them through AliExpress). In the meantime I started research.

    There are 3 topics that I had to go through:

    • USB specification and how it actually works
    • OSes USB devices handling
    • USB device firmware

    I have started by going through the USB specification... It really is a confusing mess. I can't say I would design a universal standard better. It's not an easy task. I can't say USB is well designed though. I've played around with connecting Playstation 2 game pad and sniffing the USB packets with WireShark. I've also went through libopencm3's USB examples. Combining those different sources of information gave me some idea about USB, but it also made me realize there is way more than what HID USB device can teach you about USB.

    When it comes to the firmware I've already decided I want to use libopencm3 library and that the Windows has to be first OS that handles this device. Depending on how it goes I might tackle Linux in the future. Except the libopencm3's examples, the Pill duck project gave me a bit of insight of how firmware like that could look like.

    I admire the Handmade Network started by Casey Muratori and his Handmade Hero project. I share the attitude of avoiding third party libraries. I do belive it's possible to do a lot of low level things on your own. Because of that I wanted to write a legit USB device driver. That means I wanted to use direct OS calls.... but...

    I researched the topic of writing a USB device driver for Windows and once again... it's a mess. There are multiple ways of doing it and I'm still not sure where are the boundaries between those. After some experimentation i've decided to go with the hidapi library. There are multiple reasons why I ended up using that one, and I will explain it better in next posts.

View all 3 project logs

Enjoy this project?

Share

Discussions

Dan Maloney wrote 02/05/2019 at 19:21 point

Interesting - do you picture this being a replacement for a digitizing tablet?

  Are you sure? yes | no

Michal wrote 02/06/2019 at 16:51 point

You mean drawing tablet like Wacom tablets? Usually when you use a drawing tablet one hand uses the pen and the other one is on the keyboard. This thing is supposed to replace the keyboard.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates