puppeteerPro ECE 4180 Project

By: Arjun Chib and Matthew Hannah For: ECE 4180

Meet the puppeteerPro Bluetooth controller!

/media/uploads/mrhannah/img_1989.jpg

Demo

Project Goals

  1. to create a Bluetooth game controller that would be natively recognized by an OS
  2. to be able to (somewhat) play a game with it

Parts List

Block Diagram

/media/uploads/mrhannah/diagram_2.png

Architecture

Note: The Adafruit Feather board is actually two separate microcontrollers on one board. One is a standard Arduino (ATmega32u4) chip and the other is the nRF51822 Bluetooth module. The nRF51822 is the same microcontroller that is in the ECE 4180 parts kit. The firmware that Adafruit ships with the nRF51822 though does not allow it to identify itself as a gamepad, or send axes information from thumbsticks.

High Level

The controller is separated into two main pieces:

  • ATmega32u4 collecting input information
  • nRF51822 sending Bluetooth reports

The ATmega sends the nRF chip the current input status over an SPI bus. Button inputs are routed to the IO expander chip, which the ATmega reads via I2C.

ATmega Code

The Arduino sketch's loop behavior is to continually make and send reports to the nRF chip. Reports consist of:

  • 16 bits for buttons
  • 4 * 8 bits of axis information for the thumbsticks

for a total of 6 bytes.

For the button information, the ATmega code makes use of the Adafruit-provided library to read off of the IO expander chip. The axis information is generated by reading an analog voltage from the thumbsticks, which act as potentiometers.

Some basic synchronization is done between the ATmega and the nRF chip.

  1. ATmega transfers a 0xFF, indicating the beginning of a frame; nRF chip responds 0xAA
  2. ATmega transfers byte 0 of the report; nRF chip responds 0xAA
  3. ATmega transfers byte 1 of the report; nRF chip responds 0xAA
  4. ...
  5. Atmega transfers byte 5 of the report; nRF chip responds 0xAA

If this nRF chip does not respond 0xAA then the ATmega resends the byte. In order to let the nRF chip catch the transfer, the ATmega chip waits 23ms in between transfers. (Reasons in the nRF code section)

The Arduino sketch is below

ble_controller.io

#include <SPI.h>
#include <Wire.h>
#include "Adafruit_MCP23017.h"

SPISettings bluetoothSetting(1000000, MSBFIRST, SPI_MODE0);
byte BLE_RST = 4;
byte BLE_SS = 8;
byte BLE_INT = 7;

Adafruit_MCP23017 mcp;

void reset_ble() {
  digitalWrite(BLE_RST, HIGH);
  delay(10);
  digitalWrite(BLE_RST, LOW);
  delay(10);
  digitalWrite(BLE_RST, HIGH);
  delay(10);
}

bool bluetooth_received = false;
void bluetooth_isr() {
  bluetooth_received = true;
}

void setup() {
  // reset the bluetooth module
  //pinMode(BLE_RST, OUTPUT);
  //reset_ble();
  delay(500);
  
  SPI.begin();
  Serial.println("print some here too");
  pinMode(BLE_SS, OUTPUT);
  pinMode(BLE_INT, INPUT);

  pinMode(13, OUTPUT);
  digitalWrite(BLE_SS, HIGH);
  //digitalWrite(BLE_SS, LOW);

  attachInterrupt(digitalPinToInterrupt(7),bluetooth_isr,RISING);

  mcp.begin();
  // set all of the pins to be inputs & pull up's
  for (int i = 0; i < 16; i++) {
    mcp.pinMode(i, INPUT);
    mcp.pullUp(i, HIGH);
  }
  
  delay(500);
  Serial.println("doing stuff here");
}

byte bluetooth_transfer(byte b) {
  SPI.beginTransaction(bluetoothSetting);

  digitalWrite(BLE_SS, LOW);
  byte resp = SPI.transfer(b);
  //Serial.println(b);
  
  digitalWrite(BLE_SS, HIGH);
  SPI.endTransaction();

  //Serial.println("before interrupt");
  //while(!bluetooth_received) {}
  //Serial.println("after interrupt");
  
  bluetooth_received = false;
  // we guaranteed that it was received

  return resp;
}

bool toggle = true;
byte onReport[6] = {0x7F, 0x7F, 0x80, 0x80, 0x80, 0x80};
byte offReport[6] = {0x00, 0x00, 0x20, 0x20, 0x20, 0x20};
uint8_t curr_report[6] = {0};

void send_report(uint8_t *r) {
  //Serial.println("sending report");

  // haven't sent anything so there is nothing to receive
  bluetooth_received = false;

  bluetooth_transfer(0xFF);

  for (int i = 0; i < 6; i++) {
    byte resp = bluetooth_transfer(r[i]);
    delay(23);
    

    if (resp != 0xAA) {
      i--;
      //Serial.println("repeating message");
      //Serial.println(resp);
      //while(1) {}
    }
  }
  //Serial.println("done sending");
}

