Introduction
Some competitions are harder to complete than others, and this one was one of the hardest.
It was not that this challenge was technically more difficult than others, it was more about trying to convince my better half that this was time well spent doing serious project work - laughing at ones own jokes doesn't help either .
So hopefully there’ll be a few like-minded element14 members out there that will be similarly amused by my two whacky computer apps, which happened to be developed using Processing.
At the very least I’ve been amused at the output of my learning by doing efforts.
I even created some customised desktop icons (as shown above)... I decided to use an image of Mr. Tickle (sourced from Wikipedia) as one of my personalised icons - this is a tribute to the humorous Mr. Men series by Roger Hargreaves (first published 50 years ago). According to Wikipedia, Mr. Tickle was the first Mr. Men book, which was published on 10 August 1971. I believe UK viewers will be treated to a Mr Men documentary on channel 4 TV - https://www.channel4.com/press/news/channel-4-commissions-50-years-mr-men-celebrate-half-century-iconic-childrens-books-and.
So what is Processing
For those of you in the dark (because you did not read a previous blog about it), Processing is an Integrated Development Environment (IDE) that combines the power of the Java language (every Processing sketch is a subclass of the PApplet Java class) and the simplified coding architecture, and language syntax, of the Wiring programming language (as used in Arduino), which allows you to create apps for Linux, MAC and Windows computers. It even allows you to create apps for Android OS and ARM (Linux ARM OS) as well.
According to Wikipedia, Processing was initiated in 2001 by Casey Reas and Ben Fry and in 2012 they formed the Processing Foundation along with Daniel Shiffman.
The motivation behind Processing is to promote software literacy amongst students and so uses similar Arduino terminology like using sketchbook and sketches etc. and uses similar architecture with setup() and draw() as it’s two main functions. If you are comfortable with, or want to learn, object orientated programming (OOP) then Processing is a good platform to use.
It’s main focus is to provide a computer programming platform that delivers impressive computer visual effects and animation. Thanks to a wide range of very helpful libraries you can readily connect with other peripherals like video cams, audio and serial terminals and there are also a range computational libraries that allow you to simulate or generate 2D and 3D movement. This allows you to create some very interesting apps like image recognition and manipulation, for example, or create visualisation of rotating planets, which uses a physics model, for example.
There is a wealth of available libraries thanks to the contribution of others. All these libraries also provide you with a good selection of examples to learn from. A reference of all the libraries can be found here: https://processing.org/reference/libraries/
As an added bonus you can also create stand-alone Windows/Linux applications, using the export function.
For me personally, Processing has been a freely available solution that’s been looking for the right problem to solve. Well, thanks to this competition its time has come. This Project14 challenge has allowed me to finally to get to grips with many of the benefits of developing a Java-derived stand-alone app using Processing.
The big reveal (Video Demo)
Raspberry Pi Pico Board Hardware Setup
With all the fanfare following Raspberry Pi’s release of their very first microcontroller chip, the RP2040, I just couldn’t resist and purchased a $4 Raspberry Pi Pico board. I then did what most would probably do and tried out a few examples, then put it on the shelf while I thought about applications that would benefit from the dual core ARM Cortex M0+ microprocessor.
It was about a month later that I read a fellow element14 member's blog titled “Program RPi Pico using Mbed library with Arduino IDE”. Now this was music to my ears as I always felt that the RP2040 and Mbed were well suited to each other.
It just so happened that I had two Wii NunChucks, which used the I2C communication protocol with an I2C slave address of 0x52. As the Raspberry Pi Pico board offered 2 x I2C bus controllers I decided to see if I could create an application that would use both I2C ports using the Arduino IDE. I knew this was possible as the Arduino Due and the Espressif ESP32 also offered two I2C ports, and I had used these board before. This would be achieved in your Arduino firmware by initialising the two I2C ports as “Wire” and “Wire1”.
So I set about building my circuit.
As everyone quickly discovers, it is really useful to have a reset button when prototyping and developing an application. As this is not included on the Pico board, I started by adding in one.
Then with the Raspberry Pi Pico board you have the flexibility to choose which pins to use for I2C0 and I2C1 (as shown on the Pinout diagram). I arbitrarily chose to use GP20 and GP21 for I2C0 and GP9 and GP10 for I2C1. I also wanted to separate out my serial output from the USB port. So I used a Sparkfun FTDI Basic breakout board as my UART-to-USB serial bridge and connected this to UART0 (GP0 and GP1).
I did not have any off-the-shelf breakout boards, like those available from Adafruit, for my Wii NunChucks so I set about creating my own connections.
I discovered that a standard 3-terminal (TO-220) voltage regulator has pins that are the right shape and size to fit inside the unique NunChuck connector slots above and below the main connector slot. As I had a few re-salvaged regulators lying around I simply snipped off the legs and trimmed the length to suit. I then shaped a fragment of an ice cream stick for the main NunChuck central slot to give added robustness, as this helped prevent my makeshift connectors from disconnecting. I then soldered these onto some Grove connectors which when then slotted onto my breadboard to allow connection to the Pico board. It all works a charm.
{gallery:autoplay=false} NunChuck Connection |
---|
Now that I had my hardware setup, it was time to work on the firmware.
Firmware Development
Arduino Mbed Core
On the 27th of April (2021), Arduino published a blog stating that the official Arduino Mbed Core for the Raspberry Pi RP2040 chip was now available for download through the Arduino IDE.
As mentioned in the previous section, this was also documented by another element14 member who wrote about his first experiences with this release in his blog. In this blog, he commented on how to get I2C working: “I checked the pins_arduino.h file in the Mbed library and saw that the default Wire library I2C pins for SDA and SCL were GPIO 6 and 7”.
This was my cue to get started with I2C. However, I quickly discovered that you could not use both I2C ports with the Arduino Mbed Core. Undeterred, I dived in to see if I could fashion a quick fix knowing full well that any official board update from Arduino (through Arduino IDE Board Manager) would overwrite my mods.
And... success!
I found in Wire.h that there is this check:
#if WIRE_HOWMANY > 0 extern arduino::MbedI2C Wire; #endif #if WIRE_HOWMANY > 1 extern arduino::MbedI2C Wire1; #endif typedef arduino::MbedI2C TwoWire;
Then I found that WIRE_HOWMANY was defined inside pins_arduino.h.
Also within pins_arduino.h I found that we need to make a few other modifications to define the SDA and SCL pins for the 2nd I2C port. Then for my own convenience, I also changed my default I2C port pin numbers to something I wanted to use as my default as Arduino used Pico I2C1 pins as the default rather than one of the I2C0 pins. It’s worth noting that you can also then later choose your own pins for SDA/SCL in your sketches through the Wire library.
// Wire #define PIN_WIRE_SDA (20u) // default was 6 #define PIN_WIRE_SCL (21u) // default was 7 #define PIN_WIRE_SDA1 (6u) #define PIN_WIRE_SCL1 (7u) #define WIRE_HOWMANY (2) //default 1 #define I2C_SDA (digitalPinToPinName(PIN_WIRE_SDA)) #define I2C_SCL (digitalPinToPinName(PIN_WIRE_SCL)) #define I2C_SDA1 (digitalPinToPinName(PIN_WIRE_SDA1)) #define I2C_SCL1 (digitalPinToPinName(PIN_WIRE_SCL1))
I was now ready to develop my project code using the Arduino IDE.
First up was my NunChuck library. Here I based my Nunchuck class off another open source library, which is available on Github: https://github.com/RalphBacon/198-Wii-Nunchuck-for-your-Arduino
ArduinoNunchuck.h:
/* * ArduinoNunchuk.h - Wii Nunchuk library for Rasberry Pi Pico based on * * @license Nunchuk Arduino library v0.0.1 16/12/2016 * http://www.xarg.org/2016/12/arduino-nunchuk-library/ * * Copyright (c) 2016, Robert Eisele (robert@xarg.org) * Dual licensed under the MIT or GPL Version 2 licenses. * * Modified my Colin Gerrish (2021) **/ #ifndef ArduinoNunchuk_H #define ArduinoNunchuk_H #include <Arduino.h> class ArduinoNunchuk { public: ArduinoNunchuk(TwoWire& theWire); uint8_t analogXY[2]; uint16_t accelXYZ[3]; uint8_t btnsZC[2]; uint8_t IDbuff[6]; void init(); void getID(); void update(); private: TwoWire* _wire; void _sendByte(byte data, byte location); }; #endif
ArduinoNunChuck.cpp:
/* * ArduinoNunchuk.h - Wii Nunchuk library for Rasberry Pi Pico based on * * @license Nunchuk Arduino library v0.0.1 16/12/2016 * http://www.xarg.org/2016/12/arduino-nunchuk-library/ * * Copyright (c) 2016, Robert Eisele (robert@xarg.org) * Dual licensed under the MIT or GPL Version 2 licenses. * * Modified my Colin Gerrish (2021) **/ #include <Arduino.h> #include <Wire.h> #include "ArduinoNunchuk.h" #define NUNCHUK_ADDRESS 0x52 ArduinoNunchuk::ArduinoNunchuk(TwoWire& theWire) { _wire = &theWire; } void ArduinoNunchuk::init() { _wire->begin(); ArduinoNunchuk::_sendByte(0xF0, 0x55); delay(15); ArduinoNunchuk::_sendByte(0xFB, 0x00); } void ArduinoNunchuk::getID() { byte cnt = 0; _wire->beginTransmission(NUNCHUK_ADDRESS); _wire->write(0xFA); _wire->endTransmission(); delay(5); _wire->requestFrom(NUNCHUK_ADDRESS, 6); while(_wire->available()) { if (cnt < 6) { IDbuff[cnt] = _wire->read(); cnt++; } } _wire->endTransmission(); } void ArduinoNunchuk::update() { byte cnt = 0; uint8_t values[6]; _wire->beginTransmission(NUNCHUK_ADDRESS); _wire->write(0x00); _wire->endTransmission(); delay(5); _wire->requestFrom(NUNCHUK_ADDRESS, 6); while(_wire->available()) { if (cnt < 6) { values[cnt] = _wire->read(); cnt++; } } ArduinoNunchuk::analogXY[0] = values[0]; ArduinoNunchuk::analogXY[1] = values[1]; ArduinoNunchuk::accelXYZ[0] = (values[2] << 2) | ((values[5] >> 2) & 3); ArduinoNunchuk::accelXYZ[1] = (values[3] << 2) | ((values[5] >> 4) & 3); ArduinoNunchuk::accelXYZ[2] = (values[4] << 2) | ((values[5] >> 6) & 3); ArduinoNunchuk::btnsZC[0] = !((values[5] >> 0) & 1); ArduinoNunchuk::btnsZC[1] = !((values[5] >> 1) & 1); } void ArduinoNunchuk::_sendByte(byte location, byte data) { _wire->beginTransmission(NUNCHUK_ADDRESS); _wire->write(location); _wire->write(data); _wire->endTransmission(); }
Then my Raspberry Pi Pico application.
PicoNunChuck.ino:
/* * ArduinoNunchuk.h - Wii Nunchuk library for Rasberry Pi Pico based on * * @license Nunchuk Arduino library v0.0.1 16/12/2016 * http://www.xarg.org/2016/12/arduino-nunchuk-library/ * * Copyright (c) 2016, Robert Eisele (robert@xarg.org) * Dual licensed under the MIT or GPL Version 2 licenses. * * Modified my Colin Gerrish (2021) **/ #include <Wire.h> #include "ArduinoNunchuk.h" #define BAUDRATE 115200 ArduinoNunchuk nunchuk0 = ArduinoNunchuk(Wire); ArduinoNunchuk nunchuk1 = ArduinoNunchuk(Wire1); uint8_t cntr = 0; char buf[64]; String N0_ID = ""; String N1_ID = ""; void setup() { Serial1.begin(BAUDRATE); delay(10); while (!Serial1) {;;} // Initialise Nunchucks by sending start instructions to not encrypt the data nunchuk0.init(); nunchuk1.init(); // The ID tells you whether your NunChuck is offical version or otherwise. Just nice to know. nunchuk0.getID(); nunchuk1.getID(); // Convert ID to readable string for (byte i = 0; i < 6; i++) { N0_ID += String(nunchuk0.IDbuff[i], HEX); N1_ID += String(nunchuk1.IDbuff[i], HEX); } N0_ID.toUpperCase(); N1_ID.toUpperCase(); } void loop() { if (cntr < 11) { // This is just some padding to get the UART serial stream started Serial1.print("."); if (cntr == 10) Serial1.println(""); cntr++; } else { // Get NunChuck data nunchuk0.update(); nunchuk1.update(); // Send data to Serial1 - just broken out for easier read and to get transmit payload short memset(buf, '\0', 64); sprintf(buf, "N0ID:%s,N1ID:%s,N0BZ:%u,N0BC:%u,N1BZ:%u,N1BC:%u,", N0_ID.c_str(), N1_ID.c_str(), nunchuk0.btnsZC[0], nunchuk0.btnsZC[1], nunchuk1.btnsZC[0], nunchuk1.btnsZC[1]); Serial1.print(buf); memset(buf, '\0', 64); sprintf(buf, "N0JX:%u,N0JY:%u,N1JX:%u,N1JY:%u,", nunchuk0.analogXY[0], nunchuk0.analogXY[1], nunchuk1.analogXY[0], nunchuk1.analogXY[1]); Serial1.print(buf); memset(buf, '\0', 64); sprintf(buf, "N0AX:%u,N0AY:%u,N0AZ:%u,", nunchuk0.accelXYZ[0], nunchuk0.accelXYZ[1], nunchuk0.accelXYZ[2]); Serial1.print(buf); memset(buf, '\0', 64); sprintf(buf, "N1AX:%u,N1AY:%u,N1AZ:%u\n", nunchuk1.accelXYZ[0], nunchuk1.accelXYZ[1], nunchuk1.accelXYZ[2]); Serial1.print(buf); } delay(200); // Sampling every 200ms (5 Hz) }
I was now able to stream data via the Serial 1 port.
It was time to build my front end.
My Processing Applications
I decided to create two applications as my front end user interface, which I named "PicoNunChuck MonsterShake" and "PicoNunChuck TickleMe".
PicoNunChuck MonsterShake App
Who needs boring graphs to depict acceleration when you can use a monster to show the result.
So for my first NunChuck app-come-game I took an existing example ScaleShape.pde, which is found inside the Basics --> Shape folder and made a few amendments. Basically, this ScaleShape example used an SVG (scalor vector graphics) image which would change scale depending on the x-coordinates of the mouse pointer. I changed this so that the SVG would scale depending on how vigorously you could shake one Wii NunChuck. The degree of scaling is based on a sample of X-Y-Z accleration data. I then also added in some sounds for added effect.
For my project I used the standard Processing Serial and the Sound libraries.
/** * PicoNunChuck_MonsterShare. * Sketch by Colin Gerrish. * Copyright 2021 * MIT License applies * */ import processing.sound.*; import processing.serial.*; SoundFile soundfile1; SoundFile soundfile2; Serial PicoSerial; // Create object from Serial class int PortNum = -1; // the serial port matching index for USB port String PicoString = null; // ShakeMe Text final String message1 = "Shake Nunchuck!"; final String message2 = "Press Z to start"; final String message3 = "Pico board not found"; float msg_x, msg_y; // X and Y coordinates of text final int NUNCHUK_ACCEL_ZERO = 512; int btnsZC[] = new int[4]; int accelXYZ[] = new int[6]; int deltaXYZ[] = new int[30]; long deltaMax = 0; float delta = 1.36; float xval = 0.0; boolean NunChuckStart = false; boolean completeRecord = false; boolean firstRecord = false; boolean showAnimation = false; int NoSerialMsg = 0; int i; int ShakeDuration = 0; int MessageTimer = 0; PShape bot; void setup() { size(720, 480); frameRate(50); // Create the font textFont(createFont("SourceCodePro-Regular.ttf", 36)); textAlign(CENTER, CENTER); // The file "bot1.svg" must be in the data folder // of the current sketch to load successfully bot = loadShape("bot1.svg"); // Load soundfiles soundfile1 = new SoundFile(this, "monster1.wav"); soundfile2 = new SoundFile(this, "monster2.wav"); // Assign initial values to accelXYZ[] and deltaXYZ[] arrays for (i = 0; i < 6; i++) accelXYZ[i] = 0; for (i = 0; i < 3; i++) deltaXYZ[i] = 0; // Prints out the available serial ports. printArray(Serial.list()); println(); for (int i = 0; i < Serial.list().length; i++) { if ((Serial.list()[i].indexOf("USB",0) >= 0) || (Serial.list()[i].indexOf("COM",0) >= 0)) { PortNum = i; } } if (PortNum >=0) { String portName = Serial.list()[PortNum]; PicoSerial = new Serial(this, portName, 115200); PicoSerial.clear(); } } void draw() { if (PortNum >=0) { if (PicoSerial != null) { if ( PicoSerial.available() > 0) { // If data is available - yep, using the polling method rather than Serial Event MessageTimer = millis(); PicoString = PicoSerial.readStringUntil(10); if (PicoString != null) { completeRecord = false; String[] picoParams = splitTokens(PicoString, "\n, "); int ParamCnt = 0; for (int i = 0; i < picoParams.length; i++) { String[] p = splitTokens(picoParams[i], ":"); if (p[0].indexOf("N0BZ",0)>=0) { btnsZC[0] = int(p[1]); if (btnsZC[0] == 1) println("Btn Z"); ParamCnt++; } else if (p[0].indexOf("N0BC",0)>=0) { btnsZC[1] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N0AX",0)>=0) { accelXYZ[3] = accelXYZ[0]; accelXYZ[3] = abs(int(p[1]) - NUNCHUK_ACCEL_ZERO); ParamCnt++; } else if (p[0].indexOf("N0AY",0)>=0) { accelXYZ[4] = accelXYZ[1]; accelXYZ[4] = abs(int(p[1]) - NUNCHUK_ACCEL_ZERO); ParamCnt++; } else if (p[0].indexOf("N1AY",0)>=0) { accelXYZ[5] = accelXYZ[2]; accelXYZ[5] = abs(int(p[1]) - NUNCHUK_ACCEL_ZERO); ParamCnt++; } } if (ParamCnt >= 5) completeRecord = true; if (completeRecord) { NoSerialMsg = 0; if (firstRecord) { int dX = accelXYZ[3] - accelXYZ[0]; int dY = accelXYZ[4] - accelXYZ[1]; int dZ = accelXYZ[5] - accelXYZ[2]; //int dX = accelXYZ[3]; //int dY = accelXYZ[4]; //int dZ = accelXYZ[5]; if (!showAnimation && !NunChuckStart && btnsZC[0] == 1) { background(102); NunChuckStart = true; ShakeDuration = millis(); // set up initial coords for text msg_x = width * 0.5; msg_y = height * 0.2; i = 0; } if (NunChuckStart) { if (i < 24) { deltaXYZ[i] = (dX + dY + dZ); //deltaX[i] = (dX); //deltaY[i] = (dY); //deltaZ[i] = (dZ); i++; } else { deltaMax = 0; for (i = 0; i < 24; i++) { deltaMax += deltaXYZ[i]; //println("dx:"+deltaX[i]+", dy:"+deltaY[i]+", dz:"+deltaY[i]+" >> tot:"+(deltaX[i]+deltaY[i]+deltaZ[i])); } if ((millis() - ShakeDuration) > 7000) { // introducing a short delay for effect deltaMax /= 24; println("deltaAve: " + deltaMax); if (deltaMax > 650) delta = 1.56; else if (deltaMax > 625) delta = 1.54; else if (deltaMax > 530) delta = 1.52; else if (deltaMax > 465) delta = 1.50; else if (deltaMax > 420) delta = 1.48; else if (deltaMax > 395) delta = 1.46; else if (deltaMax > 340) delta = 1.44; else if (deltaMax > 295) delta = 1.42; else if (deltaMax > 220) delta = 1.40; else if (deltaMax > 145) delta = 1.38; if (deltaMax > 530) { soundfile2.amp(0.99); soundfile2.play(); } else { soundfile1.amp(0.6); soundfile1.rate(1.2); soundfile1.play(); } NunChuckStart = false; showAnimation = true; // reset the common index value i = 1; } } } } else firstRecord = true; } } } else { if (NoSerialMsg > 100) { println("NunChuck Tickles: No data"); } else NoSerialMsg++; } } if (completeRecord && NoSerialMsg < 100) { } } if (NunChuckStart) { background(110, 255, 110); // Display shakeme text msg_x += random(-3, 3); msg_y += random(-3, 3); fill(0); text(message1, msg_x, msg_y); } else { background(102); if (!showAnimation) { // set up initial coords for text msg_x = width * 0.5; msg_y = height * 0.2; fill(0); if ((millis() - MessageTimer) < 500) text(message2, msg_x, msg_y); else text(message3, msg_x, msg_y); translate(width/2, height/2); //float zoom = map(mouseX, 0, width, 0.1, 4.5); float zoom = map(0, 0, width, 0.1, 3.0); scale(zoom); shape(bot, -140, -140); } else { translate(width/2, height/2); //float zoom = map(mouseX, 0, width, 0.1, 4.5); if (i < 16) { xval= pow( delta, i ); i++; } float zoom = map(xval, 0, 600, 0.1, 3.0); scale(zoom); shape(bot, -140, -140); if (i > 15 && !soundfile1.isPlaying() && !soundfile2.isPlaying()) { if (btnsZC[1] == 1) { // We now reset everything showAnimation = false; firstRecord = false; for (i = 0; i < 6; i++) accelXYZ[i] = 0; for (i = 0; i < 3; i++) deltaXYZ[i] = 0; ShakeDuration = 0; } } } } }
PicoNunChuck TickleMe App
Sometime laugher is the best medicine when trying to chase down another object.
So, for my second NunChuck app uses 2 Wii NunChucks. This app-come-game was derived from three Processing examples, namely:
- Tickle.pde – this can be found in the Topics -> Interaction folder.
- AccelerationWithVectors.pde – this can be found in the Topics -> Vectors folder.
- G4P_Stick – this example is from an external library (G4P) which can be downloaded via the Processing Import Library -> Add Library... menu option. This example is found in the Contributed Libraries -> G4P folder.
Here I changed the way the “Tickle” message moved on the screen in the Tickle.pde sketch by applying the Acceleration movement characteristics found in the AccelerationWithVectors.pde sketch. Then instead of using the mouse coordinates to govern direction and magnitude of acceleration as per the AccelerationWithVectors.pde sketch I used one NunChuck’s joystick movement for direction and the Z & C buttons to acceleration boost or dampen.
Then I added in the pointer or arrow as found in the G4P_Stick.pde sketch. Here I used the 2nd NunChuck joystick to govern movement and used the Z and C buttons to speed up or slow down speed.
Similarly, I added in sound for added effect.
The code for this project is as follows:
/** * PicoNunChuck_TickleMe. * Sketch by Colin Gerrish. * Copyright 2021 * MIT License applies * */ import processing.sound.*; import processing.serial.*; SoundFile soundfile; Serial PicoSerial; // Create object from Serial class int PortNum = -1; // the serial port matching index for USB port String PicoString = null; int btnsZC[] = new int[4]; int joyXY[] = new int[4]; int accelXYZ[] = new int[6]; byte NunChuckNum = 0; boolean completeRecord = false; // Object 1 is Text String message = "e14!"; float msg_speed = 0; float msg_x, msg_y; // X and Y coordinates of text float msg_hr, msg_vr; // horizontal and vertical radius of the text float msg_rad = 0; PVector CentrePoint; PVector velocity; PVector acceleration; // The Messages's maximum speed float topspeed = 8.0; int hov_trig = 0; // Object 2 is a triangle int tri_dirX, tri_dirY; float tri_x, tri_y; int tri_speed = 0; int tri_point = 5; int NoSerialMsg = 0; void setup() { // set up screen size size(720, 480); frameRate(50); CentrePoint = new PVector(width/2,height/2); velocity = new PVector(0,0); // Load a soundfile soundfile = new SoundFile(this, "mixkitlaugh.wav"); // Create the font textFont(createFont("SourceCodePro-Regular.ttf", 36)); textAlign(CENTER, CENTER); // Initial positions of objects msg_hr = textWidth(message) / 2; msg_vr = (textAscent() + textDescent()) / 2; msg_x = width/4; msg_y = height/4; tri_x = 3*width/4; tri_y = 3*height/4; soundfile.amp(0.8); // Prints out the available serial ports. printArray(Serial.list()); println(); for (int i = 0; i < Serial.list().length; i++) { if ((Serial.list()[i].indexOf("USB",0) >= 0) || (Serial.list()[i].indexOf("COM",0) >= 0)) { PortNum = i; } } if (PortNum >=0) { String portName = Serial.list()[PortNum]; PicoSerial = new Serial(this, portName, 115200); PicoSerial.clear(); } } void draw() { // Instead of clearing the background, fade it by drawing // a semi-transparent rectangle on top noStroke(); fill(204, 120); rect(0, 0, width, height); if (PortNum >=0) { if (PicoSerial != null) { if ( PicoSerial.available() > 0) { // If data is available - yep, using the polling method rather than Serial Event PicoString = PicoSerial.readStringUntil(10); if (PicoString != null) { completeRecord = false; String[] picoParams = splitTokens(PicoString, "\n, "); int ParamCnt = 0; for (int i = 0; i < picoParams.length; i++) { String[] p = splitTokens(picoParams[i], ":"); if (p[0].indexOf("N0BZ",0)>=0) { btnsZC[0] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N0BC",0)>=0) { btnsZC[1] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N1BZ",0)>=0) { btnsZC[2] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N1BC",0)>=0) { btnsZC[3] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N0JX",0)>=0) { joyXY[0] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N0JY",0)>=0) { joyXY[1] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N1JX",0)>=0) { joyXY[2] = int(p[1]); ParamCnt++; } else if (p[0].indexOf("N1JY",0)>=0) { joyXY[3] = int(p[1]); ParamCnt++; } } println(); if (ParamCnt >= 7) completeRecord = true; if (completeRecord) { NoSerialMsg = 0; updateArrowPointer(); updateMsgPosition(); } } } else { if (NoSerialMsg > 100) { fill(5, 75, 0); text("NunChuck Tickles: No data", width * 0.5, height * 0.1); //NoSerialMsg = 0; } else NoSerialMsg++; } } if (completeRecord && NoSerialMsg < 100) { strokeWeight(1.5f); // Calculate current position of arrow tri_x = (tri_x + tri_dirX * tri_speed + width) % width; tri_y = (tri_y + tri_dirY * tri_speed + height) % height; // Draw arrow in current position and rotation pushMatrix(); translate(tri_x, tri_y); rotate(tri_point * PI/4); fill(0, 125, 255); stroke(204, 102, 0); beginShape(); vertex(-10, -10); vertex(-10, 10); vertex(20, 0); endShape(CLOSE); fill(204, 102, 0); noStroke(); ellipse(-6, 0, 8, 6); popMatrix(); // If the cursor is over the text, change the position and within a timer then play sound if (abs(tri_x - msg_x) < msg_hr && abs(tri_y - msg_y) < msg_vr) { hov_trig = millis(); msg_x += random(-5, 5); msg_y += random(-5, 5); // we stop the pointer for added effect if (tri_speed > 1) tri_speed = 0; if (!soundfile.isPlaying()) soundfile.loop(); } else { if (soundfile.isPlaying()) { if (millis() - hov_trig > 1000) soundfile.stop(); else { msg_x += random(-5, 5); msg_y += random(-5, 5); } } } // Display tickle me text fill(0); text(message, msg_x, msg_y); // Now update the position //msg_x = msg_x + 0.8; //msg_y = msg_y + 0.2; msg_x = CentrePoint.x; msg_y = CentrePoint.y; if (msg_x > width + msg_hr) { msg_x = -msg_hr/2; } if (msg_y > height + msg_vr) { msg_y = -msg_vr/2; } } } else { fill(5, 75, 0); text("NunChuck Tickles: CONNECT Pico", width * 0.5, height * 0.1); } } void updateArrowPointer() { // Determine arrow pointing direction if (joyXY[0] < 75) { if (joyXY[1] < 75) { println("N1 L DN"); tri_point = 3; tri_dirX = -1; tri_dirY = 1; } else if (joyXY[1] > 175) { println("N1 L UP"); tri_point = 5; tri_dirX = -1; tri_dirY = -1; } else { println("N1 L"); tri_point = 4; tri_dirX = -1; tri_dirY = 0; } } else if (joyXY[0] > 175) { if (joyXY[1] < 75) { println("N1 R DN"); tri_point = 1; tri_dirX = 1; tri_dirY = 1; } else if (joyXY[1] > 175) { println("N1 R UP"); tri_point = 7; tri_dirX = 1; tri_dirY = -1; } else { println("N1 R"); tri_point = 0; tri_dirX = 1; tri_dirY = 0; } } else { if (joyXY[1] < 75) { println("N1 DN"); tri_point = 2; tri_dirX = 0; tri_dirY = 1; } else if (joyXY[1] > 175) { println("N1 UP"); tri_point = 6; tri_dirX = 0; tri_dirY = -1; } } // Determine Speed of pointer if (btnsZC[0] == 1 && btnsZC[1] == 0) { if (tri_speed < 5) tri_speed++; } else if (btnsZC[0] == 0 && btnsZC[1] == 1) { if (tri_speed > 0) tri_speed--; } } void updateMsgPosition() { float tmp_x, tmp_y; // X and Y coordinates of text float AccelMag = 0.3; // Determine Speed of pointer if (btnsZC[2] == 1 && btnsZC[3] == 0) { AccelMag = 0.9; } else if (btnsZC[2] == 0 && btnsZC[3] == 1) { AccelMag = 0.1; } // Determine message change direction // set default to centre of screen tmp_x = width/4; tmp_y = height/4; if (joyXY[2] < 75) { if (joyXY[3] < 75) { println("N2 L DN"); tmp_x = width/4; tmp_y = 3*height/4; } else if (joyXY[3] > 175) { println("N2 L UP"); tmp_x = width/4; tmp_y = height/4; } else { println("N2 L"); tmp_x = width/4; } } else if (joyXY[2] > 175) { if (joyXY[3] < 75) { println("N2 R DN"); tmp_x = 3*width/4; tmp_y = 3*height/4; } else if (joyXY[3] > 175) { println("N2 R UP"); tmp_x = 3*width/4; tmp_y = height/4; } else { println("N2 R"); tmp_x = 3*width/4; } } else { if (joyXY[3] < 75) { println("N2 DN"); tmp_y = 3*height/4; } else if (joyXY[3] > 175) { println("N2 UP"); tmp_y = height/4; } } PVector movment = new PVector(tmp_x,tmp_y); PVector acceleration = PVector.sub(movment,CentrePoint); // Set magnitude of acceleration acceleration.setMag(AccelMag); // Velocity changes according to acceleration velocity.add(acceleration); // Limit the velocity by topspeed velocity.limit(topspeed); // Location changes by velocity CentrePoint.add(velocity); }
Final Remarks
No doubt, these apps need further polishing but they were really fun to make, I learnt some new things and they made me chuckle.
Hopefully they cheered you up too.
Top Comments