2023 Group 4 – Angel Hat and Glove Set
Created by Nimat Choudhury, Sahra Yusuf, Shahd Ahmed
Do you wish to make heads turn everytime you step in the room? Well, we’ve got you covered with our newest release: The Angelic Hat and Magic Glove set! With a cute design and unique never-before-seen features, this hat will quickly become the most prized item in your wardrobe. Interactions such as moving your head to flap the wings or triggering a colour change with a single touch are sure to make everyone jealous of the power you’ll possess after owning this set.
Motion Tracking Fashionable Wearable
We chose to create a motion-tracking fashionable wearable, in the form of a hat and glove, syncing wirelessly. Our brief was to design a garment capable of tracking body movement and provide outputs based on collected data, whilst also focusing on being practical for everyday use and maintaining aesthetics. Our project revolves around a hat with wings and LEDs, which are controlled by movement, and the wearer pressing sensors on a glove. The sensors control the LED colour and light sequence, and head movement makes the wings flap. A hat perfect for making heads turn!
Design Blog
Week 7
Brainstorming
After choosing our brief, we began brainstorming and expanding on initial ideas.
We came up with numerous ideas such as a dress, a skirt, a glove, a hat, and glasses. After discussion, we decided to use a hat as our project base. Designs included a costume-style hat, similar to Captain Hook, with large feathers and suitable for dress-up parties, a ship’s captain-style hat with matching gloves, and various styles of hats with wings.
Ultimately, we went with a casual hat suitable for everyday wear. We also wanted to combine a scrapped idea with this hat, so we brought back the glove. The glove, along with the addition of wings, will communicate with the hat wirelessly through hand gestures. The end product would give the wearer an accessory to wear on any occasion, whilst having unique features, adding interactive elements, as well as customisation.
Week 8
Components Research
After completing the design of our project, we researched components that we would utilise to develop a better understanding of how they work and how to implement them with Arduino.
Wireless Module
We researched wireless modules and how they can be used with Arduino, so we can use them to make our hat and glove communicate with each other. We ended up ordering a NRF24L01, a good module for wireless communication between 2 Arduinos with close proximity.
Gyroscope
A gyroscope to be used to detect movement in angles, which will determine the movements of the wings on the hat. Specified angle ranges would result in different wing movements. We decided the MPU6050 module would be most suitable to meet our task’s goals as it can detect angles and acceleration.
Mini Servo
We saw mini servos as the best way to make the wings move and most suitable for the movements we wanted to implement. They would also be small enough to fit into the hat while also managing to hold up the wings.
NeoPixel Ring 16
We wanted to add LEDs placed in the shape of a heart on our hat and glove, but instead we found that a NeoPixel Ring would be much more efficient to use as it would connect to one pin, as opposed to multiple LEDs which would be a hassle to keep control of. The NeoPixel can have various colours and different light animations, which would work well with our project and can be controlled with sensors in the glove.
GSR Sensor
We knew we wanted additional features that would add a small health aspect to our product. Because of this, we also chose to incorporate a GSR sensor which measures the amount of sweat produced. Along with being able to indicate if someone has just done exercise, the amount of sweat can also correspond to the user’s emotional state. We plan to use this to manage change in hues of the NeoPixel on the glove.
Week 9
Prototype
We began prototyping the wings first, in order to attach them to servos and test their movement. The wings were crocheted by Sahra, and originally were white and light blue. The next wing prototype was a larger size, to better match the hat. After considering our hat’s aesthetics and asking a couple stakeholders on their preference, we decided to use wings with brown stitching instead of light blue, to better match the colour scheme.
Week 10
After prototyping, we started to implement the real functionalities of the final product. To ensure a proper functionality of each component, each was tested separately and then each functionality was combined with another until all functionalities are working out simultaneously. Unfortunately, not all features worked well with the wireless module. The servo and animation feature would not send from transmitter to receiver so we had to compromise by limiting servo movements to the gyroscope and switching animations from being triggered by the glove sensors to being activated by a button on the hat.
Week 11
We were working on combining multiple features and debugging errors we came across. Further investigation of enhancing the code and using more comprehensive commands has been done to enable the better functionality performance.
It was hard to get many videos of the hat working using batteries because the Arduino drained them so quickly. Here we manage to show the hat’s light animation and servo feature working with the Arduino inside of the hat and a battery powering it. As you can see, pressing the button flicks through a variety of animations, while moving your head causes the wings attached to the servos to move.
Project Task List
Week | Task | Member(s) |
7 | Topic selection Project brainstorm, exploring ideas | All members |
8 | Research components: Wireless module Gyroscope/Servo Galvanic sensors, LDR | All members Shahd Sahra Nimat |
9 | Prototypes Order components Hard coding, testing separate functions and components | All members |
10 | Assembling circuits Making wings Coding functions Order components Plan/organise portfolio | All members Sahra All members Shahd Nimat |
11 | Debugging Further assembling of components Combining materials to components Portfolio/design blog Testing | Sahra, Shahd All members Sahra Nimat Sahra, Shahd |
Maker Manual
Overview
Tools and Components
Glove (Transmitter)
- 1 NeoPixel Ring 16
- 1 NRF24L01 (Wireless module)
- 1 10uF Capacitor
- 1 GSR sensor
- 2 round force sensitive resistors
- 4 AA 1.5V batteries
- 1 Arduino Uno
- 1 Breadboard
- Wires
Hat (Receiver)
- 1 NeoPixel Ring 16
- 1 NRF24L01 (Wireless module)
- 1 10uF Capacitor
- 1 MPU6050 (Gyroscope/Accelerometer)
- 2 mini servos
- 1 button
- 4 AA 1.5V batteries
- 1 Arduino Uno
- 1 Breadboard
- Wires
Circuit Layout
Transmitter
Receiver
How To Build
Code
Transmitter
// Code 1: Sending Text (Transmitter)
#include <SPI.h>
#include <RF24.h> //Wireless Module Libraries
#include <nRF24L01.h>
#include <Adafruit_NeoPixel.h> //RGB 16 LED Ring Library
RF24 radio(9, 8); //Pins for wireless module (CE, CSN)
const byte address [10] = "ADDRESS01"; //Address for radio communication
char color [][7] = {"C0", "C1", "C2", "C3", "C4", "C5"}; //Array of colour numbers to be sent to Receiver
const int FSR_THUMB = A0; //Pin for force sensor at the thumb
const int FSR_RING = A1; //Pin for force sensor at the ring finger
int thumbReading = 0; //Reading from thumb's force sensor
int ringReading = 0; //Reading from ring finger's force sensor
int countColour = 0; //Pointer for color array
const int GSR = A5; //Pin for sweat sensor
int sensorValue; //Reading from sweat sensor
int LED_PIN = 6; //NeoPixel Pin
int LED_COUNT = 16; //Number of LEDs on the NeoPixel
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); //Declare NeoPixel strip object
int brightness = 20; //brightness of NeoPixel
int ledColour; //Colour of NeoPixels
int blueColour = 0; //Blue hue of NeoPixel
int greenColour = 60; //Green hue of NeoPixel
int redColour = 60; //Red hue of NeoPixel
unsigned long timer = 0; //Variable used with millis() when tracking GSR reading
void setup() {
Serial.begin(9600);
radio.begin();
radio.openWritingPipe(address); //Set destination address
radio.setPALevel (RF24_PA_MIN); //Adjust power level to minimum as modules will be close together
radio.stopListening(); //The Transmitter does not receive data
strip.begin();
}
void loop(){
sweatBlue(); //Call function for using GSR sensor
sensorTapping(); //Call function for reading force sensors and sending data to receiver
}
void showColour(uint32_t color, int brightness) { //Function to show a given colour and brightness
for (int i = 0; i < strip.numPixels(); i++) {
uint8_t red = (color >> 16) & 0xFF;
uint8_t green = (color >> 8) & 0xFF;
uint8_t blue = color & 0xFF;
strip.setPixelColor(i, strip.Color(red * brightness / 255, green * brightness / 255, blue * brightness / 255));
strip.show();
}
}
void sweatBlue() { //Function to change colour of NeoPixel on glove to display level of sweat
if (millis() - timer >= 100) { //This should happen every 100ms
sensorValue=analogRead(GSR);
blueColour = map(sensorValue, 0, 800, 255, 0); //Map the sweat sensor value to be used as the blue hue for the NeoPixel's colour
Serial.println(sensorValue);
ledColour = strip.Color(redColour, greenColour, blueColour);
showColour(strip.Color(redColour, greenColour, blueColour), brightness); //Put the new blue value into the NeoPixel
timer = millis(); //Update timer
}
}
void sensorTapping() { //Function to check if ring finger and thumb are pressed together
thumbReading = analogRead(FSR_THUMB);
ringReading = analogRead(FSR_RING);
Serial.println("thumb" + String(thumbReading)); //Used for testing if sensors are being read correctly
Serial.println("ring" + String(ringReading));
delay(1000); //Delay of 1 second to make sure one finger tap isnt registered as multiple taps
if ((thumbReading > 400) && (ringReading > 400)) { //Colour change when force exceeds 400
if (countColour < 5) {
countColour++; //Increment pointer in color array
}
else {
countColour = 0; //reset pointer to index 0 when it exceeds index 4
}
radio.write(&color[countColour], sizeof(color[countColour])); //Send the new colour to Receiver
Serial.print("Text: ");
Serial.println(color[countColour]); //Used to test if pointer at color array has been changed
delay(500); //Delay of 0.5 seconds to make sure one finger tap isnt registered as multiple taps
}
}
Receiver
// Code 1: Sending Text (Receiver)
// Library: TMRh20/RF24 (https://github.com/tmrh20/RF24/)
#include <SPI.h>
#include <RF24.h> //Wireless Module Libraries
#include <nRF24L01.h>
#include <Adafruit_NeoPixel.h>//RGB 16 LED Ring Library
#ifdef __AVR__
#include <avr/power.h> //Required for 16 MHz Adafruit Trinket
#endif
#define LED_PIN 6 //NeoPixel pin
#define LED_COUNT 16 //Number of LEDs on the NeoPixel
#define BUTTON_PIN 7 //Pin for button
#include <Servo.h> //Servo Library
#include <MPU6050_tockn.h> //Gyroscope/Accelerometer module
#ifdef __AVR__
#include <avr/power.h> //Required for 16 MHz Adafruit Trinket
#endif
bool oldState = HIGH; //Variable to store the button's old state
int showType = 0; //Variable for the animation number used to as an argument for startshow function
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); //Declare NeoPixel strip object
int pos = 0; //Initialise position of servo
int brightness = 50; //Initialise brightness of NeoPixel
RF24 radio (9, 8); //CE and CSN pins
const byte address [10] = "ADDRESS01"; //Variable for the host and receiver
int animation_count = 0; //Pointer for animation array
int countColour = 0; //Pointer for colours array
uint32_t colours[] = { //Array of colours to be
strip.Color(0, 255, 0), //Green
strip.Color(255, 0, 0), //Red
strip.Color(0, 0, 255), //Blue
strip.Color(255, 255, 0), //Yellow
strip.Color(255, 0, 255), //Magenta
strip.Color(0, 0, 0) //Off
};
MPU6050 mpu6050(Wire); //Creating MPU6050 object
float x; //x rotation value from MPU6050 module
float y; //y rotation value from MPU6050 module
float z; //z rotation value from MPU6050 module
float prevX; //Saving the x rotation value from the previous 1/2 second
float prevY; //Saving the y rotation value from the previous 1/2 second
float prevZ; //Saving the z rotation value from the previous 1/2 second
float xDiff; //Difference in x rotation within 1 second
float zDiff; //Difference in z rotation within 1 second
unsigned long timer = 0;
unsigned long timer2 = 0; //Variables to be used when implementing millis() for multitasking
unsigned long timer3 = 0;
//Class for multitasking servos reused from https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview
class Sweeper { //Declaring a class for the servos to work with multitasking
Servo servo; //The servo
int pos; //Current servo position
int increment; //Increment to move for each interval
int updateInterval; //Interval between updates
unsigned long lastUpdate; //Last update of position
public:
Sweeper(int interval, int inputIncrement) { //Method to create instance of Sweeper class
updateInterval = interval;
increment = inputIncrement;
}
void Attach(int pin) { //Method that causes wings to move
servo.attach(pin); //Attach servo in order to move
}
void Detach() { //Method that causes wings to stop moving
servo.detach(); //Detach servo in order to halt it
}
void Update() { //Increment servo using the argument
if((millis() - lastUpdate) > updateInterval) { //Using millis() to allow for multitasking
lastUpdate = millis();
pos += increment; //Move wing forwards until it reaches 80 degrees
servo.write(pos);
if ((pos >= 80) || (pos <= 0)) { //Wing moves 80 degrees backwards
increment = -increment; //Reverse direction
}
}
}
};
Sweeper sweeper1(5, 5); //Left wing will move 5 degrees every 5 milliseconds
Sweeper sweeper2(5, 5); //Right wing will move 5 degrees every 5 milliseconds
void setup() {
pinMode(BUTTON_PIN, INPUT); //Set button pin mode to input
Serial.begin(9600);
radio.begin();
radio.openReadingPipe(0, address); //Set up radio to listen on pipe 0 with address "ADDRESS01"
radio.setPALevel(RF24_PA_MIN); //Adjust power level to minimum as modules will be close together
radio.startListening(); //The Transmitter does not receive data
Wire.begin();
mpu6050.begin();
mpu6050.calcGyroOffsets(true); //Used to remove the other forces acting on the gryoscope
sweeper1.Attach(3); //Attach left servo to pin 3
sweeper2.Attach(5); //Attach right servo to pin 5
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
strip.begin();
strip.show();
showColour(strip.Color(255, 255, 255), 50); //Start program by setting colour to white and brightness to 50
strip.setBrightness(50); //Set the LEDs brightness to 50
}
void loop() {
changecolor(); //Call function that changes colour when a message is sent from the transmitter
gyroServo(); //Call function that combines MPU6050 and servos
animationButton(); //Call function that uses a button to change light animations
}
void changecolor() { //Function to change the colour of light when message is sent from the transmitter
while (radio.available()) { //While the transmitter and receiver are connected
char txt[4] = ""; //Create a blank array of characters where the message sent from the transmitter will be stored
radio.read(&txt, sizeof(txt)); //Read the message being sent from transmitter and store it in the txt array
Serial.println(txt); //Display to see if anything is being sent from the transmitter
if (txt[0] == 'C') { //If a colour change is being initiated...
if (txt[1] == '1') { //(Number corresponds to how many times user has pressed force sensor)
Serial.print("text: ");
Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
Serial.println(txt[1]);
showColour(colours[0], 50); //Display the appropriate colour from the array according to the number that's being received
} else if (txt[1] == '2') {
Serial.print("text: ");
Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
Serial.println(txt[1]);
showColour(colours[1], 50); //Display the appropriate colour from the array according to the number that's being received
} else if (txt[1] == '3') {
Serial.print("text: ");
Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
Serial.println(txt[1]);
showColour(colours[2], 50); //Display the appropriate colour from the array according to the number that's being received
} else if (txt[1] == '4') {
Serial.print("text: ");
Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
Serial.println(txt[1]);
showColour(colours[3], 50); //Display the appropriate colour from the array according to the number that's being received
} else if (txt[1] == '5') {
Serial.print("text: ");
Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
Serial.println(txt[1]);
showColour(colours[4], 50); //Display the appropriate colour from the array according to the number that's being received
} else if (txt[1] == '0') {
Serial.print("text: ");
Serial.print(txt[0]); //Display to see if what's being sent from the transmitter is being received
Serial.println(txt[1]);
showColour(colours[5], 50); //Display the appropriate colour from the array according to the number that's being received
}
}
}
}
void showColour(uint32_t color, int brightness) { //Function to show a given colour and brightness
for (int i = 0; i < strip.numPixels(); i++) {
uint8_t red = (color >> 16) & 0xFF;
uint8_t green = (color >> 8) & 0xFF;
uint8_t blue = color & 0xFF;
strip.setPixelColor(i, strip.Color(red * brightness / 255, green * brightness / 255, blue * brightness / 255));
strip.show();
}
}
void gyroServo() { //Function to move servos according to the gyroscope movements
sweeper1.Update();
sweeper2.Update(); //Update state of servos everytime the gyroscope is checked
mpu6050.update(); //Update gyroscope to check position of the board(hat)
x = mpu6050.getAngleX(); //Read rotation on x axis
y = mpu6050.getAngleY(); //Read rotation on y axis
z = mpu6050.getAngleZ(); //Read rotation on z axis
if (millis() - timer > 500) { //Every 1/2 second, record the gyroscope position
prevX = x;
prevZ = z;
timer = millis();
}
if (millis() - timer2 > 1000) { //Every second, record the difference between the previous gyroscope position and current one
xDiff = prevX - x;
zDiff = prevZ - z;
timer2 = millis();
}
if (zDiff>15 || zDiff<-15) { //Move both wings when the hat is moved at least 15 degrees on the z axis in a second
sweeper1.Attach(3);
sweeper2.Attach(5);
Serial.println(zDiff);
} else if (xDiff>20 || xDiff<-20) { //Move both wings when the hat is moved at least 15 degrees on the z axis in a second
sweeper1.Attach(3);
sweeper2.Attach(5);
Serial.println(xDiff);
} else if (y>30) { //Rotating hat past 30 degrees to the right on the y axis causes left wing to move
sweeper1.Attach(3);
} else if (y<-30) { //Rotating hat past 30 degrees to the left on the y axis causes right wing to move
sweeper2.Attach(5);
} else { //If none of these conditions are met, wings do not move
sweeper1.Detach();
sweeper2.Detach();
}
if (millis() - timer3 > 3000) { //Used to see if gyroscope is responding and working correctly
Serial.print("angleX : ");Serial.print(x);
Serial.print(" angleY : ");Serial.print(y);
Serial.print(" angleZ : ");Serial.print(z);
Serial.println("");
timer3 = millis(); //millis() used instead of delay() to not interfere with multitasking features
}
}
void animationButton() { //Function that changes animation when button is pressed
bool newState = digitalRead(BUTTON_PIN); //Get current button state
if (newState == LOW && oldState == HIGH) { //Check if button is pressed
delay(20); //Short delay to debounce button
newState = digitalRead(BUTTON_PIN); //Check if button is still low after debounce
if (newState == LOW) { //If button is pressed, increment variable that keeps track of the animation type
showType++;
if (showType > 9) //Theres 9 animations, so reset variable to 0
showType=0;
startShow(showType);
}
}
oldState = newState; //Set the last button state to the old state
}
void startShow(int i) { //Function used to display different animations
switch(i){
case 0: colorWipe(strip.Color(0, 0, 0), 50); //Off
break;
case 1: colorWipe(strip.Color(255, 0, 0), 50); //Red
break;
case 2: colorWipe(strip.Color(0, 255, 0), 50); //Green
break;
case 3: colorWipe(strip.Color(0, 0, 255), 50); //Blue
break;
case 4: theaterChase(strip.Color(127, 127, 127), 50); //White
break;
case 5: theaterChase(strip.Color(127, 0, 0), 50); //Red
break;
case 6: theaterChase(strip.Color( 0, 0, 127), 50); //Blue
break;
case 7: rainbow(20);
break;
case 8: theaterChaseRainbow(50);
break;
}
}
//Animation function - Reused from Adafruit NeoPixel example code 'strandtest'
void colorWipe(uint32_t color, int wait) { //Animation 1
for(int i=0; i<strip.numPixels(); i++) { //For each pixel in strip...
strip.setPixelColor(i, color); //Set pixel's color (in RAM)
strip.show(); //Update strip to match
delay(wait);
}
}
void theaterChase(uint32_t color, int wait) { //Animation 2
for(int a=0; a<10; a++) { // Repeat 10 times...
for(int b=0; b<3; b++) { //'b' counts from 0 to 2...
strip.clear(); //Set all pixels in RAM to 0 (off)
//'c' counts up from 'b' to end of strip in steps of 3...
for(int c=b; c<strip.numPixels(); c += 3) {
strip.setPixelColor(c, color); //Set pixel 'c' to value 'color'
}
strip.show(); //Update strip with new contents
delay(wait); //Pause for a moment
}
}
}
void rainbow(int wait) { //Animation 3
for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
strip.rainbow(firstPixelHue);
strip.show(); //Update strip with new contents
delay(wait);
}
}
void theaterChaseRainbow(int wait) { //Animation 4
int firstPixelHue = 0; //First pixel starts at red (hue 0)
for(int a=0; a<30; a++) { //Repeat 30 times...
for(int b=0; b<3; b++) { //'b' counts from 0 to 2...
strip.clear(); //Set all pixels in RAM to 0 (off)
//'c' counts up from 'b' to end of strip in increments of 3...
for(int c=b; c<strip.numPixels(); c += 3) {
//hue of pixel 'c' is offset by an amount to make one full
//revolution of the color wheel (range 65536) along the length
//of the strip (strip.numPixels() steps):
int hue = firstPixelHue + c * 65536L / strip.numPixels();
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); //hue -> RGB
strip.setPixelColor(c, color); //Set pixel 'c' to value 'color'
}
strip.show(); //Update strip with new contents
delay(wait); //Pause for a moment
firstPixelHue += 65536 / 90; //One cycle of color wheel over 90 frames
}
}
}
uint32_t Wheel(byte WheelPos) { //Animation 4
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
} else if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
} else {
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
}
Testing & Shortcomings
Multitasking with Arduino
Servo Rotation
Initially we utilized For loops in order to make the servos move. However, this proved to not work very well in the grand scheme of the project, as we had many features that required multitasking. In order to combat this issue, we added a Sweeper class that used millis() in one of the methods. Using millis(), the servo is incremented by 5 degrees every 5 milliseconds after creating 2 Sweeper objects such that Sweeper sweeper1(5, 5) and Sweeper sweeper2(5, 5).
Animation Control
Each animation is a sequence of LEDs with changing colors. There is a delay for each sequence to be displayed. We tried to use millis(), but it could not break out of the while or for loops when the button is triggered. To overcome this limitation, we opted to reduce the animation duration instead of endlessly looping, in order to allow the button to trigger a change in the animation sequence. The animation functions are called in switch cases instead of while loops.
Wireless Communication
The NRF24L01 transceiver disconnected while testing. The two microcontrollers for the host and transmitter had to be reset before starting to send texts. When another functionality was implemented to change the color animation based on a different text message, the receiver could not configure it. The communication lagging happened due to various reason such as noise interference and short distance between the host and receiver.
For future improvements, ESP-01 WiFi or HC-12 SI4463 is recommended to establish the wireless communication because of the high noise prevention. Additionally, instead of altering animations using a button, another force sensor can be implemented on the glove so that with thumb and index tapping, the ring displays various animations.
Components and Batteries Assembly:
The weight of components is not convenient for a hat-glove set, so it is recommended to design a PCB that has all components mounted to it. The glove would also benefit if an Arduino Nano was used instead of an Uno, but our Nano had compatibility issues which is why we stuck to using an Uno. The batteries assembly is unsafe to be right next to the wearer’s head or hand as they might explode. For safety, a couple of fuses or battery switches should implemented.
You must be logged in to post a comment.