puppeteerPro ECE 4180 Project
By: Arjun Chib and Matthew Hannah For: ECE 4180
Meet the puppeteerPro Bluetooth controller!
Demo
Project Goals
- to create a Bluetooth game controller that would be natively recognized by an OS
- to be able to (somewhat) play a game with it
Parts List
- Adafruit Feather 32u4 Bluefruit BLE
- I2C 16 input/output port expander
- Lithium Ion Battery 2500mAh
- 2 x Thumbsticks with breakout board
- 8 x Soft tactile buttons
- Raspberry Pi Zero (used for flashing firmware)
Block Diagram
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.
- ATmega transfers a 0xFF, indicating the beginning of a frame; nRF chip responds 0xAA
- ATmega transfers byte 0 of the report; nRF chip responds 0xAA
- ATmega transfers byte 1 of the report; nRF chip responds 0xAA
- ...
- 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:
- Solder two wires onto the back of the Feather labeled SWDIO and SWDCLK
- Solder thumbsticks to breakout board with wires out for connections to the breadboard
- Put Feather into breadboard
- Connect Vin and Gnd on thumbsticks; Connect Xout and Yout on thumbsticks to A1-A4 analog inputs on the Feather
- Put IO expander into breadboard
- 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:
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:
- Clone this repository
- Build the mbed project and download it to that folder
- Add "reset_config none seperate" to the last line in raspberrypi-native.cfg
- 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:
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.