void make_report() {
  // fill out the buttons
  curr_report[0] = ~((uint8_t)(mcp.readGPIO(0)));  // read from bank A on GPIO expander
  curr_report[1] = ~((uint8_t)(mcp.readGPIO(1)));  // read from bank B on GPIO expander

  // fill in the axis data
  // blank for now
  curr_report[2] = 127 - convert_stick(analogRead(2));  // x-axis
  curr_report[3] = -(127 - convert_stick(analogRead(3)));  // y-axis
  curr_report[4] = 127 - convert_stick(analogRead(0));  // z-axis
  curr_report[5] = -(127 - convert_stick(analogRead(1)));  // rx-axis

  Serial.println(curr_report[2]);
  Serial.println(curr_report[3]);
}

void loop() {
  delay(20);
  // put your main code here, to run repeatedly:
  digitalWrite(13, HIGH);

  make_report();
  send_report(curr_report);
  //bluetooth_transfer(0xAA);
  //delay(500);
  //if (toggle) {
  //  Serial.println("sending on report");
  //  send_report(onReport);
  //} else {
  //  Serial.println("sending off report");
  //  send_report(offReport);
  //}
  //toggle = !toggle;

  delay(200);
  digitalWrite(13, LOW);
}

uint8_t convert_stick(int value) {
  uint8_t ret = (uint8_t)(((uint16_t)value) >> 2);
  if (ret > 254) {
    ret = 254;
  }
  return ret;
}

nRF Code

The nRF51822 happens to be an mbed platform! Instead of trying to figure out the Keil uVision compiler, we were able to make use of the familiar mbed compiler and platform system. Programming the nRF51822 will be described in the Assembly section.

The nRF51822 code makes use of the mbed BLE and BLE HID libraries in order to abstract away directly dealing with most of the Bluetooth. Read these for more detail into BLE and HID over BLE (they are pretty complicated). In order to implement our controller though, we had to create a report descriptor, which would tell the computer what we are sending, and add a function to set our report.

The code for the project on the mbed can be found here.

Import programble_controller

A BLE HID controller implementation with communication over SPI

The code mostly follows examples used by the BLE HID library, but includes a Ticker that checks the SPI controller for a message every 20ms. This was needed because there the synchronization and threading available to us was minimal.

Assembly

Instructions for assembly are as followed:

  1. Solder two wires onto the back of the Feather labeled SWDIO and SWDCLK
  2. Solder thumbsticks to breakout board with wires out for connections to the breadboard
  3. Put Feather into breadboard
  4. Connect Vin and Gnd on thumbsticks; Connect Xout and Yout on thumbsticks to A1-A4 analog inputs on the Feather
  5. Put IO expander into breadboard
  6. Connect Vin and Gnd; Connect RST pin to Vin; Connect the address lines to Gnd; Connect SDA and SCL to the Feather; Connect the buttons to Gnd and the GPIO on the IO expander

After assembly ours looked on the inside like this:

/media/uploads/mrhannah/img_1980.jpg

Flashing Microcontrollers

Note: Make sure the boards are powered and the Pi Zero ground is connected to the Feather ground.

Program the Arduino first by using the normal Arduino IDE tools

To program the nRF chip, you need to do the following:

  1. Clone this repository
  2. Build the mbed project and download it to that folder
  3. Add "reset_config none seperate" to the last line in raspberrypi-native.cfg
  4. Run the following command:

Quote:

sudo openocd-0.9.0/rpi_gpio/openocd -s openocd-0.9.0/scripts -l log.txt -f interface/raspberrypi-native.cfg -c "transport select swd" -c "set WORKAREASIZE 0" -f target/nrf51.cfg -c init -c "reset init" -c halt -c "nrf51 mass_erase" -c "program ble_controller_NRF51822.hex verify" -c reset -c exit

It does not indicate visibly whether it was successful. Check log.txt for "Verified OK".

Below is a picture of our setup for flashing: /media/uploads/mrhannah/img_1978.jpg

At this point the controller should be functional and ready to pair. The LEDs on the board should be blinking to indicate sending reports. If you do have a battery, connect it now.

Future Work/Lessons Learned

  • Adding the rest of normal controller buttons.
  • Getting a SPISlave library that was interrupt driven instead of polling driven to reduce latency
  • A standalone nRF51822 dev kit is available that would get rid of the communication issue between the microcontrollers.
  • Resetting the nRF51822 chip from the Arduino would be useful, but our current flashing solution breaks when that is set up.
  • Occasionally the nRF51822 chip will lose the information that was flashed on it when power is lost.


Please log in to post comments.